+
+
+
+
+ {STATUS_LABELS[entry.status].de}
+
+
+
+ {CATEGORY_LABELS[category].de}
+
+
+
+ {#if entry.inferredFrom}
+
+ {$_('lasts.detail.inferredFrom')}: {entry.inferredFrom.refTable}
+ {#if entry.inferredFrom.frequencyHint}
+ — {entry.inferredFrom.frequencyHint}
+ {/if}
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if entry.status === 'suspected'}
+
+
{$_('lasts.detail.confidenceLabel')}
+
+ {#each CONFIDENCE_OPTS as opt}
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{$_('lasts.detail.tendernessLabel')}
+
+ {#each RATING_STARS as star}
+
+ {/each}
+
+
+
+
{$_('lasts.detail.wouldReclaimLabel')}
+
+ {#each WOULD_RECLAIM_OPTS as opt}
+
+ {/each}
+
+
+
+
+
+
+
+
+ {#if entry.status === 'reclaimed'}
+
+ {#if entry.reclaimedAt}
+
+ {$_('lasts.detail.reclaimedAt')}:
+ {formatDate(entry.reclaimedAt.slice(0, 10))}
+
+ {/if}
+ {#if entry.reclaimedNote}
+
{entry.reclaimedNote}
+ {/if}
+
+ {/if}
+
+
+ {#if entry.status !== 'reclaimed'}
+
+ {$_('lasts.detail.visibilityLabel')}
+
+ {#if visibilityError}
+ {visibilityError}
+ {/if}
+ {#if entry.visibility === 'unlisted' && entry.unlistedToken && shareUrl}
+
+
+
+ {/if}
+
+ {/if}
+
+
+ {#if reclaimOpen}
+
+ {/if}
+
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/modules/lasts/views/InboxView.svelte b/apps/mana/apps/web/src/lib/modules/lasts/views/InboxView.svelte
new file mode 100644
index 000000000..25c3640d1
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/lasts/views/InboxView.svelte
@@ -0,0 +1,284 @@
+
+
+
+
Unbekannter Link-Typ
diff --git a/docs/plans/lasts-module.md b/docs/plans/lasts-module.md
new file mode 100644
index 000000000..2c2914b3e
--- /dev/null
+++ b/docs/plans/lasts-module.md
@@ -0,0 +1,519 @@
+# Lasts — Module Plan
+
+## Status (2026-04-26)
+
+**M1 Skelett: DONE** — `lasts/`-Modul registriert, Dexie v51, Encryption-Registry, Per-Space-Welcome-Seed, Route `/lasts` mountet mit Empty-State, Refactor `firsts/types.ts` extrahiert Categories nach `data/milestones/categories.ts` ohne API-Bruch.
+
+**M2 CRUD + DetailView: DONE** — ListView mit StatusTabs (Alle | Vermutet | Bestätigt | Aufgehoben), Quick-Add (Suspected/Confirmed-Toggle, Enter erstellt + öffnet Detail), Context-Menu, Search ab > 5 Einträgen. DetailView (`views/DetailView.svelte` + Route `/lasts/entry/[id]`) mit always-editable Feldern, Autosave on blur/change, Lifecycle-Buttons (Bestätigen, Aufheben mit Inline-Note), Delete + Auto-Back. 44 i18n-Keys × 5 Locales.
+
+**M3 Inbox + Inferenz: DONE** — Dexie v52 mit `lastsCooldown`-Tabelle (deterministische ID `${refTable}:${refId}`, 12-Monate-Cooldown). Inferenz-Engine (`inference/scan.ts` + `inference/sources/places.ts`) als Source-Registry-Pattern. Erste Quelle: Places — Heuristik `visitCount ≥ 5 ∧ Span ≥ 180d ∧ Silence ≥ 365d`. Orchestrator dedupliziert gegen existierende Lasts + Cooldown-Liste. `suggestLasts()`-Store-Methode triggert Scan + schreibt Survivors als `suspected` mit `inferredFrom`. InboxView (`views/InboxView.svelte` + Route `/lasts/inbox`) mit "Jetzt scannen"-Button + Akzeptieren (löscht inferredFrom → bleibt suspected im Hauptfeed) / Verwerfen (delete + cooldown). ListView trägt Inbox-Link + Live-Count rechts in der Tab-Bar.
+
+**Deferred zu M3.b**: `contacts`-Source braucht `lastInteractionAt`-Feld auf Contact-Records (existiert nicht); `habits`-Source braucht direkten Timestamp im HabitLog (aktuell via `timeBlockId`-Join). Beide nachziehen sobald jeweilige Felder existieren oder via separater Aggregation. Tabu-Liste (kein Auto-Suggest für `relationship: family|partner` in contacts, no refs zu period/dreams/losses/regret) wird erst beim Hinzufügen der jeweiligen Source aktiv — Hooks sind im Orchestrator vorbereitet (kann pro Source-Scanner früh ausgefiltert werden, bevor Kandidat zurückkehrt).
+
+**M4 AI-Tools: DONE** — 5 Tools im `AI_TOOL_CATALOG` (`@mana/shared-ai/src/tools/schemas.ts`):
+- `create_last` (propose) — neuer Last suspected | confirmed
+- `confirm_last` (propose) — suspected → confirmed mit Reflexion (date, meaning, whatIKnewThen, whatIKnowNow, tenderness 1-5, wouldReclaim no/maybe/yes)
+- `reclaim_last` (propose) — confirmed → reclaimed mit optionaler Note
+- `list_lasts` (auto) — gefiltert nach status + category, max 100
+- `suggest_lasts` (auto) — triggert Inferenz-Engine, schreibt Survivors als suspected mit inferredFrom in Inbox
+
+Webapp-Implementierungen in `lasts/tools.ts` (Vault-locked-Handling für `list_lasts`, Validierung von Enums + Range-Checks). Registriert in `data/tools/init.ts`. Server-side Planner-Drift-Test (`services/mana-ai/src/planner/tools.test.ts`) bestätigt Konsistenz: 4/4 grün. Shared-AI Schema-Tests: 6/6 grün.
+
+**Nicht in M4**: `
` Wiring in ListView entfällt — die Komponente existiert nicht im Repo (root `apps/mana/CLAUDE.md` beschreibt sie als "wired in /todo, /calendar, /places, /drink, /food, /news, /notes" aber `find apps/mana -iname "*proposal*"` liefert null). Aspirational-Doc-Drift, nicht meine M4-Lieferung. Sobald die Komponente existiert, ist `
` ein Einzeiler in `lasts/ListView.svelte` zwischen Tab-Bar und Quick-Add.
+
+**M5 Reminders + Settings: DONE** (Pivot zu In-App-Banner statt OS-Push) — kein PWA-Push-System existiert im Repo (`mana-notify` ist server-side für Email/Web-Push, kein Service-Worker-Push-Subscription, keine `Notification.requestPermission()` Aufrufe in der webapp). Pragmatischer Pfad analog zum **augur `DueBanner`-Pattern**: in-app surfacing der heutigen Lasts beim Öffnen von `/lasts`, opt-in-toggelbar in den Settings.
+
+Lieferung:
+- **Pure Date-Math** (`lib/reminders.ts`): `isSameDayOfYear`, `yearsBetween`, `findAnniversaryLasts` (confirmed lasts mit `date` heute vor X Jahren), `findRecognitionAnniversaryLasts` (any status mit `recognisedAt` heute vor X Jahren). 12 Vitest-Cases, alle grün.
+- **Settings-Store** (`stores/settings.svelte.ts`) via `createAppSettingsStore('lasts-settings', …)`: 4 persistent localStorage-Flags — `anniversaryReminders`, `recognitionReminders`, `inboxNotify`, `bannerMaxItems` (Default 3). Modul-Pattern analog `todoSettings`/`broadcastSettings`.
+- **DueBanner-Component** (`components/DueBanner.svelte`): rendert max-N Zeilen — Anniversaries → Recognition-Anniversaries → Inbox-Notify in dieser Priorität, deduplicated wenn Anniversary + Recognition denselben Last treffen. Klick → `/lasts/entry/[id]` oder `/lasts/inbox`.
+- **SettingsView + Route** (`views/SettingsView.svelte` + `/lasts/settings`): 3 Toggles + Slider für `bannerMaxItems` + "Zurücksetzen" + "Test-Banner zeigen" (rendert 4 Sek Beispiel) + Footnote zur fehlenden OS-Push-Infrastruktur.
+- **ListView-Wiring**: `
` ganz oben, `⚙`-Settings-Link in der Tab-Bar.
+- **i18n**: 22 neue Keys × 5 Locales (banner.* + settings.*).
+
+**Deferred zu M5.b — echtes OS-Push** sobald PWA-Push-Infra existiert: Service-Worker-Subscription via `Notification.requestPermission()` + Push-Subscription-Endpoint, `mana-notify`-Backend-Cron für Anniversary-Scans (statt client-side beim App-Öffnen), Hard-Cap 2 Pushs/Monat als Server-Throttle. Die Date-Math-Helper (`findAnniversaryLasts` + `findRecognitionAnniversaryLasts`) sind bereits push-tauglich purer Code ohne Svelte-Runen-Bindings — können der Server-Cron ohne Refactor wiederverwendet werden.
+
+**Vor-Push-Validatoren**: `validate:i18n-parity` rot wegen pre-existing untracked WIP `apps/mana/apps/web/src/lib/i18n/locales/settings/{de,en,es,fr}.json` — `it.json` fehlt; nicht in git history, mtime = 20:42 (nicht meine Lieferung; vermutlich parallele Session oder Hook). Mein `lasts/`-Namespace hat alle 5 Locales aligned.
+
+**M6 Visibility + Unlisted-Sharing: DONE** — Modul auf das Repo-weite `@mana/shared-privacy`-System aufgesattelt, analog augur/library/places/events.
+
+Lieferung:
+- **Type-Erweiterung**: `LocalLast` und `Last` haben jetzt `visibility`, `visibilityChangedAt`, `visibilityChangedBy`, `unlistedToken`, `unlistedExpiresAt`. `toLast` setzt Default `'private'` (intim, anders als firsts).
+- **Encryption-Registry**: visibility/Token-Felder bleiben plaintext (Server-Routing-Felder, keine User-typed Inhalte). Crypto-Audit weiter sauber bei 211 Tables.
+- **Per-Space-Welcome-Seed**: explizit `visibility: 'private'`.
+- **Resolver**: `buildLastBlob` in `data/unlisted/resolvers.ts` — Whitelist nur "reflective core" (title, status, category, date, meaning, whatIKnewThen, whatIKnowNow, tenderness, wouldReclaim). `note`, `inferredFrom`, person/place/media-Refs, recognisedAt, reclaimedNote bleiben PRIVAT. **Hard-Block: reclaimed Lasts werden nicht serialisiert** — die zurückgekommen-Emotion ist verletzlicher als der Last selbst.
+- **Store-Methoden** (`stores/items.svelte.ts`): `setVisibility(id, level)` mit publish/revoke unlisted-Snapshot via `@mana/shared-privacy/unlisted-client`, `regenerateUnlistedToken(id)` für Token-Rotation, `setUnlistedExpiry(id, date)` für TTL-Update. Reclaim-Lasts → unlisted wird im Store geblockt mit klarer Fehlermeldung.
+- **SharedLastView** (`SharedLastView.svelte`): public-render-Komponente, kontemplativer Ton, weisse Karte mit Kategorie-Akzent links, "Damals / Heute"-Reflexion zweispaltig, optional Tenderness-Stars + WouldReclaim. "via Mana Lasts" Footer, kein Marketing.
+- **Share-Dispatcher**: `routes/share/[token]/+page.svelte` kennt jetzt `data.collection === 'lasts'`.
+- **DetailView-Wire**: `
` + `` Block oberhalb der Lifecycle-Action-Bar — nur sichtbar für non-reclaimed Lasts.
+- **i18n**: `lasts.detail.visibilityLabel` × 5 Locales.
+
+**M6 Done-Definition**: ✓ Last kann auf `unlisted` gesetzt werden, Share-Link funktioniert öffentlich ohne Login (Snapshot-Server-Resolution via mana-api `/api/v1/unlisted/public/`, dann SSR-Render via `SharedLastView`).
+
+**Vor-Push-Validatoren** weiter: 2 svelte-check-Errors in `SettingsSidebar.svelte` — gleiche Orphan-WIP-Quelle wie `settings/it.json` (nicht meine Lieferung). Mein Code: 0/0/0 in allen reminders.test (12/12), i18n-keys baseline-equal, crypto 211 ✓.
+
+**M7 Timeline-Aggregator + Year-Recap: DONE** — Cross-modulares "Meilensteine"-Surface, das firsts ∪ lasts als ein chronologisches Feed rendert.
+
+Lieferung:
+- **Pure Aggregator** `data/milestones/timeline-query.ts`: `mergeMilestones(firsts, lasts)` mit Discriminator-Direction (`'first'` | `'last'`), Pinned-First-Sort, Date-desc-fallback-zu-createdAt. Plus `filterByDirection`, `filterByYear`, `compareTimelineDesc`. Reactive Hook `useMilestonesTimeline()` lädt beide Tabellen parallel + dekodiert client-seitig.
+- **Recap-Aggregator** `data/milestones/year-recap.ts`: `buildMilestonesRecap(entries, year)` → `{ year, total, firsts, lasts, byCategory: per-Cat × per-Direction, topFirsts (5), topLasts (5), activeMonths: 'YYYY-MM'[] }`. Bewusst nur Counts, keine Hit-Rate/Brier-Style-Metriken (lasts/firsts haben kein "verifizierbares" Element).
+- **Tests** `timeline-query.test.ts`: 12/12 passed (mergeMilestones, filterByDirection, filterByYear, buildMilestonesRecap mit allen Feldern, compareTimelineDesc).
+- **TimelineView** (`lib/components/milestones/TimelineView.svelte`): Tab-Bar (Alle | Firsts | Lasts), Karten mit Direction-Chip + Kategorie-Pille, Klick → jeweilige Modul-Detail-Route. Recap-Link top-right zum aktuellen Jahr.
+- **YearRecapView** (`lib/components/milestones/YearRecapView.svelte`): Hero-Stats (Total | Firsts | Lasts mit direction-coloring), Kategorie-Breakdown mit Per-Direction-Counts, Top-5-Listen pro Direction (klickbar), Active-Months-Strip.
+- **Routes** `/milestones` und `/milestones/recap/[year]` (nutzen `RoutePage` mit `appId="milestones"` — Registry hat keinen Eintrag, fallback rendert sauber mit Title-Override).
+- **Cross-Link** in `lasts/ListView.svelte` Tab-Bar: "Meilensteine"-Link führt zu `/milestones`.
+- **i18n-Namespace** `milestones/` × 5 Locales mit `timeline.*`, `tabs.*`, `recap.*` Keys (i18n-parity nun 39 namespaces × 5 locales aligned).
+
+**M7 Done-Definition**: ✓ Timeline-View zeigt firsts und lasts interleaved, sortierbar (date desc, pinned-first), filterbar (direction tabs, year-filter über Recap-Route).
+
+**Nicht in M7** (Polish): Eintrag in `packages/shared-branding/src/mana-apps.ts` als "milestones"-App mit Icon/Color für den App-Launcher. Ohne Eintrag funktioniert die Route via direkte URL-Navigation oder den Cross-Link von `/lasts`. Kann später ergänzt werden — kostet einen App-Icon-SVG und einen mana-apps.ts-Block.
+
+---
+
+**M1-M7 SHIPPED** — Modul `lasts` ist feature-komplett gemäß Plan. Validation: 0/0/0 in svelte-check (7645 files), 24/24 tests grün (12 reminders + 12 timeline), i18n-parity 39×5 aligned (+2 namespaces: lasts, milestones), i18n-keys baseline-equal, crypto 211 tables. Browser-Test offen (`pnpm run mana:dev` → `/lasts`, `/lasts/inbox`, `/lasts/settings`, `/lasts/entry/[id]`, `/milestones`, `/milestones/recap/2026`).
+
+Vorbild: das bereits existierende Modul [`firsts/`](../../apps/mana/apps/web/src/lib/modules/firsts/) (Bucket-List + Reflexion mit `dream → lived` Lifecycle, 11 Kategorien, Foto/Audio/Place/People). `lasts` ist das spiegelbildliche Modul: das *letzte* Mal, dass du etwas getan/gefühlt/gesehen hast — meistens erst rückwirkend erkennbar.
+
+---
+
+## Ziel
+
+Ein Modul `lasts`, in dem der Nutzer **Letzte Male** erfasst und reflektiert. Kernfrage: *"Wann habe ich das eigentlich zum letzten Mal getan/gefühlt — und wusste ich's damals?"*
+
+Zwei Eingabewege:
+
+1. **Manuell** — der User markiert bewusst (selten, oft an Wendepunkten: letzter Tag im Job, letztes Konzert mit X, letzte Nacht in der alten Wohnung).
+2. **Inferred** — die AI scannt regelmässig die anderen Module (places, contacts, food, habits, routes, music) auf Frequenz-Muster und schlägt Last-Kandidaten in einer Inbox vor: *"Du warst seit 18 Monaten nicht mehr in [Café X] — vorher 3×/Woche. Last?"*
+
+Nicht im Scope:
+- Trauer-Workflow für Verluste/Tod (eigenes Modul `losses` aus Module-Ideas).
+- Bucket-List / Vorfreude — bleibt bei `firsts`.
+- Streak-Tracking — bleibt bei `habits`.
+
+## Abgrenzung
+
+- **Nicht `firsts`**: Tonalität ist anders (Kontemplation vs. Vorfreude), Lifecycle ist anders (`suspected → confirmed` vs. `dream → lived`), Push-Quoten sind anders. Eigenes Modul, eigene Tabelle. Geteilt wird nur der Code drumherum (Komponenten, Kategorien, Picker).
+- **Nicht `losses`**: dort gehört der Trauer-Workflow für markierte Verluste hin. `lasts` ist breiter und oft *zärtlich* statt schmerzhaft. Ein Last kann zu einem Loss eskaliert werden (Cross-Link), aber ein Loss erzeugt keinen Last.
+- **Nicht `eras`**: Eras aggregieren ganze Lebensabschnitte. `lasts` sind die Endpunkte einzelner Dinge — Eras können auf Lasts referenzieren ("Burnout-Jahr endete mit folgenden Lasts: …").
+- **Nicht `journal`**: ein Journal-Eintrag ist datiert auf den Schreibtag; ein Last ist datiert auf das (vermutete) Ereignis. Reflexion lebt im Last-Datensatz selbst, nicht als verlinkter Journal-Eintrag.
+
+## Architektur-Entscheidung: zwei Tabellen, geteilte Komponenten
+
+**Eigene Dexie-Tabelle `lasts`** — nicht `milestones` mit Diskriminator. Begründung in der vorgelagerten Diskussion (Modul-Boundary, `_pendingChanges`-Tagging, Encryption-Registry pro Tabelle, eigene Visibility-Defaults, eigene Migrations).
+
+**Geteilt** wird stattdessen alles ausserhalb der Tabelle:
+- Kategorien (11 Stück, identisch mit `firsts`) → `lib/data/milestones/categories.ts`
+- Lifecycle-Helpers, Validators → `lib/data/milestones/lifecycle.ts`
+- Timeline-Aggregator-Query (firsts + lasts gemerged) → `lib/data/milestones/timeline-query.ts`
+- UI-Komponenten (Card, Editor, ReflectionFields, LifecycleToggle, CategoryPill) → `lib/components/milestones/`
+
+Das macht ein zukünftiges drittes Geschwister-Modul (`peaks`, `pivots`, `cycles`) trivial — nur neue Tabelle + Lifecycle-Strings, alles andere ist da.
+
+## Lifecycle-Mapping
+
+| `firsts` | `lasts` |
+|---|---|
+| `dream` (geplant, will ich erleben) | `suspected` (vermutet, vom User oder AI markiert) |
+| `lived` (gemacht, mit Reflexion) | `confirmed` (bestätigt, mit Reflexion) |
+| — (kein Rückwärts-Pfad) | `reclaimed` (war doch nicht das letzte Mal — ist wieder passiert) |
+
+`reclaimed` ist semantisch wichtig: das Modul soll mit dem Leben atmen. Wenn du wieder mit der Person sprichst oder doch wieder ins Café gehst, klickst du "Aufgehoben" — der Eintrag bleibt in der History (mit Notiz "Aufgehoben am …"), erscheint aber nicht mehr im Hauptfeed. Reclaimed-Items sind ihre eigene kleine emotional bedeutsame Untersicht.
+
+## Felder-Mapping (`lasts` ↔ `firsts`)
+
+| `firsts` Feld | `lasts` Feld | Bemerkung |
+|---|---|---|
+| `title` | `title` | identisch |
+| `status: 'dream'\|'lived'` | `status: 'suspected'\|'confirmed'\|'reclaimed'` | Discriminator |
+| `category` | `category` | gleiches Enum |
+| `motivation` | `meaning` | "Was hat es dir bedeutet?" statt "Warum willst du das?" |
+| `priority: 1\|2\|3` | `confidence: 'probably'\|'likely'\|'certain'` | Wie sicher ist es das letzte Mal? |
+| `date` | `date` | Vermutetes/bestätigtes Datum (oft approximativ) |
+| `note` | `note` | identisch |
+| `expectation` | `whatIKnewThen` | "Was wusstest du damals nicht?" |
+| `reality` | `whatIKnowNow` | "Was weisst du jetzt?" |
+| `rating: 1-5` | `tenderness: 1-5` | Nicht "gut/schlecht" — wie sehr berührt es dich heute |
+| `wouldRepeat: yes\|no\|definitely` | `wouldReclaim: no\|maybe\|yes` | Würdest du es zurückholen, wenn du könntest? |
+| `personIds[]` | `personIds[]` | identisch |
+| `placeId` | `placeId` | identisch |
+| `mediaIds[]` | `mediaIds[]` | identisch |
+| `audioNoteId` | `audioNoteId` | identisch |
+| `sharedWith` | `sharedWith` | identisch |
+| `isPinned`, `isArchived` | `isPinned`, `isArchived` | identisch |
+| — | `recognisedAt` | Wann wurde es als Last erkannt (oft Jahre nach `date`) — wichtig für "vor X Jahren erkannt"-Reminder |
+| — | `inferredFrom` | Optionales Provenance-Object: `{ tool: 'suggest_lasts', refTable: 'places', refId: '...', frequencyHint: '3x/week → 0 in 18mo' }` für AI-Vorschläge |
+
+## Modul-Struktur
+
+```
+apps/mana/apps/web/src/lib/modules/lasts/
+├── types.ts # LocalLast, Last, LastStatus, LastConfidence, WouldReclaim, Tenderness
+├── collections.ts # lastTable + LASTS_GUEST_SEED (1 confirmed + 1 suspected Beispiel)
+├── queries.ts # useAllLasts, useLastsByStatus, useLastsByCategory, useLast(id), useLastsInbox (suspected only), useLastsStats
+├── stores/
+│ └── items.svelte.ts # createLast, updateLast, confirmLast, reclaimLast, suggestLasts (Inferenz-Loop), pin/archive/delete
+├── tools.ts # AI-Tools: create_last (propose), confirm_last (propose), reclaim_last (propose), list_lasts (auto), suggest_lasts (auto)
+├── inference/
+│ └── scan.ts # Cross-Modul-Reader: places/contacts/food/habits/routes für Frequenz-Drops
+├── ListView.svelte # Modul-Root (komponiert StatusTabs + Liste, leitet zu InboxView)
+├── InboxView.svelte # Suspected-Vorschläge: Akzeptieren / Verwerfen
+├── DetailView.svelte # Einzelansicht inkl. Reflexion + Reclaim-Button
+├── module.config.ts # { appId: 'lasts', tables: [{ name: 'lasts' }] }
+└── index.ts # Re-Exports
+```
+
+```
+apps/mana/apps/web/src/lib/data/milestones/ # NEU — geteilt firsts ↔ lasts
+├── categories.ts # MilestoneCategory, CATEGORY_LABELS, CATEGORY_COLORS (extrahiert aus firsts/types.ts)
+├── lifecycle.ts # Status-Transition-Helpers, Validators
+├── shared-types.ts # Person/Place/Media-Ref-Shapes (re-exports BaseRecord)
+└── timeline-query.ts # useMilestonesTimeline() — Union-Query firsts ∪ lasts, sortiert nach date
+```
+
+```
+apps/mana/apps/web/src/lib/components/milestones/ # NEU — geteilte UI
+├── MilestoneCard.svelte # generisch, props: direction, status, category, title, date, isPinned
+├── MilestoneEditor.svelte # Formular-Body — slot-basiert für direction-spezifische Reflexions-Felder
+├── ReflectionFields.svelte # zwei Textareas, Labels via props
+├── LifecycleToggle.svelte # generisch, status-options via props
+├── CategoryPill.svelte # Farb-Pill aus CATEGORY_COLORS
+└── PeoplePlaceMediaPicker.svelte # bündelt die drei Picker (existieren schon einzeln)
+```
+
+```
+apps/mana/apps/web/src/routes/(app)/
+├── lasts/+page.svelte # NEU — Modul-Root
+├── lasts/[id]/+page.svelte # NEU — Detail-Route
+├── lasts/inbox/+page.svelte # NEU — Suspected-Inbox (separate Route weil eigenes mentales Modell)
+└── milestones/+page.svelte # OPTIONAL M7 — Timeline-Aggregator firsts + lasts
+```
+
+## Daten-Schema
+
+### `LocalLast` (Dexie)
+
+```typescript
+import type { BaseRecord } from '@mana/local-store';
+import type { MilestoneCategory } from '$lib/data/milestones/categories';
+
+export type LastStatus = 'suspected' | 'confirmed' | 'reclaimed';
+export type LastConfidence = 'probably' | 'likely' | 'certain';
+export type WouldReclaim = 'no' | 'maybe' | 'yes';
+
+export interface InferredFrom {
+ tool: string; // z.B. 'suggest_lasts'
+ refTable: string; // 'places' | 'contacts' | 'food' | 'habits' | 'routes' | …
+ refId: string;
+ frequencyHint?: string; // human-readable: '3x/week → 0 in 18mo'
+ scannedAt: string; // ISO
+}
+
+export interface LocalLast extends BaseRecord {
+ title: string;
+ status: LastStatus;
+ category: MilestoneCategory;
+
+ // Recognition phase
+ confidence: LastConfidence | null; // wie sicher
+ inferredFrom: InferredFrom | null; // null = manuell
+
+ // Confirmed phase (Reflexion)
+ date: string | null; // ISO date — vermutet oder bestätigt
+ meaning: string | null; // "was hat es bedeutet"
+ note: string | null;
+ whatIKnewThen: string | null;
+ whatIKnowNow: string | null;
+ tenderness: number | null; // 1-5
+ wouldReclaim: WouldReclaim | null;
+
+ // Reclaimed phase
+ reclaimedAt: string | null; // ISO — falls aufgehoben
+ reclaimedNote: string | null; // optional Begründung
+
+ // Social
+ personIds: string[];
+ sharedWith: string | null;
+
+ // Rich media
+ mediaIds: string[];
+ audioNoteId: string | null;
+ placeId: string | null;
+
+ // Meta
+ recognisedAt: string; // wann wurde der Last erkannt (≠ createdAt nicht garantiert, aber meist gleich)
+ isPinned: boolean;
+ isArchived: boolean;
+}
+```
+
+### Domain-Typ `Last` — gleiche Form ohne BaseRecord-Internals (analog `firsts/types.ts`).
+
+### Encryption-Registry
+
+In `apps/mana/apps/web/src/lib/data/crypto/registry.ts` (analog zu `firsts`-Block):
+
+```typescript
+// ─── Lasts ───────────────────────────────────────────────
+// User-typed text fields are encrypted. Status, category, confidence, dates,
+// tenderness, wouldReclaim, personIds, mediaIds, placeId, inferredFrom stay
+// plaintext for indexing/filtering and so the inference scanner can read
+// provenance without master-key access.
+lasts: {
+ enabled: true,
+ fields: ['title', 'meaning', 'note', 'whatIKnewThen', 'whatIKnowNow', 'reclaimedNote', 'sharedWith'],
+},
+```
+
+### Dexie-Migration
+
+Neue Version `db.version(51)` in `apps/mana/apps/web/src/lib/data/database.ts`:
+
+```typescript
+db.version(51).stores({
+ // … alle existierenden Tabellen 1:1 übernehmen aus v50 …
+ lasts: 'id, spaceId, userId, status, category, date, recognisedAt, isPinned, isArchived',
+});
+```
+
+Index-Strategie:
+- `status` — schnelle Filter für Inbox vs. Confirmed-Liste
+- `category` — Kategorie-Filter
+- `date` — Sort + Anniversary-Scans
+- `recognisedAt` — "vor X Jahren erkannt"-Reminder
+- `isPinned`, `isArchived` — Standard-Listing-Filter
+
+Kein Soft/Hard-Split nötig — neue Tabelle, keine bestehenden Daten zu migrieren.
+
+## Inferenz-Engine (`inference/scan.ts`)
+
+Heuristik pro Quell-Modul:
+
+| Quelle | Signal |
+|---|---|
+| `places` | Visit-Frequenz drop: war `≥ N visits / month` über `≥ M months`, jetzt `0 visits` seit `≥ K months` |
+| `contacts` | Last-contact-date in `contacts.lastInteractionAt` (falls vorhanden) — wenn `> threshold` Monate und vorher häufig |
+| `food` | Gericht in `meals` regelmässig, jetzt nicht mehr |
+| `habits` | Habit pausiert oder seit X nicht mehr geloggt |
+| `routes`/`hikes` | Route mit Wiederholungs-Counter, jetzt 0 |
+| `music` (falls Listening-Logs existieren) | Künstler-Drop |
+| `notes`/`writing`/`quotes` | Tag/Theme-Frequenz-Drop |
+
+Default-Schwellen konservativ (Inbox-Lärm ist tödlich für die emotionale Wirkung):
+- minimale Vorgeschichte: ≥ 5 Vorkommen über ≥ 6 Monate
+- minimale Stille: ≥ 12 Monate ohne Vorkommen
+- max. Vorschläge pro Scan: 3
+- Cooldown: keine Wiedervorschläge derselben `(refTable, refId)` für 12 Monate nach Verwerfen
+
+Cron: einmal pro Monat, z.B. am 1. um 9:00 Lokalzeit. Ausführung im AI-Mission-Runner als Mission `lasts.monthly-scan` (oder als simpler client-seitiger Cron-Job — tendiere zu Mission, weil dadurch Audit-Log + Pause-Switch gratis). Modul-Owner: einer der bestehenden Agents oder ein dedizierter "Gefährte"-Agent.
+
+**Hard rules** (in der Heuristik verdrahtet, nicht User-konfigurierbar):
+- Keine Vorschläge für `contacts` mit `relationship: 'family' | 'partner'` ohne explizite Opt-In — Trauer-Trigger.
+- Keine Vorschläge für Refs in `period`, `dreams`, `losses`, `regret/forgive` — zu intim.
+- Wenn `losses` einen Eintrag mit gleicher `personId` hat, suspend alle Inferenz für diese Person komplett.
+
+## AI-Tool-Coverage
+
+Im `AI_TOOL_CATALOG` in `@mana/shared-ai/src/tools/schemas.ts`:
+
+| Modul | Propose | Auto |
+|---|---|---|
+| **lasts** | `create_last`, `confirm_last`, `reclaim_last` | `list_lasts`, `suggest_lasts` |
+
+Schemas (skizziert):
+
+```typescript
+create_last: {
+ policyHint: 'standard',
+ input: { title, category, status?, date?, confidence?, meaning?, note?, personIds?, placeId? }
+}
+confirm_last: {
+ policyHint: 'standard',
+ input: { id, date?, whatIKnewThen?, whatIKnowNow?, tenderness?, wouldReclaim? }
+}
+reclaim_last: {
+ policyHint: 'standard',
+ input: { id, reclaimedAt, reclaimedNote? }
+}
+list_lasts: {
+ policyHint: 'read',
+ input: { status?, category?, sinceDate? }
+}
+suggest_lasts: {
+ policyHint: 'read', // liefert Vorschläge, schreibt nicht
+ input: { sources?: string[], minMonthsSilent?, limit? }
+}
+```
+
+`suggest_lasts` schreibt selbst nichts — der Planner kann das Resultat in eine `create_last`-Proposal umwandeln, die der User approved.
+
+## Push-Notifications (M5)
+
+Drei opt-in-Klassen, getrennt umschaltbar in `/lasts/settings`:
+
+1. **Anniversary-Reminder** — "Heute vor X Jahren das letzte Mal …" (nur für `confirmed` mit `date`).
+2. **Recognition-Reminder** — "Vor X Jahren als Last erkannt: …" (nutzt `recognisedAt`).
+3. **Inbox-Notify** — "3 neue Last-Vorschläge in der Inbox" (max. 1×/Monat nach dem Scan).
+
+Hard-Cap insgesamt: 2 Push pro Monat. Snooze-pro-Item.
+
+Implementierung über das bestehende Notification-System (zu prüfen: existiert das schon zentral, oder ad-hoc pro Modul?). Falls noch nicht vorhanden: M5 als separater Sub-Plan, M1-M4 funktionieren ohne.
+
+## Visibility / Sharing
+
+Default-Visibility: `private`. Anders als `firsts` (oft teilbar — du erzählst gerne von deinem ersten Mal Bungee-Jumping) sind Lasts intim.
+
+Embed-Resolver für Visibility-System (analog `events`/`library` aus `project_visibility_system.md`-Memory): einzelne `lasts` können `unlisted` werden für `/share/[token]`-Routen, mit QR + Expiry. Sinnvolle Public-Aggregate kommen erst in M7+:
+- "Lasts of 2026" Year-Recap (anonymisiert/kuratiert)
+- Embed auf personal-site: poetische Sammlung kuratierter Lasts
+
+## Refactor `firsts/` (Vorbereitung M1)
+
+Damit `lasts` die geteilten Pieces wirklich nutzen kann, muss `firsts/` minimal umgebaut werden:
+
+1. **Extract Categories**: `CATEGORY_LABELS`, `CATEGORY_COLORS` aus `firsts/types.ts` raus → `data/milestones/categories.ts`. `firsts/types.ts` re-exportiert für Abwärtskompatibilität.
+2. **Extract MilestoneCard**: aus `firsts/ListView.svelte` die Listen-Item-Markup-Logik extrahieren in `components/milestones/MilestoneCard.svelte`. `ListView.svelte` rendert dann ``.
+3. **Optional jetzt, sicher später**: ReflectionFields, LifecycleToggle, CategoryPill, PeoplePlaceMediaPicker analog extrahieren. Nicht im kritischen Pfad — kann passieren, wenn `lasts` sie real braucht.
+
+Klassischer **soft-first**-Migrationsstil (siehe Memory `feedback_soft_before_hard_migrations.md`): zuerst extrahieren mit Re-Export-Aliassen, dann später Imports umstellen, dann Aliasse löschen. Aber kein Schema-Change — nur Code-Move, deshalb risikoarm.
+
+## Milestones
+
+### M1 — Refactor + Skelett (~ 1 Tag)
+- `data/milestones/categories.ts` extrahieren, `firsts/types.ts` re-exportiert
+- `components/milestones/MilestoneCard.svelte` extrahieren, `firsts/ListView.svelte` umstellen
+- `lasts/` Modul-Skelett: `module.config.ts`, `types.ts`, `collections.ts`, `index.ts`
+- Dexie v51 mit `lasts`-Tabelle
+- Encryption-Registry-Eintrag
+- Guest-Seed: 1 confirmed Beispiel ("Letzter Tag im alten Job"), 1 suspected ("Vermutlich letztes Mal …")
+- Route `/lasts/+page.svelte` mountet leer mit "Noch keine Lasts"
+
+**Done-Definition**: `lasts`-Modul lädt, leere Liste rendert, Dexie-Inspector zeigt Tabelle, `validate:all` grün.
+
+### M2 — CRUD + ListView + DetailView (~ 1 Tag)
+- `stores/items.svelte.ts`: createLast, updateLast, confirmLast, reclaimLast, pin/archive/delete
+- `queries.ts`: useAllLasts, useLastsByStatus, useLast(id)
+- `ListView.svelte`: StatusTabs (Suspected | Confirmed | Reclaimed), MilestoneCard-basierte Liste
+- `DetailView.svelte` + Route `/lasts/[id]/+page.svelte`: Reflexionsfelder, Lifecycle-Buttons
+- Editor-Component (kann inline oder modal): nutzt geteilten `MilestoneEditor` mit lasts-spezifischen Reflexions-Labels
+
+**Done-Definition**: User kann Last manuell anlegen, von suspected nach confirmed bewegen, reflektieren, reclaimen.
+
+### M3 — Inbox + Inference (~ 1 Tag)
+- `inference/scan.ts`: Place-Drop + Contact-Drop + Habit-Drop Heuristiken (erste drei reichen für M3)
+- `InboxView.svelte` + Route `/lasts/inbox/+page.svelte`: Liste der suspected mit `inferredFrom != null`, Akzeptieren/Verwerfen
+- `suggestLasts()`-Methode im Store (nicht der AI-Tool — direct call), die einen Scan triggern und Suspected-Records anlegen kann
+- "Scan jetzt"-Button für Dev/Manual-Trigger
+- Cooldown-Liste: Tabelle `lastsInferenceCooldown` oder Feld in einer Settings-Tabelle für die "verworfen, nicht wieder vorschlagen"-Logik
+
+**Done-Definition**: Manueller Scan-Trigger erzeugt sinnvolle Vorschläge basierend auf realen places/contacts/habits-Daten; Verwerfen unterdrückt Wiedervorschlag.
+
+### M4 — AI-Tools (~ 0.5 Tage)
+- `tools.ts` mit den fünf Tool-Definitionen
+- Eintrag in `AI_TOOL_CATALOG` (`@mana/shared-ai/src/tools/schemas.ts`)
+- Server-side Planner-Drift-Check läuft automatisch grün
+- `` in ListView eingebaut
+- Tool-Implementierungen in `data/ai/tools/` analog zu existierenden Modulen
+
+**Done-Definition**: AI-Mission "Schau mal, ob es Lasts gibt" generiert Proposals, User kann approven, Eintrag landet als `suspected` mit `inferredFrom`.
+
+### M5 — Push-Notifications + Settings (~ 0.5 Tage, abhängig vom Notification-System)
+- `/lasts/settings/+page.svelte` mit drei opt-in-Toggles
+- Anniversary-Scan (Cron + match auf `date` mit Jahres-Differenz)
+- Recognition-Scan (Cron + match auf `recognisedAt`)
+- Inbox-Notify-Hook nach `suggest_lasts`
+- Hard-Cap-Logik
+
+**Done-Definition**: Toggles persistent, eine Push-Test-Funktion sendet Beispiel.
+
+### M6 — Visibility + Unlisted-Sharing (~ 0.5 Tage)
+- Default-Visibility auf `private`
+- VisibilityPicker in DetailView
+- Embed-Resolver für `lasts` registrieren (analog `events`/`library`)
+- `/share/[token]`-Route lädt einzelnes Last in lesbarem Format
+
+**Done-Definition**: Last kann auf `unlisted` gesetzt werden, Share-Link funktioniert öffentlich ohne Login.
+
+### M7 — Optional: Timeline-Aggregator + Year-Recap
+- `data/milestones/timeline-query.ts`: Union-Query firsts ∪ lasts
+- `routes/(app)/milestones/+page.svelte`: chronologische Timeline beider, Filter nach Direction
+- Year-Recap-Page mit Top-N Lasts + Firsts des Jahres (analog Augur-Year-Recap aus Memory)
+
+**Done-Definition**: Timeline-View zeigt firsts und lasts interleaved, sortierbar, filterbar.
+
+## Per-Space-Seeds
+
+`lasts` ist per-space (analog allen post-spaces-foundation Modulen). Ein Per-Space-Seeder registriert sich in `apps/mana/apps/web/src/lib/data/seeds/lasts.ts`:
+
+```typescript
+registerSpaceSeed('lasts-welcome', async (spaceId) => {
+ const id = `seed-welcome-${spaceId}`;
+ if (await db.table('lasts').get(id)) return;
+ await db.table('lasts').add({
+ id, spaceId,
+ title: 'Willkommen bei Lasts',
+ status: 'confirmed',
+ category: 'other',
+ confidence: 'certain',
+ inferredFrom: null,
+ date: new Date().toISOString().slice(0, 10),
+ meaning: 'Hier fängst du an, deine "letzten Male" festzuhalten.',
+ note: null,
+ whatIKnewThen: null,
+ whatIKnowNow: null,
+ tenderness: 3,
+ wouldReclaim: null,
+ reclaimedAt: null,
+ reclaimedNote: null,
+ personIds: [], sharedWith: null,
+ mediaIds: [], audioNoteId: null, placeId: null,
+ recognisedAt: new Date().toISOString(),
+ isPinned: false, isArchived: false,
+ });
+});
+```
+
+Side-effect-Import in `data/seeds/index.ts`.
+
+## App-Registry
+
+In `packages/shared-branding/src/mana-apps.ts`:
+
+```typescript
+{
+ id: 'lasts',
+ name: 'Lasts',
+ description: 'Letzte Male — bewusst markiert oder rückwirkend erkannt',
+ category: 'reflection', // gleiche Kategorie wie firsts
+ requiredTier: 'guest', // LOCAL TIER PATCH bis Release, dann auf prod-tier setzen
+ icon: '…',
+ // …
+}
+```
+
+## Tier-Strategie
+
+Während Entwicklung: `'guest'` mit `// LOCAL TIER PATCH` Marker (Memory `project_tier_patch_resolved.md`). Vor Release auf prod-tier hochziehen — Vorschlag: `beta` analog zu firsts.
+
+## Offene Fragen
+
+1. **`losses` Cross-Link**: Soll ein Last → Loss eskaliert werden können (Button "Das war ein echter Verlust → in losses übernehmen")? Ja, aber `losses` existiert noch nicht als Modul. Hook-Point in DetailView vorbereiten, no-op bis losses gebaut ist.
+2. **Audio-First-Eingabe**: Mic → STT → AI-strukturiert → Last-Draft? Würde gut zu Rubberduck/Scribe-Pattern passen. Erstmal nicht in M1-M6, aber DetailView so designen, dass `audioNoteId`-Eingabe leicht später ergänzbar ist (Feld existiert ja schon im Schema).
+3. **Persona-Begleitung**: Soll ein dedizierter "Gefährte"-Agent (sanft, kontemplativ) für die Lasts-Begleitung gespawnt werden, oder reicht der Default-Mana-Agent? Vorschlag: in M5 prüfen, fürs Erste Default-Agent.
+4. **`recognisedAt` vs. `createdAt`**: In 99% der Fälle gleich. Brauchen wir beide? Ja, weil bei AI-inferred Records der `createdAt` der Scan-Zeitpunkt ist und `recognisedAt` der "User-akzeptiert"-Zeitpunkt — relevant für Recognition-Reminder.
+5. **i18n**: Direkt mit echten Keys bauen (nach Memory `project_i18n_hardening.md` ist hardcoded German verboten; validator wird sonst rot). Namespace: `lasts.*`.
+
+## Kosten-Schätzung
+
+| Milestone | Aufwand |
+|---|---|
+| M1 Refactor + Skelett | 1 Tag |
+| M2 CRUD + Views | 1 Tag |
+| M3 Inbox + Inference | 1 Tag |
+| M4 AI-Tools | 0.5 Tage |
+| M5 Push + Settings | 0.5 Tage |
+| M6 Visibility + Sharing | 0.5 Tage |
+| M7 Timeline (optional) | 0.5 Tage |
+| **Total M1-M6** | **4 Tage** |
+| **Total inkl. M7** | **4.5 Tage** |
+
+Die ursprüngliche Schätzung "1-2 Tage für M1, +1 Tag Inferenz, +1 Tag Push" war zu knapp — vor allem M3 (Inferenz mit konservativen Schwellen + Cooldown-Mechanik) und der Refactor-Vorlauf in M1 brauchen jeweils ihren vollen Tag.
diff --git a/packages/shared-ai/src/tools/schemas.ts b/packages/shared-ai/src/tools/schemas.ts
index 2d02a999e..f1dfa5734 100644
--- a/packages/shared-ai/src/tools/schemas.ts
+++ b/packages/shared-ai/src/tools/schemas.ts
@@ -2299,6 +2299,186 @@ export const AI_TOOL_CATALOG: readonly ToolSchema[] = [
},
],
},
+
+ // ── Lasts (mirror sibling to firsts) ────────────────────────
+ {
+ name: 'create_last',
+ module: 'lasts',
+ description:
+ 'Erstellt einen neuen "Last" — ein letztes Mal, das markiert oder vermutet werden soll. Status standardmaessig "suspected"; "confirmed" nur setzen wenn der User sicher ist.',
+ defaultPolicy: 'propose',
+ parameters: [
+ {
+ name: 'title',
+ type: 'string',
+ description: 'Was zum letzten Mal passiert ist (z.B. "Letzter Tag im alten Job")',
+ required: true,
+ },
+ {
+ name: 'category',
+ type: 'string',
+ description: 'Kategorie',
+ required: false,
+ enum: [
+ 'culinary',
+ 'adventure',
+ 'travel',
+ 'people',
+ 'career',
+ 'creative',
+ 'nature',
+ 'culture',
+ 'health',
+ 'tech',
+ 'other',
+ ],
+ },
+ {
+ name: 'status',
+ type: 'string',
+ description: 'Lifecycle-Status',
+ required: false,
+ enum: ['suspected', 'confirmed'],
+ },
+ {
+ name: 'date',
+ type: 'string',
+ description: 'Datum des letzten Mals (YYYY-MM-DD), falls bekannt',
+ required: false,
+ },
+ {
+ name: 'confidence',
+ type: 'string',
+ description: 'Sicherheit, dass es das letzte Mal war',
+ required: false,
+ enum: ['probably', 'likely', 'certain'],
+ },
+ {
+ name: 'meaning',
+ type: 'string',
+ description: 'Was es bedeutet hat (optional)',
+ required: false,
+ },
+ {
+ name: 'note',
+ type: 'string',
+ description: 'Freie Notiz (optional)',
+ required: false,
+ },
+ ],
+ },
+ {
+ name: 'confirm_last',
+ module: 'lasts',
+ description:
+ 'Bewegt einen Last von "suspected" auf "confirmed" und ergaenzt Reflexionsfelder. Setzt Datum auf heute, falls keines uebergeben wird.',
+ defaultPolicy: 'propose',
+ parameters: [
+ { name: 'lastId', type: 'string', description: 'ID des Lasts', required: true },
+ {
+ name: 'date',
+ type: 'string',
+ description: 'Datum des letzten Mals (YYYY-MM-DD)',
+ required: false,
+ },
+ {
+ name: 'meaning',
+ type: 'string',
+ description: 'Was es bedeutet hat',
+ required: false,
+ },
+ {
+ name: 'whatIKnewThen',
+ type: 'string',
+ description: 'Was du damals nicht wusstest',
+ required: false,
+ },
+ {
+ name: 'whatIKnowNow',
+ type: 'string',
+ description: 'Was du heute siehst',
+ required: false,
+ },
+ {
+ name: 'tenderness',
+ type: 'number',
+ description: 'Wie sehr es dich heute beruehrt (1-5)',
+ required: false,
+ },
+ {
+ name: 'wouldReclaim',
+ type: 'string',
+ description: 'Wuerdest du es zurueckholen?',
+ required: false,
+ enum: ['no', 'maybe', 'yes'],
+ },
+ ],
+ },
+ {
+ name: 'reclaim_last',
+ module: 'lasts',
+ description:
+ 'Markiert einen Last als "aufgehoben" — es ist wieder passiert. Optionaler Notiz-Text beschreibt, was zurueckgekommen ist.',
+ defaultPolicy: 'propose',
+ parameters: [
+ { name: 'lastId', type: 'string', description: 'ID des Lasts', required: true },
+ {
+ name: 'reclaimedNote',
+ type: 'string',
+ description: 'Was ist wieder passiert (optional)',
+ required: false,
+ },
+ ],
+ },
+ {
+ name: 'list_lasts',
+ module: 'lasts',
+ description:
+ 'Listet Lasts (id, title, status, category, date). Optional nach Status oder Kategorie filterbar.',
+ defaultPolicy: 'auto',
+ parameters: [
+ {
+ name: 'status',
+ type: 'string',
+ description: 'Nur einen Status zeigen',
+ required: false,
+ enum: ['suspected', 'confirmed', 'reclaimed'],
+ },
+ {
+ name: 'category',
+ type: 'string',
+ description: 'Nur eine Kategorie zeigen',
+ required: false,
+ enum: [
+ 'culinary',
+ 'adventure',
+ 'travel',
+ 'people',
+ 'career',
+ 'creative',
+ 'nature',
+ 'culture',
+ 'health',
+ 'tech',
+ 'other',
+ ],
+ },
+ {
+ name: 'limit',
+ type: 'number',
+ description: 'Maximale Anzahl (Standard 30)',
+ required: false,
+ },
+ ],
+ },
+ {
+ name: 'suggest_lasts',
+ module: 'lasts',
+ description:
+ 'Laesst die Inferenz-Engine ueber places/habits/contacts scannen und generiert "suspected"-Lasts mit inferredFrom-Provenance fuer Eintraege, die Frequenz-Drops zeigen. Dedupliziert gegen existierende Lasts und die Cooldown-Liste. Schreibt direkt in die Inbox — kein Proposal-Workflow noetig, weil die Eintraege als suspected landen und der User sie dort akzeptieren oder verwerfen kann.',
+ defaultPolicy: 'auto',
+ parameters: [],
+ },
];
// ═══════════════════════════════════════════════════════════════
diff --git a/packages/shared-branding/src/app-icons.ts b/packages/shared-branding/src/app-icons.ts
index 002bc62c8..0d34460d8 100644
--- a/packages/shared-branding/src/app-icons.ts
+++ b/packages/shared-branding/src/app-icons.ts
@@ -193,6 +193,12 @@ export const APP_ICONS = {
// Warm amber→rose gradient to evoke excitement and novelty.
``
),
+ lasts: svgToDataUrl(
+ // Hourglass with a single falling grain — the moment something
+ // passes for the last time. Indigo→slate gradient for the
+ // contemplative, retrospective tone (mirror to firsts' warm amber).
+ ``
+ ),
drink: svgToDataUrl(
// Water drop + glass — represents beverage tracking.
// Blue→cyan gradient for the hydration theme.
diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts
index f0bf532f0..5388b1938 100644
--- a/packages/shared-branding/src/mana-apps.ts
+++ b/packages/shared-branding/src/mana-apps.ts
@@ -751,6 +751,23 @@ export const MANA_APPS: ManaApp[] = [
status: 'development',
requiredTier: 'guest',
},
+ {
+ id: 'lasts',
+ name: 'Lasts',
+ description: {
+ de: 'Letzte Male',
+ en: 'Last Times',
+ },
+ longDescription: {
+ de: 'Halte fest, was zum letzten Mal passiert ist — bewusst markiert oder rückwirkend erkannt. Spiegelbild zu Firsts: leise Reflexion statt Vorfreude.',
+ en: 'Capture what happened for the last time — marked deliberately or recognised in hindsight. Mirror sibling to Firsts: quiet reflection instead of anticipation.',
+ },
+ icon: APP_ICONS.lasts,
+ color: '#6366f1',
+ comingSoon: false,
+ status: 'development',
+ requiredTier: 'guest', // LOCAL TIER PATCH — revert to 'beta' before release (see project_tier_patch_resolved memory)
+ },
{
id: 'period',
name: 'Periode',