Two one-shot bootstraps left a per-user flag in localStorage so they
wouldn't run twice — and after F7 deleted the helpers themselves
(2a8e8ff98), the flags pointed at code that no longer existed:
mana.profile.silentTwinRepair.<userId>
mana.profile.avatarMigration.<userId>
New \`cleanupOrphanMigrationFlags()\` runs once per page load from the
(app) layout's onMount, right after \`restoreClientIdFromDexie()\`.
Cheap (single localStorage scan), idempotent (no-op once swept),
silent on private-mode / quota errors. The known-orphan prefix list
lives in the helper file with deletion-commit refs so it's clear
when each entry can be retired.
Future migration deletions: append the prefix to ORPHAN_KEY_PREFIXES
in the same commit that drops the helper, and the next page load
on every device cleans up.
Closes Punkt 8 of the F1-F7 follow-up audit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final follow-up to drop the type-bypass patterns from F3's codemod.
Mit \`Partial<LocalX>\` als Deklaration akzeptiert Dexie's UpdateSpec
ohne weiteren Cast — die kombinierte \`as Record<string,unknown>\` +
\`as never\` Konstruktion wird durch eine einzige saubere
Typ-Annotation ersetzt.
Touched stores (12 Files):
wardrobe/stores/{garments,outfits}, invoices/stores/invoices,
sleep/stores/sleep, library/stores/entries,
comic/stores/{characters,stories},
profile/stores/me-images, recipes/stores/recipes,
broadcast/stores/campaigns, writing/stores/{styles,drafts}
Plus inline literal-object patterns (\`{ lines, totals } as Record\`,
\`{ content } as Record\`, \`{ audience } as Record\`,
\`{ ...spread } as Record\` im comic appendPanel).
Verbleibende \`as Record<string, unknown>\` Vorkommen sind legitime
Reads von typed-data und nicht das F3-Pattern.
7670 svelte-check Files, 0 Errors, 0 Warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds list_view, detail_view, page, links_route, analytics_route,
settings_route, tags_route sub-namespaces across all 5 locales.
Component patches in follow-up commit (split to land safely with
parallel sessions in this repo committing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mana-web SSR + browser need the analytics URL so the inline
FeedbackHook + /community page can talk to the new public-feedback
endpoints. SSR uses the internal docker hostname; browser uses the
public subdomain.
Note: analytics.mana.how DNS + Caddy reverse-proxy block must be
provisioned separately on the Mac Mini before browser-side calls
work — TODO in deploy-followup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to a68933bff. Multi-Terminal commits hatten meinen ersten
Cleanup teilweise verschluckt — dieser Commit räumt die übrig
gebliebenen 22 \`update(id, X as never)\` Casts in den 13 Stores
zusammen mit den letzten \`as Record<string, unknown>\` Argumenten
für \`encryptRecord\` weg. Public API der Stores unverändert,
\`Partial<LocalX>\` reicht für Dexie's UpdateSpec ohne Cast.
7670 svelte-check Files, 0 Errors, 0 Warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Patches ListView, AssessmentWizard, ReminderManager, RoutineCreator,
SessionHistory, SessionPlayer, plus the /stretch route page title.
Locale JSONs landed in 421663ba3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Locale files only — component patches land in a follow-up commit.
Splitting the work to land translations safely while parallel sessions
in this repo are committing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cleanup-Schuld aus F3 (sync-field-meta-overhaul). Der Codemod hatte
\`Record<string, unknown>\`-Deklarationen via \`as never\` durch
Dexie's strikten \`UpdateSpec<LocalX>\` durchgemogelt. Jetzt sauber:
jeder Store deklariert \`const wrapped: Partial<LocalX> = { ...patch }\`
und Dexie akzeptiert das ohne Cast.
Touched stores (13 Files, ~24 update-sites):
comic/stores/{stories,characters}, comic/views/DetailView
wardrobe/stores/{garments,outfits}
invoices/stores/invoices
sleep/stores/sleep
library/stores/entries
profile/stores/me-images
recipes/stores/recipes
broadcast/stores/campaigns
writing/stores/{styles,drafts}
\`encryptRecord\` ist generic (\`<T extends object>\`) und akzeptiert
Partial<LocalX> direkt — der äußere \`as Record<string, unknown>\`
Cast ist auch weg.
Übrig bleibende \`as Record<string, unknown>\`-Vorkommen in
{invoices,broadcast}/stores/settings + profile/user-context sind
legitime Reads von nested-data, nicht das F3-Pattern.
7670 svelte-check Files, 0 Errors, 0 Warnings. 29/29 sync.test.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds wardrobe namespace (de/en/es/fr/it) covering ListView,
GridView, OutfitsView, DetailGarmentView, DetailOutfitView,
GarmentForm, OutfitComposer, GarmentTryOnButton, TryOnButton,
TryOnModelPicker, CategoryTabs, GarmentCard, OutfitCard, plus
the /wardrobe/compose route. Categories/occasions/seasons routed
through dynamic `wardrobe.categories.{key}` lookups so constants.ts
keeps the order-tuples without leaking DE labels into UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two one-shot bootstraps that were the structural source of three
of the four pre-F1 conflict-toasts have been obsolete since F2 +
shipped:
- F2 now stamps `origin: 'migration'` on Repair-Migration writes via
the system actor wrapper, so even if these helpers ran they would
not surface as conflict toasts on other devices anymore.
- F3 took `updatedAt` out of the wire entirely, removing the field
the helpers used to bump explicitly (the only reason their writes
showed up in someone else's pull as a conflict).
Files removed:
- apps/mana/apps/web/src/lib/modules/profile/migration/repair-silent-twin.ts
- apps/mana/apps/web/src/lib/modules/profile/migration/legacy-avatar.ts
- (empty) migration/ directory
Callers cleaned up:
- profile/MeImagesView.svelte — onMount block + imports gone.
- wardrobe/ListView.svelte — same; `onMount` import dropped (unused).
The original silent-twin bug was already fixed in M2.5 via
`setPrimary` no longer creating a "silent twin" — the repair helper
existed only to clean up rows produced by the buggy code before the
fix shipped. Pre-live, with no production data, no users hold rows
in that broken state, so the cleanup is safe.
Plan: docs/plans/sync-field-meta-overhaul.md F7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the client_id-inflation bug where every localStorage wipe
spun up a fresh sync identity. Five distinct client_ids accumulated
in mana_sync.sync_changes for a single physical browser over five
days — every wipe made the device's own historical writes look like
"another session" on replay.
Architecture:
- New Dexie v54 table `_clientIdentity` (single row keyed by
`id='self'`) is the canonical source of the client id.
- `restoreClientIdFromDexie()` runs once at app boot, before
`createUnifiedSync`. Reconciles Dexie ↔ localStorage in three
scenarios: Dexie has it (restore localStorage), only localStorage
has it (canonicalise into Dexie), neither has it (mint + write
both). Dexie wins on disagreement.
- `getOrCreateClientId()` keeps reading from localStorage
synchronously — that's the hot path inside push/pull. The async
reconciliation just makes sure localStorage has the right value
by the time sync starts.
Survives: clear-site-data, incognito flush, Settings → "delete
browser cache". Does not survive: full IndexedDB reset (intentional
— that's a real device reset).
Plan: docs/plans/sync-field-meta-overhaul.md F6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the on-mount `void userContextStore.ensureDoc()` race from
ContextOverview / ContextInterview / ContextFreeform. After F4 the
server creates the singleton at /register time; the first sync pull
lands it before the UI can race.
The internal logic survives as `getOrCreateLocalDoc()` — a private
fallback for the brand-new client whose pull hasn't caught up yet.
First user mutation (setField, setFreeform, …) inserts an empty
local doc with origin='user' on the field-meta map. The F2
conflict-gate then makes sure the server's origin='system' bootstrap
row never silently overwrites the user's local edits — they land in
the conflict toast like a real edit-race would.
`kontextStore.ensureDoc()` is intentionally kept (per-Space, not
per-user; F4 didn't bootstrap it). Its removal will follow once
Space-creation gains its own bootstrap hook.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes `updatedAt` from the wire protocol and from every Local-prefixed
record type. Replaced by two orthogonal mechanisms — deriveUpdatedAt()
for read-side public-facing values, _updatedAtIndex shadow for indexed
sorts.
Local-side:
- New `_updatedAtIndex` shadow column. Stamped by the Dexie creating /
updating hook on every write. Stripped from the pending-change payload
so it never travels to mana-sync. Indexed in Dexie v53 on the 22 tables
that previously indexed `updatedAt`.
- `deriveUpdatedAt(record)` in sync.ts returns max(__fieldMeta[*].at) so
the public-facing Task / Note / etc. shape keeps an `updatedAt: string`
property without holding it as data.
- Type-converters across ~60 module/queries.ts and types.ts files now
call `deriveUpdatedAt(local)` instead of reading `local.updatedAt`.
Module-store sweep:
- Regex codemod removed `updatedAt: new Date().toISOString()` /
`: now` / `: now()` / `: nowIso()` stamping from 121 store files
(~382 call sites total). Single-property update calls
(`{ updatedAt: now }`) collapsed to `{}`; touch-only patterns
(writing/drafts, writing/generations) kept the call as a no-op
because the hook now stamps `_updatedAtIndex` automatically on
any Dexie modification.
- Local* interfaces stripped of `updatedAt: string` (43 types.ts files).
Public-facing types (Task, Note, Mission, Agent, …) keep
`updatedAt: string` as a computed read-side property.
- Companion's chat conversation now sorts on a real
`lastMessageAt` data field instead of touching `updatedAt`.
- Session-only stores (times/session-alarms, session-countdown-timers)
stamp `updatedAt: now` directly because they're not in Dexie and
have no field-meta layer to derive from.
Sync engine:
- applyServerChanges sets `_updatedAtIndex` itself when applying
server changes (max of server-field times for updates, recordTime
for inserts) so server-replays land orderable.
- Dropped the legacy `localUpdatedAt` fallback — every record now has
`__fieldMeta`, the per-field at is the canonical source.
- Soft-delete tombstone path stops stamping `updatedAt: serverTime`,
uses `_updatedAtIndex` instead.
Server-side:
- mana-ai iteration-writer no longer emits `updatedAt` in
sync_changes.data; receivers derive it from the field-meta map.
- mana-sync types: no change (the wire format already uses
`field_meta` / `at` from F1).
Out of scope: backend Drizzle schemas (mana-credits, mana-events, …)
keep their `updated_at` columns. Those are pure server-internal — not
part of the sync_changes / __fieldMeta mechanism F3 cleans up.
Tests + checks:
- 0 svelte-check errors over 7652 files.
- 29/29 sync.test.ts (vitest).
- 61 mana-ai bun tests.
- mana-sync go test ./... cached green.
Plan: docs/plans/sync-field-meta-overhaul.md F3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lasts war im Workbench-Add-Page-Picker nicht findbar — mein M1-Commit
setzte nur den MANA_APPS-Eintrag in shared-branding (für AppSlider/
Launcher), aber NICHT den parallelen registerApp-Eintrag im web-
internen \$lib/app-registry/apps.ts (für Workbench-Scenes, DnD,
Detail-Routes).
- firsts: name "Firsts" → "Erste Male"
- lasts: NEUER registerApp-Block mit name "Letzte Male", Hourglass
icon, color #6366f1, contextMenuActions "Neues letztes Mal",
collection 'lasts', paramKey 'lastId', dragType 'last',
createItem ruft lastsStore.createSuspected.
Workbench-Picker filtert nach name — die DE-Namen tauchen jetzt
direkt in der Suche auf.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"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>
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>
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>
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>
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>
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>
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>
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>