"Lasts" auf Deutsch ist ein Homophon zu "die Last" (Bürde/Belastung).
Ein deutscher Muttersprachler las "Last nicht gefunden" als "Bürde
nicht gefunden". Falsches Gefühl für ein kontemplatives Modul.
Renames:
- mana-apps.ts: name "Lasts" → "Letzte Male", "Firsts" → "Erste Male"
- lasts/de.json: app.title + Singular-Bezüge weg von "Last" auf
"Letztes Mal" (detail.routeTitle, banner.recognition) bzw.
"Eintrag" (detail.notFound, settings.testSampleTitle, …)
- milestones/de.json: tabs.first/last + recap.topFirstsLabel/topLastsLabel
switchen auf "Erste Male" / "Letzte Male"
- store error: "Aufgehobene Lasts ..." → "Aufgehobene Einträge ..."
Andere Locales (en/es/fr/it) bleiben unangetastet — dort ist "Lasts"
und "Firsts" linguistisch unproblematisch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drizzle's pgEnum() ohne pgSchema-Wrap landet immer in public — der
schemaFilter versteckt das nur im Diff (siehe Repo-Memory:
"Drizzle enums with schemaFilter must use pgSchema().enum()"). Die
Tabelle feedback.user_feedback referenziert die Enums quer aus public,
das funktioniert; aber die ALTER-TYPE-Statements in der ursprünglichen
Migration zielten auf feedback.feedback_status / feedback.feedback_category
und hätten damit nichts gefunden.
Lokal verifiziert (mana_platform.public.feedback_status,
mana_platform.public.feedback_category):
- 6 Status-Werte umbenannt → submitted/under_review/planned/in_progress/completed/declined
- Default-Status auf 'submitted'
- Category 'onboarding-wish' hinzugefügt
- Re-Run idempotent (DO-Blöcke + ADD VALUE IF NOT EXISTS)
Mittelfristig sollte feedbackSchema.enum(...) verwendet werden, damit
Enums tatsächlich im feedback-Namespace landen — eigener Refactor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Onboarding wird zur 4-Step-Card im Workbench-Look und schließt mit einer
Freitext-Frage, die als @mana/feedback-Record landet.
UI-Redesign:
- Wraps die Screens in einer zentrierten Card mit ModuleShell-Chrome
(paper texture, soft border, 1.25rem radius, dual shadow). Liest sich
wie eine Workbench-Page statt eines flat Takeover-Screens.
- Header weg. Globaler Skip-Button sitzt unten links, Step-Dots zentriert
unten — drei-Spalten-Grid hält Dots perfekt zentriert egal wie breit
der Skip-Button ist.
- Per-Screen-Skip-Buttons aus name/ und templates/ entfernt — eine
einzige Skip-Affordance reicht.
Wish-Step (neu, Step 4):
- /onboarding/wish: Freitext-Textarea (max 2000), Aktivierungstext
("Eine letzte Sache — was wünschst du dir von Mana?"). Submit postet
fail-soft an feedbackService.createFeedback({ category:
'onboarding-wish', isPublic: false }) — Server-Down blockiert das
Onboarding nicht.
- onboarding-flow Store um pendingWish erweitert (Back-Nav-Preserve).
- Layout: 3 → 4 Step-Dots, Path-Mapping erweitert.
- markComplete + reset wandert von templates' Fertig-Handler in den
wish-Screen; templates' Button heißt jetzt "Weiter" und routet zu
/onboarding/wish.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Macht @mana/feedback zur SSOT für alle Nutzer-Feedback-Categories und
-Status — Voraussetzung dafür, dass Onboarding-Wishes, NPS, Churn-Feedback
etc. künftig dort landen.
- Status-Enum: DB-Werte umbenannt new/reviewed/done/rejected →
submitted/under_review/completed/declined (Package gewinnt). PG≥10
ALTER TYPE … RENAME VALUE ist non-destructive.
- Category 'praise' ins Package aufgenommen (war nur in DB).
- Category 'onboarding-wish' neu in Package + DB für den Wish-Step.
- Default status in DB: 'new' → 'submitted'.
- CreateFeedbackInput.isPublic optional → Service reicht durch, default
bleibt true; private Categories wie onboarding-wish setzen false.
- Schema-Datei mit SSOT-Kommentar versehen, der Drift in Zukunft verhindert.
Hand-authored Migration unter services/mana-analytics/drizzle/0001_*.sql
weil drizzle-kit push Enum-Werte nicht zuverlässig umbenennt. Manuell
einspielen vor nächstem db:push:
psql "\$DATABASE_URL" -f services/mana-analytics/drizzle/0001_align-feedback-enums.sql
Plan in docs/plans/feedback-hub.md (Phase 0–4); Phase 0 + 1 jetzt, 2-4
deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the false-positive conflict-toast loop on history-replay. Conflict
notifications now fire only when the local field meta records origin='user'
AND the pull is not an initial hydration round.
Origin source-of-truth:
- shared-ai/field-meta.ts → originFromActor(actor) maps actor.kind onto
the FieldOrigin enum: user→'user', ai→'agent', system+SYSTEM_MIGRATION
→'migration', any other system source→'system'.
- Dexie creating/updating hooks call it once per write so every persisted
field carries the right pipeline tag.
- repair-silent-twin + legacy-avatar wrap their writes in
runAsAsync(makeSystemActor(SYSTEM_MIGRATION, ...)) so the hook stamps
origin='migration'. Future replays of those rows from another device
will not surface as conflicts.
applyServerChanges options:
- New ApplyServerChangesOptions { isInitialHydration?: boolean }.
- Push-response and pull-paged-loop callers compute it from the cursor
state (`!oldestCursor` / `!cursor`). Pagination resets the flag after
the first page.
- Conflict-trigger gates on `!options.isInitialHydration && localMeta[k]
?.origin === 'user'` in addition to the prior tests.
Tests (sync.test.ts):
- New: replay-burst (10 sequential server updates → 0 conflicts)
- New: agent-origin local write + server overwrite → 0 conflicts
- New: isInitialHydration suppresses everything → 0 conflicts
- New: real user edit + server overwrite → 1 conflict
- All 25 prior tests still pass.
29/29 vitest sync.test.ts cases green; svelte-check 0 errors over 7647
files.
Plan: docs/plans/sync-field-meta-overhaul.md F2 done-criteria met.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ContextOverview ("Freundebuch" profile cards) was the single biggest
hardcoded-string hot-spot at 35 strings — every user sees this on their
profile. Extended `profile.context.*` namespace with section titles,
field labels (routine/social/leisure), placeholders, weekday short
names, and empty-state hints across DE/EN/ES/FR/IT.
Bonus: ratchet i18n-hardcoded baseline from 1879 → 1817 (settings
namespace + ContextOverview together cleared 62 violations).
- validate:i18n-parity: 39 namespaces × 5 locales — 3381 keys aligned
- svelte-check: 7647 files, 0 errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 5 milestones landed today in one continuous session: registry,
health cache, fallback router, observability, and consumer migration.
115 service-side tests, validator covers 2538 files.
Final milestone of docs/plans/llm-fallback-aliases.md. Every backend
caller now requests models via the `mana/<class>` alias system instead
of hardcoded `ollama/...` strings. mana-llm resolves aliases through
`services/mana-llm/aliases.yaml` with health-aware fallback (M3) and
emits resolved-model + fallback metrics (M4).
SSOT moved to `packages/shared-ai/src/llm-aliases.ts` so apps/api,
apps/mana/apps/web, and services/mana-ai all import the same
`MANA_LLM` constant via the existing `@mana/shared-ai` workspace
dependency. Three additional sites (memoro-server, mana-events,
mana-research) inline the alias string with a SSOT comment because
they don't pull @mana/shared-ai today.
Migrated 14 sites across 10 files:
- apps/api: writing(LONG_FORM), comic(STRUCTURED), context(FAST_TEXT),
food(VISION), plants(VISION), research orchestrator (3 tiers
collapsed to STRUCTURED+FAST_TEXT/LONG_FORM)
- apps/mana/apps/web: voice/parse-task + parse-habit (STRUCTURED)
- services/mana-ai: planner llm-client + tick.ts (REASONING)
- services/mana-events: website-extractor (STRUCTURED, inlined)
- services/mana-research: mana-llm client (FAST_TEXT, inlined)
- apps/memoro/apps/server: ai.ts (FAST_TEXT, inlined)
Legacy env-vars removed: WRITING_MODEL, COMIC_STORYBOARD_MODEL,
VISION_MODEL, MANA_LLM_DEFAULT_MODEL. The chain in aliases.yaml is
now the single tuning surface; SIGHUP reloads it without redeploys.
New `scripts/validate-llm-strings.mjs` regex-scans 2538 files for
hardcoded `<provider>/<model>` strings and fails the build if any
land outside the SSOT or the explicitly-allowed paths (image-gen
modules, model-inspector code, this validator itself, the registry).
Wired into `validate:all` next to the i18n + theme validators.
Verified: `pnpm validate:llm-strings` clean, `pnpm --filter @mana/api
type-check` clean, `pnpm --filter @mana/ai-service type-check`
clean. Web type-check has 2 pre-existing errors in
SettingsSidebar.svelte (i18n MessageFormatter type drift, last
touched in 988c17a67 — unrelated to this work).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `X-Mana-LLM-Resolved: <provider>/<model>` header on non-streaming
responses. Streaming clients read the same info from each chunk's
`model` field (SSE headers go out before the chain is walked).
- Three new Prometheus metrics: `mana_llm_alias_resolved_total{alias,
target}` (which concrete model an alias resolved to per request),
`mana_llm_fallback_total{from_model, to_model, reason}` (each
fallback transition), `mana_llm_provider_healthy{provider}` (gauge,
mirrors the circuit-breaker).
- New debug endpoints: `GET /v1/aliases` (registry inspection — chain
+ description per alias, useful for confirming SIGHUP reloads),
`GET /v1/health` (full per-provider liveness snapshot — failure
counter, last error, unhealthy-until backoff).
- `kill -HUP <pid>` reloads `aliases.yaml`. Parse errors leave the
previous good state in memory and log the rejection.
- `ProviderHealthCache.add_listener()` for cache→metrics decoupling:
the gauge is updated via a transition-only listener wired in main.py
rather than the cache importing prometheus_client itself.
- Request-side metrics now use the requested model string, success-side
uses the resolved one. So `mana_llm_llm_requests_total{provider="ollama",
model="gemma3:12b"}` reflects actual upstream load even when callers
used `mana/long-form` aliases.
16 new observability tests (test_m4_observability.py): listener
fire-on-transition semantics, exception-isolation, multi-listener,
counter increments, gauge writes, end-to-end alias→metric flow,
v1/aliases + v1/health endpoint shape, response.model carries the
resolved target after fallback. Total suite: 115/115 in 1.6s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the old Ollama→Google special-case auto-fallback with the
unified pipeline: caller passes either a direct provider/model or an
alias from the `mana/` namespace; the router resolves to a chain and
walks it skipping unhealthy providers (per ProviderHealthCache from M2),
trying each entry, marking provider unhealthy on retryable errors and
falling through to the next.
Retryable: ConnectError, ReadTimeout, RemoteProtocolError, 5xx,
ProviderRateLimitError. Propagated (don't fall back, don't poison the
cache): ProviderCapabilityError, ProviderAuthError, ProviderBlockedError,
4xx, unknown exception types. The cache stays "what the network told us
about this provider's liveness" — caller errors don't muddy that signal.
Streaming: pre-first-byte fallback only. Once a chunk has been yielded
the provider is committed; mid-stream errors propagate as-is so we
don't splice two voices into one output.
`NoHealthyProviderError` (HTTP 503) carries a structured attempt log —
each chain entry shows up as `(model, reason)` so the cause of a 503
is visible in the response and metrics, not only in service logs.
main.py wires the lifespan: aliases.yaml is loaded, ProviderHealthCache
created, ProviderRouter takes both as constructor deps, HealthProbe
spawned with cheap HTTP probes per configured provider (Ollama
/api/tags, OpenAI-compat /v1/models with Bearer header). Google is
skipped — google-genai SDK has no obvious cheap probe; the call-site
fallback handles real errors.
22 new router tests (test_router_fallback.py): chain walking, capability
& auth propagation, 5xx vs 4xx differentiation, rate-limit retry,
all-fail → NoHealthyProviderError, direct provider strings bypass
aliases, streaming pre-first-byte fallback, mid-stream-failure does
NOT fall back, empty stream commits without retry, cache feedback on
success/failure/non-retryable. Existing test_providers.py updated for
the new constructor signature; all 99 service tests green via the dev
container (Python 3.12).
Legacy purged: `_ollama_concurrent`, `_ollama_health_cache`,
`_can_fallback_to_google`, `_should_use_ollama`, `_fallback_to_google`,
`_get_ollama_health_cached` all gone. The `auto_fallback_enabled` /
`ollama_max_concurrent` settings remain in config.py for now (M5 will
remove them along with the per-feature env-var overrides).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-provider liveness with circuit-breaker semantics. The router (M3)
will read `is_healthy()` to skip dead providers in a chain; the probe
loop and the call-site fallback handler write state via
`mark_healthy` / `mark_unhealthy`.
State machine: 1st failure stays healthy (transient blips happen);
2nd consecutive failure trips the breaker and sets a 60s backoff
window during which `is_healthy → False`. After the window the
provider is half-open again — next call exercises it, success
resets, failure re-arms.
HealthProbe is the background asyncio.Task that pings every
registered provider every 30s with a 3s timeout. Probes run
concurrently per tick and one bad probe can't sink the loop. Probe
functions are injected (`{name: async-fn}`) so this module stays
decoupled from the provider classes — the wiring lives in main.py
where we already know which providers are configured.
32 new tests (FakeClock for deterministic backoff timing, slow-probe
helpers for parallelism + timeout, lifecycle tests for start/stop
idempotency and tick-after-error survival). 64/64 alias+health tests
green.
Not yet wired into the request path — that's M3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First milestone of the LLM-fallback plan (docs/plans/llm-fallback-aliases.md).
Introduces the `mana/<class>` namespace; the registry parses + validates
aliases.yaml at startup and reloads on demand. Schema-rejects empty
chains, missing provider prefixes, alias names outside the reserved
namespace, default→unknown references, etc.
Reload semantics: parse error keeps the previous good state in memory
so a typo + SIGHUP doesn't take the service down.
5 aliases ship with the initial config: fast-text, long-form, structured,
reasoning, vision. Each chain ends with a cloud provider so the system
keeps working when the GPU server is offline.
32 unit tests covering happy path, schema validation, namespace check,
reload safety, and a guard that the shipped aliases.yaml itself parses.
M2 (health-cache + probe-loop) and M3 (router fallback execution) build
on this; aliases are not yet wired into the request path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Centralized resilience-layer in mana-llm: callers send semantic aliases
(`mana/long-form`, `mana/structured`, …), the router resolves to a
provider chain and falls back through unhealthy providers via a 30s
health-probe loop. Triggered by today's GPU-server outage that hung
the writing-generation endpoint for 75s before 500.
5 milestones, ~3 dev-days, big-bang migration (no live yet → no legacy).
All hardcoded `ollama/...` strings move into a single aliases.yaml SSOT,
new validate-llm-strings.mjs gate prevents regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der me-image Upload-Endpunkt wird von Wardrobe (face-banner),
Picture (reference-picker), Comic (face-banner) und der profile-
Detail-View geteilt. Bisher: wenn `authStore.getValidToken()` null
zurückgab, ging die Anfrage trotzdem ohne `Authorization`-Header
raus und der Server antwortete mit dem rohen Auth-Middleware-String
"Missing authorization header" — keine Hinweis darauf was der
Nutzer tun soll. Symptom war auch über Module hinweg verschieden:
Wardrobe-Nutzer sah's nie weil sein Token frisch war, Comic-Nutzer
mit ablaufendem Token sah's beim ersten Upload.
Zwei Härtungen in `uploadMeImageFile`:
1. Pre-flight Check — wenn `getValidToken()` null liefert, throw
sofort mit Klartext-Anweisung "Du bist nicht eingeloggt — bitte
aktualisiere die Seite und logge dich neu ein". Spart einen
Server-Roundtrip und gibt actionable feedback.
2. 401 nach getToken-Erfolg — Token war zwar lokal "valid" aber
serverseitig abgelaufen/invalidiert. Statt den Server-String
durchzureichen, eigene "Session abgelaufen — bitte
aktualisieren"-Meldung.
Alle Banner-UIs (Wardrobe + Comic) catchen den Fehler bereits in
`handleFaceUpload` und zeigen ihn im Banner-Error-Bereich, also
fließt die neue Meldung 1:1 durch ohne UI-Änderung.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-Feedback: in Wardrobe konnte man das Gesichtsbild direkt aus
der Workbench-Card hochladen, in Comic verwies der Hint nur auf
/profile/me-images. Asymmetrie geheilt — beide Module nutzen jetzt
das gleiche Banner-Pattern.
Comic-ListView (Modul-Root, oberhalb der Tabs):
- Wardrobe-Banner verbatim übernommen (MeImageUploadZone +
3-Phasen-State-Machine idle/uploading/success + 2.5s
success-card mit fade-out + dismissable + spinner-overlay
während upload + error-card auf Fehler).
- Sitzt oberhalb der Tabs, damit es für BEIDE Sub-Views
(Stories | Characters) sichtbar ist — Comic-Panel UND
Charakter-Generierung brauchen das Face-Ref. Banner blendet
sich automatisch aus sobald face$ via liveQuery flippt + die
2.5s success-Window vorbei ist.
- Copy angepasst: "Wir brauchen dich auf Bild, damit Comic-Panels
und Charakter-Varianten von dir gerendert werden können"
statt Wardrobe's "Try-On Kleidung an dir visualisieren".
Success-CTA: "als nächstes baust du deinen ersten Comic-
Character oder legst direkt eine Story an".
Sub-Views aufgeräumt:
- views/ListView.svelte (StoriesView): hat den redundanten
"Lade erst dein Gesichtsbild"-Hint inkl. UserCircle-Import +
useImageByPrimary-Hook gehabt → entfernt. Modul-Root liefert
das jetzt.
- views/CharactersView.svelte: gleicher Cleanup. Imports von
UserCircle und useImageByPrimary raus.
Repair-Hook (`repairSilentTwinAvatarRows`) bewusst NICHT
kopiert — das war eine Wardrobe-spezifische Migration für die
M2.5-silent-twin-Bug; Comic ist nach v40 entstanden, hat das
Problem nie gehabt.
Comic-Files type-checken sauber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mehrere Store-Methoden lasen die verschlüsselten Felder eines frisch
aus Dexie geholten Records direkt — references/title/briefing/content
landen aber als Ciphertext-String und nicht als Array/Objekt im Speicher.
Auswirkungen die jetzt behoben sind:
- startDraftGeneration: 'refs.map is not a function' beim ersten Klick
auf "Generieren" (draft.references war Ciphertext)
- refineSelection: Crash beim Lesen von draft.briefing.language
- applyRefinement: Slice-Konkatenation auf Ciphertext (korrumpiert die
Version still beim ersten Selection-Refinement)
- updateBriefing: Spread-merge eines Ciphertext-Strings in den Patch
- createCheckpointVersion: kopiert die Ciphertext-Bytes als neue
Version-Content statt des Plaintexts
Fix: decryptRecord() direkt nach jedem .get() der relevante encrypted
Felder liest. queries.ts war schon korrekt (decryptRecords im liveQuery-
Pfad), aber die Mutation-Pfade haben das übersprungen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schreiben + research-lab + broadcast + invoices + agents + timeline +
website + spaces stehen jetzt auf 'guest' damit alle Beta-Tester ohne
Tier-Upgrade reinkönnen. LOCAL-TIER-PATCH-Marker dokumentieren den
Original-Tier für den Release-Revert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the two remaining gaps after the seeding-cleanup landed:
- `data/space-stamping.test.ts` exercises the smart-hook contract
end-to-end against fake-indexeddb. Four scenarios: active Brand
Space → row carries Brand UUID; no active Space → personal sentinel;
explicit spaceId on the record is preserved verbatim; flipping the
active Space between writes flips the stamp. The Brand-Space case
is the regression guard for the original bug (writes silently
routing to Personal after `reconcileSentinels`).
- `apps/mana/CLAUDE.md` gets a Per-Space Seeds section so the next
module dev who needs to pre-populate something on Space activation
finds the `registerSpaceSeed` pattern + `data/seeds/index.ts` barrel
+ the deterministic-id discipline without grepping the codebase.
Reference impl link points at workbench-home.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `dedupHomeScenesOn` helper in `data/scope/dedup-workbench-scenes.ts`
existed only to be called once from the v48 Dexie upgrade — outside of
that single usage it was dead code. Inlining the logic directly into
the upgrade callback eliminates a 120-line module + a 220-line test
file (343 lines net) without changing behaviour: the v48 upgrade still
collapses uncustomised "Home" duplicates per (spaceId, name='Home'),
merges openApps, and soft-deletes losers.
Drive-by tightening:
- `seedWorkbenchHomeOn` returns `Promise<void>` instead of
`Promise<boolean>`. The boolean was only consumed by the
post-`reconcileSentinels` dedup pass that already got removed; the
current callers (registry seeder + tests) don't read it. Less
signature surface, fewer assertions in tests.
- `data/scope/per-space-seeds.ts` comment header drops the
plan-internal "Schicht B + C" reference for a plain link to the
cleanup plan. Code-level vocabulary now reads cleanly without the
rollout-sequencing context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brücke von Wardrobe nach Comic: User klickt auf einem Outfit oder
einem einzelnen Kleidungsstück „Als Comic-Character", landet im
Character-Builder mit pre-filltem Add-Prompt ("wearing the
Bühnenoutfit"), picked Stil und rendert die ersten 4 Varianten.
Wardrobe-Buttons:
- DetailOutfitView: unterhalb des TryOnButton ein outline-Link
navigiert zu `/comic/character/new?title=…&prompt=wearing+the+
OUTFITNAME+outfit`.
- DetailGarmentView: analog mit `prompt=wearing+GARMENTNAME` für
ein einzelnes Kleidungsstück. Beide nur sichtbar wenn das
Outfit/Garment nicht archiviert ist.
- Sparkle-Icon + dezent neutraler Border-Style (nicht primary —
das ist die TryOn-CTA), hover schaltet auf primary/40.
Comic CharacterBuilder bekommt drei optionale Props:
`initialName?`, `initialAddPrompt?`, `initialStyle?`. Im
extend-Modus ignoriert (Source ist dann der existing-Character),
im create-Modus dienen sie als $state-Initialwerte. Routine read
ist intentional — Mounting passiert frisch pro Route-Visit, also
einmaliges Capture passt.
`/comic/character/new/+page.svelte` parsed jetzt
`page.url.searchParams` für `title`, `prompt`, `style` und reicht
sie als Props durch. style wird gegen die VALID_STYLES-Liste
validiert — defekte URL-Params fallen ohne Crash auf
"unset/default" zurück.
Bewusst NICHT gemacht: Try-On-Output direkt als sourceBodyMediaId
verwenden. Das Try-On-Bild ist im mana-media mit `app='picture'`
getaggt; `verifyMediaOwnership` auf
`/picture/generate-with-reference` akzeptiert nur
`['me','wardrobe','comic']` — der Comic-Generate würde mit
HTTP 404 abbrechen. Lösung wäre eine Server-Route die Picture-
Output als Comic-Asset re-tagged, das ist aber eigene Spec.
Aktueller Pfad ist sauberer: rohe meImages-Refs bleiben Source,
der Add-Prompt steuert den Outfit-Look.
Plan-Doc §11 Mc5 dokumentiert den Pfad + warum kein
Try-On-Reuse.
Comic-Files type-checken sauber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persona-Runner / Claude Desktop / Web-App-Mission-Runner können jetzt
Comic-Characters bauen, iterieren und pinnen — same Auto/Propose-
Pattern wie die Story-Tools.
MCP (packages/mana-tool-registry/src/modules/comic.ts):
- comic.listCharacters (read/auto): Pull, decrypt, filter (style?,
favoriteOnly?), liefert {id, name, style, addPrompt, source-Refs,
variantMediaIds, pinnedVariantId, variantCount, tags, isFavorite}.
- comic.createCharacter (write/propose): legt nur die Row an —
trennt Anlegen von Generierung damit der Agent reviewen kann
bevor Credits fließen. Liefert characterId zurück.
- comic.generateVariant (write/propose, kostet Credits): pullt
Character-Row, dekodiert, ruft /picture/generate-with-reference
mit n=count (default 4) + Stil-Prefix + Identity-Anchor-Prompt,
schreibt N picture.images mit comicCharacterId-Back-Ref, pusht
field-level Update auf variantMediaIds + pinnedVariantId
(auto-pin auf erste neue Variant wenn vorher null).
- comic.pinVariant (write/propose): Set-Equality-Check (variantMediaId
muss in variantMediaIds sein), field-level Update auf
pinnedVariantId. Snapshot-Pattern: bestehende Stories bleiben
unverändert, nur neue Stories nutzen den neuen Pin.
AI_TOOL_CATALOG (packages/shared-ai/src/tools/schemas.ts):
- list_comic_characters (auto)
- create_comic_character (propose) — auto-resolvt face/body-refs aus
meImages-primaries, Agent muss keine mediaIds kennen
- generate_character_variant (propose, count 1-4)
- pin_character_variant (propose)
Web-App-Executors (apps/mana/apps/web/src/lib/modules/comic/tools.ts):
- 4 ModuleTool-Einträge, die an comicCharactersStore +
runCharacterGenerate delegieren — gleicher Code-Pfad wie die UI,
also keine Divergenz zwischen Klick und Agent-Call.
Comic-Autor-Template (packages/shared-ai/src/agents/templates/
comic-author.ts):
- Policy bi-lingual erweitert: snake_case + dot-case Namen für
alle 4 neuen Character-Tools.
- System-Prompt Schritt 3 ergänzt: "Wenn der User noch keinen
passenden Comic-Character hat → list_comic_characters →
create_comic_character → generate_character_variant → pin.
Das ist EINMALIG — der gepinnte Character bleibt für viele
Stories der stabile Identity-Anchor."
- Tool-Liste am Ende vom System-Prompt um den Character-Pfad
ergänzt.
apps/mana/CLAUDE.md Tool-Coverage-Zeile für comic erweitert:
+ create_comic_character / generate_character_variant /
+ pin_character_variant (propose)
+ list_comic_characters (auto)
Tool-Count: comic 3→7. Module 23 unverändert.
107 shared-ai-Tests weiter grün. check für comic-Files clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The M1–M6 build shipped strings in `T = {} as const` constants — clean
enough to keep the i18n-hardcoded baseline at zero, but never the
real plan. This commit closes the loop:
- Add per-locale namespace `augur` with five bundles
(de / en / fr / it / es). DE + EN are translated; FR/IT/ES mirror
DE for parity until proper translation lands. Same staging that
other modules use.
- Replace every `T.x` with `$_('augur.section.x')` across 8 svelte
files + 2 routes. Drop the const blocks where they were the only
reason a script tag had untyped state.
- Existing `KIND_LABELS[k].de` / `VIBE_LABELS` / etc. stay — they
serve dual-mode (web + tools.ts), and the module convention is
nested-locale records rather than svelte-i18n keys for those.
Validators:
- i18n-parity: 36 namespaces × 5 locales — 2852 keys aligned
- i18n-keys: 315 missing (== baseline) — augur adds zero
- i18n-hardcoded: augur not flagged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the deprecated `isPublic` field from picture, memoro, cards,
presi, and uload. The unified `visibility` enum has been the source
of truth since M3 (picture) / M6 (others) and the soft-fallback in
queries was the last consumer of the legacy field. Killing it
cleanly:
- types: drops isPublic from LocalX + X interfaces, and from
CreateDeckInput/UpdateDeckInput/UpdateDeckDto.
- queries.ts: type converters now read `visibility ?? 'space'` (or
'private' for picture) without the isPublic fallback. Cards's
`getPublicDecks` helper now filters on `visibility === 'public'`.
- stores: createX no longer initializes isPublic; updateX no longer
accepts/mirrors it; setVisibility no longer writes the mirror.
- UI: cards CreateDeckModal drops the public-toggle (use the
Picker in DetailView post-create); DeckCard + presi ListView +
/cards/decks/[id] page badges read `visibility === 'public'`.
- collections.ts: drops isPublic: false from seed rows.
- embeds.ts: picture/memoro/cards/presi resolvers drop the
isPublic fallback. Top comment updated to reference
canEmbedOnWebsite as the canonical gate.
Existing IndexedDB rows still carry the stale isPublic value but
nothing reads it. No Dexie schema bump needed (field was never
indexed). No data loss — visibility was mirrored on every flip
during the soft-migrate window so all "public" intent has already
propagated to the unified field.
Closes M6.1 — picture/memoro/cards/presi/uload now have no
legacy visibility flags. Events' isPublished/publicToken stays
(orthogonal RSVP-snapshot system, not legacy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Story-Anlegen ist jetzt zweigleisig: Default ist Character-Mode
(picke einen iterierten Comic-Character mit gepinntem Look),
Fallback ist Quick-Mode (rohes face/body/garments wie bisher) als
opt-in-Toggle für Spontan-Stories ohne Setup.
Datenmodell-Erweiterung (soft, kein Breaking-Change):
- LocalComicStory + ComicStory bekommen ein optionales
`characterId?: string | null` Feld, plaintext, FK auf
comicCharacters.id. Im Quick-Modus null, im Character-Modus die
gewählte Character-id.
- `characterMediaIds` bleibt das einzige Feld, das runPanelGenerate
liest — im Character-Modus enthält es genau die
`pinnedVariantMediaId` als single-element-Array (Snapshot zum
Story-Create-Zeitpunkt). Re-Pinning eines Characters ändert
bestehende Stories also NICHT, weil sie das mediaId fix
gespeichert haben. Im Quick-Modus enthält's face + body? +
garments[] wie vorher. Beide Modi gehen durch denselben
/picture/generate-with-reference-Pfad.
- Soft-Migration: bestehende Stories ohne `characterId` zeigen
weiterhin keine Character-Linkage und rendern wie vorher (die
`characterMediaIds` waren vorher ja schon die Quelle).
Neue Komponente:
- `CharacterRefPicker.svelte` ersetzt den alten `CharacterPicker`
in StoryForm. Mode-Toggle (Character | Quick) erscheint nur wenn
Characters existieren — sonst startet's direkt im Quick-Modus.
Character-Mode zeigt Grid der usableCharacters (nicht-archived
+ pinnedVariantId gesetzt) mit Cover, Style-Badge, Active-Border.
"+ Neuer Character"-Tile öffnet die Builder-Route. Quick-Modus
rendert intern den alten CharacterPicker (face/body/garments) —
reuse statt parallel zu pflegen.
StoryForm:
- 2 neue $state-Felder: `characterId` und (umbenannt-) der bestehende
`characterMediaIds`. CharacterRefPicker emittiert beide via
onChange-Callback.
- createStory bekommt `characterId` mit, das landet auf der Story-
Row. canSubmit greift weiterhin auf `characterMediaIds.length > 0`
— beide Modi liefern mindestens 1 ref.
CharacterBuilder Bugfix: prettier hatte den Add-Prompt-Placeholder
mit nested double-quotes zerstört (z.B. "freundlicher Ausdruck"
wurde zu invalidem HTML). Auf einfache Liste umgestellt.
8/8 Encryption-Tests weiter grün. check für comic-files clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Datenschicht aus Mc1 wird jetzt durch UI benutzbar. End-to-end-Flow:
Tab-Switch zu Characters → "+ Neuer Character" → Stil + Add-Prompt
+ Source-Confirm (face Pflicht, body Toggle) → 4 Varianten parallel
gerendert → User pinnt eine als Identity → Character ist fertig,
nutzbar als Story-Anchor (Mc3 wired das in den StoryForm-Flow).
UI-Komponenten:
- `api/generate-character.ts`: runCharacterGenerate({character, n=4,
quality, model}) ruft /picture/generate-with-reference mit
[face, body?]-Refs + Stil-Prefix + Add-Prompt + Identity-Anchor-
Hint, schreibt N picture.images mit comicCharacterId-Back-Ref,
appended an den Character via comicCharactersStore.appendVariant
(auto-pin auf erste Variant). Ein Server-Call mit n=4 statt 4
parallele — gpt-image-2 Multi-Image-Response in einem Batch.
- `components/CharacterCard.svelte`: Grid-Tile mit Cover (pinned
Variant > erste Variant > Placeholder), Style-Badge, Favorit-
Heart, Amber "Pin offen"-Badge wenn Varianten existieren aber
keine gepinned ist.
- `components/VariantTile.svelte`: einzelne Variant im Grid mit
Pin-Star wenn aktiv, Bottom-Action-Bar auf Hover (Pinnen / Entf.).
Pinned hat primary-Border + Schatten, Unpinned dezent.
- `components/CharacterBuilder.svelte`: Zwei Modi via `existing`-
Prop. Create-Modus: Name + StylePicker + AddPrompt + Source-
Preview (face Pflicht, Body-Toggle). Extend-Modus: Style + Source
fix vom existierenden Character, nur AddPrompt editierbar pro
Generierung. Beide feuern die gleiche runCharacterGenerate-Pipeline.
- `views/CharactersView.svelte`: Grid + "+ Neuer Character"-CTA +
Face-Ref-Empty-State + leeres Empty-Board. Gleicher Aufbau wie
StoriesView für visuelle Konsistenz.
- `views/DetailCharacterView.svelte`: Meta-Card (Titel + Style-
Badge + Variant-Count + Pin-offen-Hinweis), Variant-Grid mit
Pin/Remove, "+ Mehr Varianten"-Button öffnet Builder im
extend-Modus inline (Builder bleibt offen für Iterations-Flow).
Plus Archive/Delete.
- `ListView.svelte` (Modul-Root) bekommt 2-Tab-UI:
**Stories | Characters** mit Count-Badge auf dem Characters-Tab.
Standardpattern wie Wardrobe's Garments|Outfits.
Routes:
- `/comic/character` (Liste, eigenständige Route — Back-Nav aus
Detail/New zeigt darauf)
- `/comic/character/new` (CharacterBuilder im Create-Modus)
- `/comic/character/[id]` (DetailCharacterView mit {#key id}
Re-Mount wie Story-Detail).
check passes 0/0 für comic-files.
Mc3 (Story-Create wechselt auf den neuen Picker, Soft-Migration
für bestehende Stories) folgt im nächsten Commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schreiben-Modul rendert jetzt korrekt unter allen Theme-Varianten
(Lume Gold, Nature Green, Stone, Ocean) statt überall sky-cyan zu
forcieren. 17 Files: alle #0ea5e9/#0284c7 → hsl(var(--color-primary)),
alle var(--color-text-muted, ...) → hsl(var(--color-muted-foreground)),
alle var(--color-border, ...)/var(--color-surface, ...) auf saubere
hsl()-Wraps umgestellt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comic-Modul nutzte bisher rohe meImages direkt als Story-Refs:
gpt-image-2 / Nano Banana variieren zwischen Calls, Panel 1 sah
anders aus als Panel 4, User hatte keine Iteration vor der Story.
Lösung: Comic-Character als eigene Entität, einmal aufgebaut +
iteriert + gepinnt, danach Story-Anchor.
Datenschicht:
- Dexie v49 `comicCharacters` (space-scoped, indices createdAt /
style / isFavorite / isArchived).
- types.ts: LocalComicCharacter mit name + style + addPrompt +
sourceFaceMediaId + sourceBodyMediaId? + variantMediaIds[] +
pinnedVariantId?, plus toCharacter + characterCoverVariantId
helper (pinned > erste Variant > null).
- crypto/registry.ts: comicCharacters entry — name + description
+ addPrompt + tags encrypted; style + IDs + Variant-Liste +
Booleans plaintext.
- collections.ts: comicCharactersTable.
- queries.ts: useAllCharacters, useCharactersByStyle, useCharacter
via scopedForModule (alle space-scoped).
- stores/characters.svelte.ts: createCharacter (auto-pin first
variant fallback), appendVariant (auto-pin if none yet),
pinVariant, removeVariant (mit pin-fallback auf erste
remaining), updateCharacter, toggleFavorite, archiveCharacter,
deleteCharacter. Arrays werden via [...arr] entproxiet (Svelte
5 $state defense).
- module.config.ts: comicCharacters in tables-Liste.
- picture/types.ts + queries.ts: comicCharacterId Back-Ref auf
LocalImage + Image, mutually exclusive mit comicStoryId.
- 3 neue Encryption-Roundtrip-Tests (insgesamt 8 grün) für
charakter-Row, Build-in-progress (no variants), Roundtrip.
Architektur-Entscheidungen (Plan-Doc §11 dokumentiert):
- **space-scoped**, nicht user-global: Source-meImages sind ja
selbst space-scoped post-v40, sonst orphan-Refs nach
Space-Wechsel.
- **Snapshot at story-create**, kein Live-Lookup: Stories
speichern die mediaId der gepinnten Variant zum Erstellungs-
zeitpunkt → re-pinning eines Characters lässt bestehende
Stories unverändert.
- **n=4 fixes Variant-Count**: in einem gpt-image-2-Call
parallel; sweet-spot für Auswahl ohne Decision-Fatigue.
- **Mutually-exclusive Back-Refs** auf picture.images:
comicStoryId XOR comicCharacterId — Image ist Panel ODER
Variant, nie beides.
Mc2 (UI: Builder + Variant-Grid + Routes), Mc3 (Story-Create-
Update + Soft-Migration), Mc4 (MCP/Catalog), Mc5 (Wardrobe-Hook)
folgen separat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Augur's unlisted-share backend was already wired (mana-api
ALLOWED_COLLECTIONS, blob resolver, /share/[token] dispatcher,
SharedAugurEntryView), but the DetailView didn't show the
share controls — flipping an entry to 'unlisted' generated a
token the user couldn't see, copy, regenerate, or expire.
Closes the loop:
- augurStore gains setUnlistedExpiry + regenerateUnlistedToken
(same pattern as calendar/library/places M8.5).
- DetailView's visibility section now renders SharedLinkControls
when the entry is 'unlisted' — URL + copy + QR + regenerate +
revoke + expiry picker.
This makes augur the 4th collection with full unlisted-share
support (events / library / places / augur). My previous commit's
"deferred until clear demand" note was wrong — the heavy lift
(backend + view component) was already done by the augur module
PR; only the DetailView wiring + 2 store methods were missing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The plan ended up simpler than the four-layer sequence I originally
sketched: making the hook smart (use `getEffectiveSpaceId()` instead of
the literal sentinel) replaced both Schicht-A Etappe-2 (throw on
missing) and the per-call-site stamp migration. With that, the
transitional legacy-Home check + post-reconcile dedup pass also
became dead code and got removed in the same cleanup commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With the smart hook always stamping the active-Space id and the seeder
running the deterministic-id contract, the transitional concessions
introduced for the soft-cleanup window are no longer load-bearing:
- `seedWorkbenchHomeOn` no longer scans for a legacy random-uuid Home
in the same Space and defers to it. It just no-ops on a present
deterministic-id row, otherwise inserts. The corresponding three
transitional unit tests are dropped.
- `(app)/+layout.svelte` no longer runs a post-`reconcileSentinels`
dedup sweep. The sweep was belt-and-suspenders for an edge case
that the smart hook + deterministic id structurally prevent.
D-hard via Dexie v50: soft-deletes every uncustomised "Home" row whose
id is NOT `seed-home-<spaceId>`. The per-space-seeds registry
recreates a fresh deterministic-id row on the next `setActiveSpace`
for any Space that lost its uncustomised Home, so the system self-
heals. Customised Homes (description / wallpaper / agent / scope tags)
are preserved.
Combined with v48 this leaves zero legacy duplicates and zero
random-UUID seeds in `workbenchScenes` once the upgrade runs. New
devices coming up against an empty IndexedDB walk through both
upgrades as no-ops and land on the clean state directly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the silent `_personal:<userId>` literal in the creating-hook
with `getEffectiveSpaceId()`, which returns the currently-active Space
when one is loaded and falls back to the personal sentinel for
guests / pre-bootstrap windows. Side-effect: writes during a Brand /
Family / Team session now land under that Space's UUID instead of
silently routing to Personal once `reconcileSentinels` runs — the
underlying tenancy bug Schicht A was supposed to catch.
With the hook doing the right thing automatically, the 16 explicit
`spaceId: getEffectiveSpaceId()` stamps from Etappe 1 are redundant
boilerplate. Reverted across:
picture/stores/boards (boards + boardItems)
events/stores/{guests,items}
companion/stores/chat (conversations + messages)
calc/stores/{calculations,saved-formulas}
quotes/stores/{favorites,custom-quotes}
skilltree/stores/{skills,achievements}
moodlit/stores/moods
plants/mutations
questions/stores/answers (manual + research draft)
data/ai/agents/{bootstrap,kontext}
Helper plumbing:
- `getEffectiveSpaceId()` lives in `scope/active-space.svelte.ts` (no
db dependency) so the creating-hook in `database.ts` can import it
without an ESM cycle. Inlined the `_personal:<userId>` literal there
instead of pulling `personalSpaceSentinel` from `bootstrap.ts`,
which would otherwise tangle the import graph.
- Re-exported via `scope/index.ts` for callers outside the hook.
- `setActiveSpace` and `loadActiveSpace` already funnel through the
shared `applyActiveSpace` helper, so the hook's view of the active
Space stays in sync with the rest of the scope layer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
augur.setVisibility now coordinates with the server-side unlisted-
snapshots table — same pattern as library/calendar/places. The local
token-allocation placeholder from M6 is replaced with real publish/
revoke calls; deletion revokes any active link before tombstoning.
- resolvers.ts: buildAugurEntryBlob with strict whitelist
(source, claim, kind, vibe, encounteredAt, outcome,
outcomeNote when resolved). NEVER inlines feltMeaning,
expectedOutcome, probability, tags, livingOracleSnapshot,
sourceCategory or related FK references — divinatory captures
stay sensitive even when shared.
- SharedAugurEntryView: SSR card with vibe-colored border, kind +
date meta, outcome badge, "Wie es kam" section only when the
sign was actually resolved.
- Dispatcher in /share/[token]/+page.svelte gains the
augurEntries branch.
- mana-api ALLOWED_COLLECTIONS extended to four items so the
publish endpoint accepts augurEntries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors apps/mana/apps/web/src/lib/modules/augur/tools.ts for the
shared mana-tool-registry. Lets persona-runner / mana-mcp / mana-ai
invoke augur over stdio and HTTP without going through the web app.
Tools:
- augur.captureSign (write) — log a new omen / fortune / hunch
- augur.resolveSign (write) — fulfilled / partly / not-fulfilled
- augur.listOpenSigns (read) — what's still waiting on resolution
- augur.consultOracle (read) — Living Oracle reflection from history
- augur.yearRecap (read) — structured year-in-review snapshot
The pure-math engines (fingerprint, matchScore, makeReflection,
yearRecap aggregation) are mirrored from the web-app lib/. Both
sides have unit tests covering the same contract — keep them in
sync. A future shared package would dedupe.
Encrypted fields declared on each spec (audit:encrypted-tools went
from 15 to 20). ModuleId extended in types.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Locks in the contracts of the pure-math modules:
- reminders: 30-day fallback, isDue ≤ today, signed daysUntilDue
- calibration: weighted hit-rate (partly = 0.5), Brier squared error,
per-source ranking, vibe directional hit (good = fulfilled,
bad = not-fulfilled, mysterious = no direction)
- living-oracle: stop-word filtering, 5-component matchScore, find
against resolved history only, both cold-start gates (≥50 history
AND ≥3 matches), reflection text shape
- year-recap: year filter, distribution counts, best/worst-source
n>=3 eligibility, mostSurprising = good→not-fulfilled OR
bad→fulfilled, mostFulfilled ordered by resolvedAt desc, capped
- correlation-engine: zero-σ refusal, 0.3σ delta threshold, n>=5
minimum, mood + sleep-quality + sleep-duration handled
independently, sort by |Δσ| desc
65 tests across 5 files, all pure — no Dexie + no runes. Synthetic
mood/sleep maps for the cross-module engine.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Augur landed (faa16fa89) with the visibility Picker + setVisibility
already in place — but no embed-resolver and no entry in the
/settings privacy registry. So flipping an omen to 'public' did
nothing visible, and the kill-switch couldn't see augur records
either. Closes both gaps.
- New EmbedSource `augur.entries` + resolveAugurEntries. Whitelist:
claim + "{kind} · {vibe} · {outcome}" line. Personal fields
(feltMeaning, expectedOutcome, source name, outcomeNote, related
dream/decision links, livingOracleSnapshot) all stay private.
Optional `status` filter maps to AugurOutcome so the user can
build "predictions I got right" widgets.
- Sort: resolved-first, then encounteredAt desc — fulfilled
predictions outrank still-open ones (more interesting public
signal).
- Inspector dropdown gains "Augur (Omen / Wahrsagungen)".
- exposed-records.ts gains the augur entry — augur records now
show up in /settings → Privatsphäre and the kill-switch.
Note: augur's `unlistedToken` field (set by its store on
'unlisted' flips) is currently dead code — the mana-api unlisted
backend doesn't know about `augurEntries` and there's no shared
view component. Half-state predates this commit; full unlisted-
share wiring is a separate, larger task that would touch the
backend's ALLOWED_COLLECTIONS, the resolvers blob, and a new
SharedAugurEntryView. Leaving as-is until there's clear demand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan now reflects what's actually merged: D-soft, B+C, and Schicht A
Etappe 1 are in. Etappe 2 (creating-hook flip to throw) is queued
post-soak; D-hard (deterministic-id rename) follows after that.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schicht A Etappe 1 of the workbench-seeding-cleanup plan: every module
store that writes to a space-scoped Dexie table now sets `spaceId`
explicitly via `getEffectiveSpaceId()` instead of relying on the
creating-hook's silent auto-stamp. Etappe 2 (flip the hook to throw on
missing spaceId) follows after a soak day.
New helper:
- `data/scope/scoped-db.ts` — `getEffectiveSpaceId()` returns the
active Space's id when one is loaded, falling back to the personal
sentinel `_personal:<userId>` for guests / pre-bootstrap windows.
Symmetric with `getEffectiveUserId()`. Re-exported from
`data/scope/index.ts`.
Migrated call sites (16 writes across 10 modules):
picture: boards, boardItems
events: eventGuests, eventItems
companion: companionConversations, companionMessages
calc: savedFormulas, calculations
quotes: quotesFavorites, customQuotes
skilltree: skills, achievements
moodlit: moods
plants: wateringSchedules
questions: answers (manual + research-driven paths)
data/ai: agents, agentKontextDocs
The audit also flagged `_serverIterationExecutions`, but it's an
internal infra table (leading underscore, not in SYNC_APP_MAP, never
synced) — the creating-hook doesn't run on it, so no change needed.
No behaviour change today: the hook still falls through to sentinel-
stamping when spaceId is missing, so existing rows + any unmigrated
caller keep working. Etappe 2 flips that fallback to a hard error,
turning silent omissions into write-time failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups on the borderless redesign:
- z-index bumped from `z-50` to `z-[100]`. The `(app)/+layout`'s
`.bottom-stack` (PillNav, QuickInputBar, sync-status row) sits at
z-index 90, so Tailwind's `z-50` was leaving the bottom chrome
poking through the dark backdrop. 100 is the next round number
above the layout's stack and keeps the lightbox unambiguously on
top of everything in the (app) tree.
- Meta column tightens from `max-w-md` (~28rem) to `max-w-[14rem]`
(~224px) so even long prompts hug the right edge instead of
stretching halfway across the image. The detail row gains
`flex-wrap: wrap` so model + dimensions + date wrap cleanly when
they don't fit on one line — `justify-content: flex-end` keeps
every line right-aligned to the corner.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the Augur module: capture omens, fortunes, and hunches in
a poetic Witness mode and read them back empirically in Oracle mode.
Same data, two lenses; the killer mechanic is the Living Oracle that
materialises empirical reflections from the user's own resolved
history at capture time.
Why now: docs/future/MODULE_IDEAS.md captured the brainstorm, then
the spec landed at docs/plans/augur-module.md as a Witness+Oracle
hybrid. Built end-to-end through M6 in one go.
Highlights:
- Witness gallery + DueBanner + DetailView + Resolve flow
- Oracle stats: calibration-per-source, vibe-hit-rate, cross-module
correlation engine (mood/sleep/duration after-windows)
- Living Oracle: deterministic fingerprint+match against user's own
resolved history; cold-start-gated at 50 resolved entries
- Year-Recap view at /augur/recap/[year]
- 5 MCP tools: capture_sign, resolve_sign, list_open_signs,
consult_oracle, augur_year_recap (in AI_TOOL_CATALOG)
- Visibility integration: default 'private', VisibilityPicker in
DetailView. Server-side unlisted-snapshot-publish stays follow-up
- v47 Dexie schema; encrypted: source/claim/feltMeaning/
expectedOutcome/outcomeNote/tags/livingOracleSnapshot
- LOCAL TIER PATCH: requiredTier 'guest' for testing
Strings interpolated through `T` constants so the i18n-hardcoded
baseline stays at 0 for augur — real $_('augur.*') keys land later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups to the per-space-seeds refactor:
1. Transitional check in `seedWorkbenchHomeOn`: if a Space already
carries an uncustomised "Home" row under a legacy random uuid (a
D-soft dedup survivor from before the deterministic-id contract
landed), defer to it instead of inserting a parallel
`seed-home-${spaceId}` row. Avoids an unnecessary
create-then-soft-delete roundtrip via the +layout dedup pass and
the sync churn that would follow. Schicht D-hard will rename
surviving rows to the deterministic id and this branch can go away.
2. `wiring.test.ts` — integration test for the full chain
(registry → workbench-home seeder → Dexie). Drives the same
`runSpaceSeeds` entry point that `setActiveSpace` calls in
production, so the test fails if any seam in the wiring breaks.
Includes the rapid back-and-forth Space-switch scenario that the
original bug ran into; with the deterministic id + get-then-add
guard, 5×2 activations produce exactly 2 rows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the M6 loop — flipping a memo, card-deck, or presi-deck to
'public' now actually surfaces it on the owner's website embed.
Previously M6 wired the Picker but the embed pipeline didn't know
about these sources, so the flip had no visible effect.
Three new sources in EmbedSourceSchema:
- memoro.memos — voice-memo teaser. Title + intro (140 chars) +
audio duration. Transcript, source-audio paths, and per-utterance
speaker data stay private — those are the user's words verbatim
with much stronger privacy weight than a curated headline.
- cards.decks — flashcard-collection teaser. Name + "N Karten".
Card fronts/backs, difficulty, review history all private — the
deck is a unit; the cards belong to the play experience.
- presi.decks — "talks I've given" teaser. Title + "N Folien"
(counted by joining the slides table). Slide content stays
private — the public deck is a pointer, the slides belong to
the talk experience.
Each resolver tolerates the M6 soft-migration window: visibility
falls back to legacy isPublic for rows that haven't been re-saved
since the M6 commit.
Inspector dropdown updated to expose all 15 sources.
Note: 3 unrelated svelte-check errors in
data/seeds/wiring.test.ts (spaceId on LocalWorkbenchScene) from a
parallel session. Not introduced here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "Privatsphäre" tab to /settings (next to Sicherheit) that lists
every record currently flipped to 'public' or 'unlisted' across all
15 visibility-aware tables, with one-click downgrade per record and a
global "Alle auf privat zurücksetzen" kill-switch.
Architecture:
- `lib/data/privacy/exposed-records.ts` is the single registry of
visibility-aware tables. For each: the collection name, encryption
flag, title-extraction strategy, deep-link, and a dynamic-import
fixer that calls the module's setVisibility (so unlisted records
properly revoke their server snapshots, not just flip the field).
- `PrivacySection.svelte` subscribes via Dexie liveQuery so flips
from any module DetailView surface here immediately.
- Kill-switch iterates the same fixers — best-effort sweep that
logs failures but doesn't abort. Toast reports flipped/failed.
Two error/empty states wired:
- "Aktuell ist nichts öffentlich" reassures the privacy-conscious
user that their footprint is zero.
- Per-record "Privat" button is disabled while busy so multi-clicks
don't race.
Adding a new visibility-aware module: append one entry to TABLES in
exposed-records.ts. The Section auto-renders it.
Note: 2 unrelated svelte-check errors in
stores/workbench-scenes.svelte.ts (ensureSeedScene) from a parallel
session. Not introduced here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces three race-prone seeding paths in `workbench-scenes.svelte.ts`
(count==0 init seed, replay-on-register seed, per-Space-change seed)
with a single registry pattern:
- `data/scope/per-space-seeds.ts` — registry of `Seeder` callbacks,
fired by `setActiveSpace` whenever the active Space id changes.
Errors are isolated per seeder so one module's bug can't block the
others.
- `data/seeds/workbench-home.ts` — registers the workbench Home seeder.
Uses the deterministic id `seed-home-${spaceId}` and a get-then-add
guard, making the seed structurally idempotent: re-running for the
same Space is a no-op regardless of timing. Replaces the old
random-UUID + check-by-presence pattern that the seeding race could
defeat.
- `data/seeds/index.ts` — side-effect barrel imported once at the top
of `(app)/+layout.svelte` so every module's seeder is in the
registry before the first `loadActiveSpace` fires.
- `active-space.svelte.ts` — both `setActiveSpace` and `loadActiveSpace`
funnel through a private `applyActiveSpace` helper that calls
`notifyHandlers` AND `runSpaceSeeds` in one place. No more divergent
state-update paths.
- `workbench-scenes.svelte.ts` — `ensureSeedScene` and the two
ad-hoc seed calls deleted. The store now only owns rendering and
user-driven CRUD; seeding lives in the registry.
This is Schicht B + C of the broader cleanup plan
(docs/plans/workbench-seeding-cleanup.md). Schicht D-soft already
collapsed existing duplicates; this PR prevents new ones from forming.
The tests cover the registry contract (register/run/idempotency/error
isolation), the deterministic-id helper, and the seeder against an
isolated Dexie fixture (race-safety, no-overwrite of customised rows,
per-Space isolation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings four legacy isPublic-only modules onto the unified visibility
system. The Picker (private/space/public) replaces the ad-hoc
boolean toggle in memoro + cards + presi DetailViews. Each store
now mirrors `visibility ↔ isPublic` on every flip so older readers
(search index, snapshot pipelines, sync server) keep working
through the soak window — M6.1 will hard-drop the legacy field
once the field has propagated to all rows.
Per-module:
- memoro: setVisibility on memosStore + Picker in DetailView
properties row. Picker reads the unified `visibility` with a
fallback to legacy isPublic.
- cards (decks): replaces the "Öffentlich Ja/Nein" toggle button
with the Picker. createDeck initializes both fields; updateDeck
mirrors when callers pass legacy isPublic.
- presi (decks): same pattern as cards. setVisibility added to the
store factory's exported surface.
- uload (tags): no active CRUD UI, so this is type-only soft-migrate
+ seed-data update. Future tag-management view writes visibility
directly. No store mutation method needed yet.
Out of scope (intentional):
- picture isPublic hard-drop: deferred. Picture has been on
visibility since M3 (commit 0e9f574df), the soak window is mature
enough to consider, but pre-launch there's no urgency. Defer to
M6.1 with the rest of the legacy-field cleanup.
- events isPublished/publicToken: STAYS. This isn't legacy — it's
the orthogonal mana-events RSVP-snapshot system. visibility
controls website-embed eligibility; isPublished controls RSVP
page existence. Different concerns; both stay.
Picker hides 'unlisted' for all four (no server-publish-snapshot
flow wired for these collections — same pattern as habits/quiz/events).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Feedback on the previous lightbox: the image was capped at 60vh
inside a bordered card, with metadata + action buttons stacked
underneath in their own panel. Result: ~40% of the modal surface
was chrome around a small picture, and the eye landed on the card
edge before the actual generation. The user asked for image-first:
fill the height, no module border, gray text in the bottom-right
corner.
New layout:
- Borderless backdrop. The card frame is gone; the image sits
directly on a near-black overlay (rgba(0,0,0,0.92)) so nothing
competes for the eye.
- Image fills the available area via `max-h-full max-w-full
object-contain` inside a flex-center wrapper, with 6/10-rem
outer padding so the picture doesn't kiss the viewport edges
on small screens.
- Metadata moves to a small grey overlay in the bottom-right
corner — prompt on top, then a single inline detail line
(model · dimensions · date) separated by middle dots. Right-
aligned so longer prompts wrap toward the image edge instead
of the centre.
- Close becomes a circular icon-button in the top-right (X), no
longer a footer button. ESC + backdrop-click still close.
- Caller-supplied actions (the `actions` snippet) move to the
bottom-left so they don't fight the meta block visually.
Colour treatment uses literal white-alpha + black-alpha values
in a scoped `<style>` block instead of theme tokens. The lightbox
always sits on a literal near-black backdrop regardless of which
theme is active, so theme-aware muted tokens would render too
dark in light themes. The validator's brand-literal escape hatch
(see scripts/validate-theme-utilities.mjs comment) covers this
exact case.
Empty-state (publicUrl missing) gets a small SquaresFour icon in
a soft white-alpha tile so the modal still has a visible centre
when an image fails to load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Home-seeder in workbench-scenes.svelte.ts writes new scenes without
spaceId, so the creating-hook stamps them with the _personal:<userId>
sentinel. The per-space dedup check filters by the real space UUID and
never finds them — every login adds another Home row, and every visit
to a non-personal Space (Brand/Family/Team) drops yet another seed
into the personal Space.
This is Schicht D-soft of the broader cleanup plan
(docs/plans/workbench-seeding-cleanup.md): a one-shot dedup pass that
collapses duplicate "Home" rows per spaceId, merging openApps from the
losers into the survivor (most apps wins, ties by most-recent
updatedAt) and soft-deleting the rest so mana-sync propagates the
cleanup to other devices. Touches only rows that look like fresh
default seeds — anything customized (description, wallpaper, agent
binding, scope tags, non-Home name) is left alone.
Wired in two places: a Dexie v48 upgrade so it runs once per device on
schema bump, and a belt-and-suspenders pass in (app)/+layout.svelte
right after reconcileSentinels() to catch the edge case where
sentinel-stamped rows just collapsed into the same UUID group as
already-reconciled rows.
The structural fix that prevents new duplicates from ever forming
(per-space-seeds registry + deterministic seed ids +
creating-hook hardening) ships in follow-up commits per the plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>