Commit graph

898 commits

Author SHA1 Message Date
Till JS
fa5dbb2cfc i18n(firsts): wire ListView to namespace — 15 strings cleared
Patches tabs, quick-add, category filter, search, stats, dream/lived
edit forms, repeat picker, expectation-vs-reality labels, context menu,
all empty states, people-view "Alleine" fallback. CATEGORY_LABELS +
PRIORITY_LABELS routed through firsts.categories.* / firsts.priorities.*
keys; constants kept in milestones/categories.ts for non-Svelte
callers. Locale-aware Date via get(locale).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:59:09 +02:00
Till JS
275130f8a6 test(sync): cross-cutting integration tests for field-meta overhaul (Punkt 12)
Six new tests in sync.test.ts under the "field-meta overhaul (F1-F4-fu)"
block, verifying the architectural promises of the 2026-04-26 sync
field-meta overhaul end-to-end:

- deriveUpdatedAt returns max(__fieldMeta[*].at)
- deriveUpdatedAt gracefully handles legacy / null records
- Dexie creating-hook stamps __fieldMeta + _updatedAtIndex on every
  local write
- Dexie updating-hook bumps __fieldMeta only for changed fields and
  syncs _updatedAtIndex with the latest at
- SYSTEM_BOOTSTRAP-stamped local insert produces origin='system' (the
  fallback path in userContextStore + kontextStore)
- Bootstrap-twin race scenario: local SYSTEM_BOOTSTRAP row + later
  server insert collapses via field-LWW with no conflict surface

Also re-exports SYSTEM_BOOTSTRAP from $lib/data/events/actor for
parity with the other SYSTEM_* sentinels.

35/35 sync.test.ts pass (29 prior + 6 new).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:54:35 +02:00
Till JS
220afc092a i18n(firsts): add namespace JSONs for de/en/es/fr/it
Locale-only — ListView patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:49:24 +02:00
Till JS
b06d950c4f i18n(goals): wire GoalEditor to namespace — 15 strings cleared
Patches form labels, event type options (now reactive via $derived),
source/comparison/period selectors, action buttons. Locale JSONs
landed in the previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:48:01 +02:00
Till JS
421a49a2a8 docs(sync): close Punkt 5 audit — backend updated_at columns are not sync orphans
Survey of all 17 backend Drizzle schemas (mana-mail/-media/-auth/
-analytics/-research/-events/-subscriptions/-credits + apps/api/
{unlisted,website,traces,presi,todo}):

- 3 columns are actively read by service code:
  - research.providerConfigs.updatedAt — explicit write + DTO field
  - unlisted.snapshots.updatedAt — read in public response
  - website.customDomains.updatedAt — read in DNS-status response
- 14 columns are AUTO-ONLY: Drizzle stamps them via defaultNow() /
  $onUpdate(), no service code reads them.

But the AUTO-ONLY columns are NOT sync-orphans — they're standard
Drizzle audit-timestamp convention, useful for Postgres-level forensics
(`ORDER BY updated_at DESC` to find recently-modified rows during
debugging). F3's plan note ("pure server-internal columns, not touched")
correctly identified them. No cleanup is needed.

Closing the audit item with rationale documented in
docs/plans/sync-field-meta-overhaul.md and DATA_LAYER_AUDIT.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:47:41 +02:00
Till JS
81f1056b4a i18n(goals): add namespace JSONs for de/en/es/fr/it
Locale-only — GoalEditor patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:47:02 +02:00
Till JS
9a712dde9f i18n(gifts): wire +page to namespace — 15 strings cleared
Patches all toast/error messages, page header, action buttons, tabs,
received/created sections, create form, info card. Locale-aware
Date/number formatting via get(locale). Locale JSONs landed in the
previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:46:03 +02:00
Till JS
ae6a14fb76 feat(shared-ai): SYSTEM_BOOTSTRAP system source — fallback inserts now stamp origin='system'
The race-window `getOrCreateLocalDoc()` fallback in userContextStore +
kontextStore stays (without it, a write that lands between "endpoint
provisioned the singleton in mana_sync" and "first pull landed it in
IndexedDB" would hit `update(missing-id, diff)` — a Dexie no-op that
silently swallows the user's edit). But it was semantically lying: the
insert stamped `origin='user'` even though the row is logically a
client-side replica of the server-side bootstrap.

This commit adds `SYSTEM_BOOTSTRAP = 'system:bootstrap'` to
`@mana/shared-ai` and wraps the two fallback inserts in
`runAsAsync(makeSystemActor(SYSTEM_BOOTSTRAP), ...)`. The Dexie hook
now stamps `origin: 'system'` on the empty-row insert — structurally
identical to the row mana-auth's bootstrap-singletons.ts writes. When
the server's pull arrives later both sides carry the same origin and
the conflict-gate stays quiet. The user's subsequent writes still
stamp `origin: 'user'` on the changed fields.

Plan: docs/plans/sync-field-meta-overhaul.md (F4-fu Fallback-Origin row).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:44:30 +02:00
Till JS
01681b58ff i18n(gifts): add namespace JSONs for de/en/es/fr/it
Locale-only — page patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:43:01 +02:00
Till JS
36d832a3db i18n(library): wire DetailView to namespace — 16 strings cleared
Patches all action labels, kind/status/format pills (routed through
dynamic library.kinds.*, library.statuses.*, library.book_formats.*),
detail dt/dd pairs, restart label, times badge, review section.
constants.ts kept with literal {de,en} maps for non-Svelte callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:39:25 +02:00
Till JS
099cac4a01 feat(auth): explicit bootstrap-singletons endpoint + idempotent functions (F4 robust)
The F4 server-side singleton bootstrap was fire-and-forget at signup
time — a transient mana_sync outage during registration would leave the
user with no singleton and only the in-store `getOrCreateLocalDoc()`
fallback to race on the first write. The signup-hook is still the
happy-path zero-latency bootstrap; this commit adds a deliberate
reconciliation path that converges on every boot.

- Idempotent `bootstrapUserSingletons` / `bootstrapSpaceSingletons`:
  both functions now existence-check sync_changes before INSERT and
  return boolean (true=inserted, false=skipped).
- New endpoint `POST /api/v1/me/bootstrap-singletons` — JWT-gated under
  the existing `/api/v1/me/*` prefix. Provisions the caller's
  userContext and the kontextDoc for every Space they're a member of.
  Returns `{ ok, bootstrapped: { userContext, spaces: { id: bool } } }`.
- Webapp `(app)/+layout.svelte` calls the endpoint once per
  authenticated boot, after `restoreClientIdFromDexie()` and before
  `createUnifiedSync.startAll()`. Best-effort; failures swallow into a
  console warning and the in-store fallback still covers the rare
  race window.

Plan: docs/plans/sync-field-meta-overhaul.md (F4-robust row).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:38:14 +02:00
Till JS
98d334045a i18n(library): add namespace JSONs for de/en/es/fr/it
Locale-only — DetailView patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:38:03 +02:00
Till JS
4731bc80fd i18n(ai-missions): wire ListView to namespace — 22 strings cleared
Patches list/create/detail panes incl. PHASE_LABELS, describeCadence,
describeState, formatRelative, grant box, iteration phase block, error
details, feedback form. Locale JSONs landed in the previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:36:12 +02:00
Till JS
da15f8de47 i18n(ai-missions): add namespace JSONs for de/en/es/fr/it
Locale-only — component patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:33:47 +02:00
Till JS
b064e8e51e fix(community): import queries via .svelte path; type DetailView reaction filter
- Views import '../queries.svelte' (not '../queries') so module
  resolution finds the renamed file.
- DetailView's filter callbacks need an explicit string param-type
  under the stricter implicit-any check — myReactions is string[].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:30:47 +02:00
Till JS
4081ce6346 fix(community): queries.ts → queries.svelte.ts for runes ('$state is not defined')
Svelte 5 runes only work in .svelte / .svelte.ts files; the .ts
extension caused a server-side ReferenceError on /community SSR
because the runtime ships no $state symbol there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:27:21 +02:00
Till JS
0a03e9e882 i18n(ai-agents): wire ListView to namespace — 23 strings cleared
Patches list/create/detail panes incl. POLICY_LABEL, TEMPLATES,
mission state labels, natural-language policy summary. Locale JSONs
landed in the previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:26:30 +02:00
Till JS
53fecbf4a7 chore(dexie): v55 — sweep orphan updatedAt field from existing rows (F3 cleanup)
After F3 of the sync field-meta overhaul, every read of "last modified"
goes through `deriveUpdatedAt(record)` over `__fieldMeta`. The legacy
`updatedAt` field on existing IndexedDB rows was deliberately left in
place by v53 (its comment explicitly defers the row-rewrite to a later
cleanup) so the cut-over could proceed without a full DB rewrite.

This v55 upgrade walks every sync-relevant table (`Object.keys(TABLE_TO_APP)`)
and `delete row.updatedAt`. Idempotent (rows without the field are a
no-op), best-effort (try/catch per table guards against a registry
entry that doesn't yet have a Dexie store row).

Local-only tables (_pendingChanges, _activity, _clientIdentity,
_aiDebugLog) never carried `updatedAt`, so they stay out of the sweep.

Plan: docs/plans/sync-field-meta-overhaul.md (F3-fu row in Shipping Log).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:26:21 +02:00
Till JS
da50da8964 i18n(ai-agents): add namespace JSONs for de/en/es/fr/it
Locale-only — component patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:23:57 +02:00
Till JS
3df7391905 feat(auth): bootstrap per-Space kontextDoc on Space-creation (F4 follow-up)
Symmetrically extends the F4 server-side singleton bootstrap to the
per-Space `kontextDoc`. Every Space-creation — Personal at signup and
brand/club/family/team/practice via the org plugin — now writes an empty
kontextDoc row straight into mana_sync.sync_changes with origin='system',
client_id='system:bootstrap'. Fresh clients pull the row instead of
racing on a local insert that the next pull would clobber.

- New `bootstrapSpaceSingletons(spaceId, ownerUserId, syncSql)` in
  services/mana-auth/src/services/bootstrap-singletons.ts; shared
  `buildFieldMeta` helper extracted.
- `createBetterAuth(databaseUrl, syncDatabaseUrl, webauthn)` now takes
  the sync-DB URL and lazy-creates a module-scoped postgres pool for
  the bootstrap inserts.
- Hook into `databaseHooks.user.create.after` (only on `created: true`
  from createPersonalSpaceFor) and `organizationHooks.afterCreateOrganization`.
- Webapp `kontextStore.ensureDoc()` made private as `getOrCreateLocalDoc()` —
  same fallback role as userContextStore's after F5. Public API is now just
  setContent + appendContent.

Plan: docs/plans/sync-field-meta-overhaul.md (F4-fu row in Shipping Log).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:21:31 +02:00
Till JS
bcf150ea16 i18n(credits): wire ListView to namespace — 25 strings cleared
Patches all toast/error messages, balance labels, tabs, subscription
status/details, billing interval toggle, plan rows, invoices,
transaction table, package cards, costs filters/info-banner.
Locale-aware Date/number formatting via get(locale) ?? 'de'.
APP_LABELS + getCategoryLabel routed through namespace keys.
Locale JSONs landed in da330f0c7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:20:34 +02:00
Till JS
da330f0c7a i18n(credits): extend namespace JSONs with list_view sub-namespace
Locale-only — component patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:17:36 +02:00
Till JS
6d1546975f i18n(website): wire components + views to namespace — 68 strings cleared
Patches ListView, EditorView, SubmissionsView, BlockInspector,
ImageInspector, GalleryInspector, InsertPalette, PageList,
PublishBar, RollbackDialog, SiteSettingsDialog, DomainsSection,
TemplatePicker. Locale JSONs landed in 9e9f5ce64.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:14:16 +02:00
Till JS
9e9f5ce641 i18n(website): add namespace JSONs for de/en/es/fr/it
Locale files only — component patches in follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:07:05 +02:00
Till JS
98d07a8d48 docs(claude): document sync field-meta overhaul (F1-F7) in CLAUDE.md + DATA_LAYER_AUDIT
apps/mana/CLAUDE.md:
- Data-flow diagram updated: __fieldMeta + _updatedAtIndex + origin
  replace the older __fieldTimestamps / __fieldActors / __lastActor trio.
- New "Conflict-Detection" sub-section in §Data Layer summarizes the
  four moving parts (origin-gating, derived updatedAt, server-side
  singleton bootstrap, stable client_id) with a "use this" cheatsheet
  for the patterns you'll reach for when writing new module code.

DATA_LAYER_AUDIT.md:
- Eckdaten line points at v53/v54 instead of "v9 added updatedAt
  indexes". Conflict-Resolution bullet says "Origin-gated Field-Level
  LWW via __fieldMeta" (was: __fieldTimestamps).
- New "Sync Field-Meta Overhaul (2026-04-26, F1-F7 SHIPPED)" sub-section
  with one paragraph per phase + commit hash + the four bug-roots that
  were closed.
- Punkt 15 (Conflict-Visualisierung) flipped from "🟢 Backlog" to "
  Sprint 4+ Backlog C shipped, F2 origin-gated the trigger so only
  real user edits surface".

Future sessions reading the repo cold get the post-overhaul architecture
from these two files instead of having to chase the plan + commit log.

Closes Punkt 10 of the F1-F7 follow-up audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:06:05 +02:00
Till JS
899fccd455 i18n(uload): wire components + routes to namespace — 67 strings cleared
Patches ListView, DetailView, /uload root page, /uload/links,
/uload/analytics/[id], /uload/settings, /uload/tags. Locale JSONs
landed in 812f3f7fa.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:03:00 +02:00
Till JS
119cd2cf83 chore(boot): sweep orphan migration flags from localStorage
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>
2026-04-27 01:01:35 +02:00
Till JS
515de79c8b refactor(stores): replace Record<string,unknown> declarations with Partial<LocalX>
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>
2026-04-27 00:58:01 +02:00
Till JS
812f3f7fa0 i18n(uload): extend namespace JSONs for routes/views
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>
2026-04-27 00:55:48 +02:00
Till JS
3673542f82 feat(mana-web): inject PUBLIC_MANA_ANALYTICS_URL for client-side feedback calls
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>
2026-04-27 00:55:40 +02:00
Till JS
1334bd3626 refactor(stores): drop remaining \as never\ + \as Record<string,unknown>\ casts
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>
2026-04-27 00:53:41 +02:00
Till JS
722fe74ced i18n(stretch): wire components to namespace — 34 strings cleared
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>
2026-04-27 00:49:23 +02:00
Till JS
421663ba3d i18n(stretch): add namespace JSONs for de/en/es/fr/it
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>
2026-04-27 00:30:17 +02:00
Till JS
a68933bff3 refactor(stores): drop \as never\ Dexie-update casts
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>
2026-04-27 00:29:51 +02:00
Till JS
576fe79269 fix(community): clear svelte-check warnings for fail-on-warnings push gate
Reactive-state-locally + a11y-noninteractive-tabindex/element warnings.
No behavior changes — props stay reactive via destructure-from-$props
pattern, ignore-comments document intentional a11y exceptions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 00:25:35 +02:00
Till JS
5959f66387 i18n(wardrobe): translate all 5 locales — 36 strings
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>
2026-04-27 00:12:18 +02:00
Till JS
8804a20a7f feat(community): public anon hub — module + inline + admin + onboarding
Macht @mana/feedback omnipräsent + öffentlich. Phase 2 vom
Public-Community-Hub-Plan (docs/plans/feedback-hub-public.md).

Inline-Touchpoints:
- FeedbackHook: Lightbulb-Button, opens FeedbackQuickModal vorausgefüllt
  mit module-context. Auto-injected in jeder ModuleShell-Header
  (window-actions row), opt-out via hideFeedback prop.
- GlobalFeedbackPill: Floating "Idee?"-Pill bottom-right, self-hides
  auf /onboarding, /feedback, /community, und für Gäste. Auto-detected
  module-context aus URL bzw. ?app=-Param.
- FeedbackQuickModal: 3-Klick-Submit mit Category-Dropdown, Public-
  Toggle, "Sichtbar als {Pseudonym}"-Confirm-State.

Community-Modul (eigenes Modul, in Workbench drop-bar):
- module.config.ts (server-only, keine Sync-Tabellen)
- queries.ts: useCommunityFeed + useCommunityItem mit auth-aware Switch
  zwischen public + auth-enriched Endpoints
- ListView/DetailView/RoadmapView mit ItemCard-Component
- App-Registry-Eintrag (Megaphone-Icon, #F59E0B)

Public-Mirror-Routes (kein AuthGate):
- /community            — Feed mit SSR-Pre-Render via Public-Endpoint
- /community/[id]       — Single item + replies, SSR
- /community/roadmap    — Kanban Submitted/Planned/InProgress/Completed
- /community/admin      — Founder-only Triage (Status, AdminResponse,
                         visibility-Toggle); Client-side role-gate
                         redirect → /community.
SEO: <svelte:head> mit title/description, <noscript>-Fallback,
Cache-Headers stale-while-revalidate.

API:
- web's lib/api/feedback.ts pointed an die echte mana-analytics-URL
  (3064 dev) statt mana-auth. Neuer publicFeedbackService für
  unauthenticated SSR.
- getManaAnalyticsUrl() in lib/api/config.ts.

Onboarding-Wish public-by-default:
- Disclosure-Text: "Erscheint in Community-Page als Tier-Pseudonym".
- Toggle "Öffentlich teilen" / "Nur für Admins" mit Default on.
- Submitted-Confirm zeigt das generierte Display-Name.

Plan-Doc-Updates:
- feedback-hub.md Phase 2 abgespeckt → Verweis auf feedback-hub-public.md
- feedback-hub-public.md komplett: Architektur-Optionen A-E, Phase 2.x,
  Phase 3 Roadmap (16 Future-Features), Risiken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 00:02:25 +02:00
Till JS
2a8e8ff98f feat(sync): F7 — drop repair-silent-twin + legacy-avatar migrations
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>
2026-04-26 23:52:58 +02:00
Till JS
a031493fec feat(sync): F6 — stable client_id in Dexie, localStorage as cache
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>
2026-04-26 23:50:55 +02:00
Till JS
d78f57c041 feat(sync): F5 — drop public userContextStore.ensureDoc()
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>
2026-04-26 23:47:53 +02:00
Till JS
c7d80e3423 feat(events): full i18n coverage across 12 files — DE/EN/ES/FR/IT
Events (party/RSVP) module had ~38 hardcoded German strings across
DetailView (13 incl. share row + map labels), SourceManager (5),
DiscoveredEventCard (3), DiscoveryTab (3), EventCard (3), RsvpSummary
(3), ListView (3), BringListEditor (2), GuestListEditor (2),
PublicRsvpList (2), DiscoverySetup (2), RegionPicker (1).

New `events` namespace with 119 keys × 5 locales:
- `list_view.*`, `detail_view.*` (incl. share/publish/map sections),
  `event_card.*` (status badges + summary), `discovered_card.*`,
  `discovery_tab.*`, `discovery_setup.*`, `region_picker.*`,
  `source_manager.*` (incl. errors_count + last_scan), `bring_list_editor.*`,
  `guest_list_editor.*` (RSVP options), `public_rsvp_list.*` (status
  labels + meta), `rsvp_summary.*` (yes/maybe/no/pending labels).
- SourceManager.formatDate now uses get(locale) instead of hardcoded
  'de-DE' for last-scan timestamps.

- Baseline ratchet: 1640 → 1602 (38 strings cleared)
- validate:i18n-parity: 42 namespaces × 5 locales — 4100 keys aligned
- svelte-check: no new errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 23:20:09 +02:00
Till JS
6bb9d77be9 feat(sync): F3 — drop updatedAt as a synced data field
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>
2026-04-26 23:12:22 +02:00
Till JS
e2676252d3 feat(writing): full i18n coverage across 10 files — DE/EN/ES/FR/IT
Writing (Ghostwriter) module had ~47 hardcoded German strings across
BriefingForm (11), RefinementPanel (12), DetailView (7), StylesView (4),
ReferencePicker (4), ListView (3), StyleForm (2), VersionHistory (2),
ExportMenu (1), VersionEditor (1).

New `writing` namespace with 213 keys × 5 locales:
- `kinds.*` (12 draft kinds), `statuses.*`, `generation_statuses.*`,
  `tones.*` (10 presets), `style_sources.*` — Svelte components now use
  these instead of `KIND_LABELS[k].de` / `STATUS_LABELS[s].de` /
  `STYLE_SOURCE_LABELS[s].de` / `TONE_PRESETS[i].de`. Constants stay as
  static maps for non-Svelte callers (prompt builders, AI tools).
- `briefing_form.*`, `refinement_panel.*`, `selection_tools.*`,
  `detail_view.*` (incl. published-target chips, share row, version
  label, generate/checkpoint buttons, undo), `list_view.*` (hero +
  quick-start template), `styles_view.*`, `style_form.*`,
  `version_history.*` (token-usage line), `version_editor.*`,
  `export_menu.*`, `reference_picker.*` (7 source kinds).

- Baseline ratchet: 1687 → 1640 (47 strings cleared, 10 files fully clean)
- validate:i18n-parity: 41 namespaces × 5 locales — 3981 keys aligned
- svelte-check: no new errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 22:34:49 +02:00
Till JS
4e31c8d736 feat(calendar): full i18n coverage across 17 files — DE/EN/ES/FR/IT
Calendar already had a `calendar` namespace, but ~70 strings were
hardcoded across EventForm, EventDetailModal, CustomRecurrenceBuilder,
CalendarHeader (15 block-type filter chips), QuickEventPopover, AgendaView,
EventCard, SlotSuggestions, MiniCalendar, DateStrip, ListView,
SharedEventView, the inline DetailView, and 3 routes.

- Extended namespace with `event_form.*`, `event_card.*`, `event_modal.*`,
  `agenda.*`, `recurrence.*` (custom builder + preview format),
  `weekday_short.*` / `weekday_long.*`, `header.*` (15 block-type labels +
  4 ARIA), `date_strip.*`, `mini_cal.*`, `slots.*`, `quick_event.*`,
  `list_view.*`, `detail_route.*`, `detail_view.*`, `calendars_route.*`,
  `shared_view.*` — ~172 new keys × 5 locales = ~860 translations.
- Recurrence preview formatters in EventForm + EventDetailModal +
  CustomRecurrenceBuilder all rebuilt around `recurrence.every_n_unit` /
  `weekly_with_days` / weekday-short maps.
- Locale-aware Intl.DateTimeFormat in SharedEventView (was hardcoded
  'de-DE').
- Baseline ratchet: 1753 → 1687 (66 calendar strings cleared, 16 files
  fully clean).

- validate:i18n-parity: 40 namespaces × 5 locales — 3768 keys aligned
- svelte-check: 0 new errors from i18n changes (pre-existing drift
  in unrelated modules unchanged)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 22:17:34 +02:00
Till JS
723a64808c feat(lasts,firsts): register apps in web-internal registry mit DE-Namen
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>
2026-04-26 22:11:22 +02:00
Till JS
1398d76b41 refactor(lasts,firsts): German display names — "Letzte Male" / "Erste Male"
"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>
2026-04-26 21:58:31 +02:00
Till JS
679fb160c2 feat(invoices): full i18n coverage across 12 files — DE/EN/ES/FR/IT
Invoices module had 81 hardcoded German strings across ListView,
DetailView, InvoiceForm, SenderProfileForm, ClientPicker, LinesEditor,
SendModal, StatusBadge, the open-invoices widget, and 4 routes. New
`invoices` namespace (~215 keys × 5 locales = ~1075 translations) covers
list/detail/form/picker/sender-form/send-modal + Swiss + German VAT-rate
labels.

- constants.ts: STATUS_LABELS still kept as a literal map for non-Svelte
  callers (mail-template, PDF renderer); Svelte components now use
  `$_('invoices.status.<status>')`. VAT_RATES_CH/DE switched from
  literal `label` to `i18nKey`, resolved per-component via $_.
- Locale-aware Date.toLocaleString in DetailView meta + SenderProfileForm
  saved-at timestamp (was hardcoded 'de-DE'/default).
- Baseline ratchet: 1817 → 1753 (64 invoices strings + a handful of
  SettingsSidebar follow-ons cleared).

- validate:i18n-parity: 40 namespaces × 5 locales — 3596 keys aligned
- svelte-check: 7647 files, 0 errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:56:48 +02:00
Till JS
e5cd98936f feat(onboarding): card redesign + add wish step routing to feedback hub
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>
2026-04-26 21:52:52 +02:00
Till JS
bf3bca268a feat(lasts): M1-M7 — module ship + Meilensteine-Aggregator
Mirror sibling to firsts: das *letzte* Mal, das du etwas getan hast —
markiert oder rückwirkend erkannt. Plan: docs/plans/lasts-module.md.

M1 Skelett — Dexie v51 lasts-Tabelle, Encryption-Registry, Per-Space-
Welcome-Seed, Empty-State ListView. Kategorien aus firsts/types.ts
nach \$lib/data/milestones/categories.ts extrahiert (Re-Exports halten
firsts-API stabil).

M2 CRUD + DetailView — StatusTabs (Vermutet/Bestätigt/Aufgehoben),
Quick-Add mit Mode-Toggle, always-editable DetailView mit Lifecycle-
Buttons (Bestätigen, Aufheben mit Inline-Note), 44 i18n-Keys × 5 Locales.

M3 Inbox + Inferenz — Dexie v52 lastsCooldown (12-Monate-Cooldown,
deterministische ID), Source-Registry-Pattern in inference/, places-
Source mit Heuristik visitCount>=5 Span>=180d Silence>=365d. InboxView
mit Akzeptieren/Verwerfen + manueller Scan. contacts/habits → M3.b
sobald jeweilige Frequenz-Felder existieren.

M4 AI-Tools — 5 Tools im AI_TOOL_CATALOG (create_last, confirm_last,
reclaim_last, list_lasts, suggest_lasts), Webapp-Executor mit Vault-
Locked-Handling. Server-Drift-Test 4/4, Schema-Test 6/6.

M5 Reminders + Settings — Pivot zu In-App-DueBanner statt OS-Push (kein
PWA-Push-System im Repo). Pure date-math (12 Vitest cases), Settings-
Store mit 4 Toggles, DueBanner mit max-N rendering, Test-Banner-Knopf.

M6 Visibility + Unlisted-Sharing — VisibilityPicker + SharedLinkControls
in DetailView, buildLastBlob mit reflective-core whitelist (reclaimed
Lasts gehärtet ausgeblockt), SharedLastView public-render, Share-
Dispatcher kennt 'lasts'.

M7 Meilensteine-Aggregator — Cross-modul firsts vereinigt mit lasts
Timeline + Year-Recap. Pure aggregator (mergeMilestones,
buildMilestonesRecap), 12 Vitest cases. /milestones und
/milestones/recap/[year] Routes, Cross-Link in lasts/ListView.

Validation: 0 errors / 0 warnings (svelte-check 7645 files), 24/24
tests, i18n-parity 39x5 aligned (+2 namespaces), i18n-keys baseline-
equal, crypto 211 tables.

LOCAL TIER PATCH: lasts ist 'guest' für Testing — vor Release auf
'beta' setzen (packages/shared-branding/src/mana-apps.ts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:40:29 +02:00
Till JS
ad5e04a554 feat(sync): F2 — origin-gated conflict-detection
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>
2026-04-26 21:38:56 +02:00