Mirror of github.com/Memo-2023/mana-monorepo
Find a file
Till JS ed8ab44832 feat(sync): conflict visualization with restore-my-version toast
Closes backlog C from the Phase 9 audit. The data layer has had
real field-level LWW since Sprint 1, but when the server's value
beat a local edit, the user had no way to know. This commit adds
the missing UI piece: a toast that appears whenever applyServerChanges
overwrites a non-empty local field with a strictly newer server
value, with a one-click "restore my version" path.

sync.ts — detection
-------------------
Two new exports:

  - SyncConflictPayload: per-field overwrite event shape
    (tableName, recordId, field, wasLocal, nowServer, localTime,
    serverTime).
  - subscribeSyncConflicts(listener): in-module pub/sub. Returns
    an unsubscribe function.

Both LWW branches in applyServerChanges (insert-as-update and the
canonical update-with-fields path) now call notifyConflict() when:

  1. The server time is STRICTLY greater (not equal) than the local
     field time → there's actually an edit window to lose
  2. The local field value is non-null/undefined → user actually
     typed something to overwrite
  3. The values are not equal (cheap JSON-string compare for objects,
     === for primitives) → there's a real change, not an idempotent
     server replay

Why a custom registry instead of CustomEvent + window.dispatchEvent?
The existing sync-telemetry + quota-detect helpers use
window.dispatchEvent which doesn't work in node-based vitest envs
(no DOM EventTarget). The conflict bus is small enough that a plain
Set<listener> is simpler than polyfilling EventTarget — and the
node test path matters because we need automated coverage of the
detection logic.

conflict-store.svelte.ts — UI state
-----------------------------------
Svelte 5 $state-backed store with three responsibilities:

  1. Coalescing: a SyncConflict is keyed by `${tableName}|${recordId}`,
     so a burst of N field-overwrites on the same record collapses
     into ONE toast with all affected fields underneath. The original
     wasLocal value is preserved across coalescing (we don't clobber
     the user's first typed value if a later field event arrives).

  2. Auto-dismiss: each conflict has a 30s TTL after which it
     evicts itself. Manual dismiss trumps the timer.

  3. Restore: writes wasLocal back to Dexie with a fresh updatedAt
     that beats the server's serverTime, plus a __fieldTimestamps
     patch so the field-LWW pass on the next sync round will let
     our value win. Deferred via setTimeout(0) so it lands AFTER
     applyServerChanges releases its per-table apply lock — running
     before the lock release would silently drop the restore (the
     hook suppression is per-table-set, not per-record).

FIFO eviction at MAX_VISIBLE=8 keeps a bursty server from growing
the visible array unbounded.

SyncConflictToast.svelte — the UI
---------------------------------
Mounts globally in +layout.svelte. Stacks bottom-right above the
OfflineIndicator. Each toast shows:

  - Module label ("Aufgabe", "Notiz", "Termin", …) derived from a
    table-name → German label map. Unknown tables fall through to
    the bare table name.
  - Field count summary ("Feld »title«" / "3 Felder") — we
    deliberately do NOT render the actual values because some are
    encrypted blobs and decrypting them in the toast would be
    significant complexity for marginal UX gain. The user knows
    what they were just editing.
  - Two buttons: "Wiederherstellen" (calls conflictStore.restore)
    and "Behalten" (calls dismiss).

Slide-in animation, dark-mode-aware styling, role="alertdialog"
for accessibility.

Wiring
------
data-layer-listeners.ts:
  - Imports installConflictListener from conflict-store
  - Calls it from installDataLayerListeners() right after the
    quota + telemetry handlers
  - Adds the disposeConflict() call to the cleanup return

+layout.svelte:
  - Imports SyncConflictToast and mounts it next to SuggestionToast
    so it inherits the same global-overlay positioning context

Tests
-----
Five new integration tests in sync.test.ts cover:

  - Fires when server overwrites a non-empty local field with a
    strictly newer value
  - Does NOT fire when local field is null/undefined (no edit to lose)
  - Does NOT fire when values are equal (idempotent replay)
  - Fires once per overwritten field on a multi-field update
  - Does NOT fire on a timestamp tie (LWW lets server win silently
    when there's no real edit window)

All 25 sync tests + 138 total data-layer tests pass. The new
captureConflicts() helper subscribes via subscribeSyncConflicts()
which works in the node-vitest env without needing a DOM polyfill.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:01:17 +02:00
.changeset feat(versioning): add semantic versioning and changesets to all apps 2026-03-19 16:20:18 +01:00
.claude feat(manacore/web): wire TagField, FavoriteButton, ColorPicker into module UIs 2026-04-02 17:20:46 +02:00
.github chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
.husky fix(devtools): fix pre-commit hook - add eslint-config dep, remove type-check 2026-03-17 13:08:51 +01:00
apps feat(sync): conflict visualization with restore-my-version toast 2026-04-08 12:01:17 +02:00
docker chore(macmini): infra cleanup — compose env, blackbox mem, prometheus gpu probes 2026-04-07 22:59:38 +02:00
docs docs: PRE_LAUNCH_CLEANUP.md — what we removed before launch and why 2026-04-07 22:32:14 +02:00
games chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
load-tests chore: rename mukke to music in infra, scripts, and CI/CD 2026-04-05 16:47:57 +02:00
NewAppIdeas/Roblox Reimagined chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
packages feat(cycles): add menstrual cycle tracking module 2026-04-07 14:35:33 +02:00
patches fix(traces): configure EAS Build for TestFlight and fix bot-services build 2026-03-17 13:16:38 +01:00
scripts chore(macmini/scripts): runbook hardening — status diff + ingress walk 2026-04-07 22:31:53 +02:00
services fix(mana-video-gen): typo in get_model_info — total_mem → total_memory 2026-04-08 11:59:40 +02:00
tests/e2e first implementation 2025-11-27 17:26:18 +01:00
.dockerignore make auth working 2025-11-26 01:31:12 +01:00
.editorconfig feat: add monorepo configuration and shared packages structure 2025-11-22 23:41:52 +01:00
.env.development feat(memoro): voice recording → mana-stt transcription pipeline 2026-04-07 18:48:41 +02:00
.env.macmini.example docs: Phase 9 documentation roundup — close encryption-shaped doc gaps 2026-04-08 11:47:59 +02:00
.gitignore chore: misc fixes, new services, lockfile cleanup 2026-03-28 10:27:35 +01:00
.npmrc fix(monorepo): add .npmrc with node-linker=hoisted for EAS Build compatibility 2026-03-15 08:50:18 +01:00
.nvmrc feat: add monorepo configuration and shared packages structure 2025-11-22 23:41:52 +01:00
.prettierignore chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
.prettierrc.json fix(cicd): docker paths, formatting config, 2025-11-27 18:33:08 +01:00
CLAUDE.md docs: trim CLAUDE.md files — remove stale + duplicated guidance 2026-04-08 11:59:51 +02:00
cloudflared-config.yml feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
docker-compose.dev.yml feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
docker-compose.macmini.yml fix(macmini): blackbox-exporter uses 1.1.1.1/8.8.8.8 directly for DNS 2026-04-07 23:47:57 +02:00
eslint.config.mjs chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
gift-codes-2026-02-14.txt feat(gifts): add gift code creation script and initial codes 2026-02-14 11:23:08 +01:00
lint-staged.config.js chore: archive 17 standalone app servers (replaced by unified API) 2026-04-02 21:37:45 +02:00
mac-mini-setup.sh feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
package.json feat(events): add mana-events service + public RSVP flow (Phase 1b) 2026-04-07 14:27:48 +02:00
playwright.config.ts style: auto-format codebase with Prettier 2025-11-27 18:33:16 +01:00
pnpm-lock.yaml chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
pnpm-workspace.yaml chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00
README.md chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
test-chat-auth.sh feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
TROUBLESHOOTING.md chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
turbo.json feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
vitest.config.ts feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00

Mana Monorepo

Monorepo containing all Mana projects — a self-hosted multi-app ecosystem with shared packages and unified tooling.

Projects

Project Description Apps
mana Multi-app ecosystem platform Expo mobile, SvelteKit web
chat AI chat application NestJS backend, Expo mobile, SvelteKit web, Astro landing
todo Task management NestJS backend, SvelteKit web, Astro landing
calendar Calendar & scheduling NestJS backend, SvelteKit web, Astro landing
clock Pomodoro & time tracking NestJS backend, SvelteKit web, Astro landing
contacts Contact management NestJS backend, SvelteKit web
picture AI image generation NestJS backend, Expo mobile, SvelteKit web, Astro landing
cards Card/deck management NestJS backend, Expo mobile, SvelteKit web
zitare Daily inspiration quotes NestJS backend, Expo mobile, SvelteKit web, Astro landing
mukke Music player NestJS backend, SvelteKit web
planta Plant care tracker NestJS backend, SvelteKit web
storage Cloud storage NestJS backend, SvelteKit web
questions Q&A with web search SvelteKit web
skilltree Skill tree visualization NestJS backend, SvelteKit web
nutriphi Nutrition tracking NestJS backend, SvelteKit web
citycorners City guide NestJS backend, SvelteKit web, Astro landing
presi Presentation tool NestJS backend, SvelteKit web
photos Photo management NestJS backend, SvelteKit web

Getting Started

Prerequisites

  • Node.js 20+
  • pnpm 9.15.0+
  • Docker (for PostgreSQL, Redis, MinIO)

Installation

pnpm install

Development

# Start infrastructure (PostgreSQL, Redis, MinIO)
pnpm docker:up

# Start any app with auto DB setup
pnpm dev:chat:full
pnpm dev:todo:full
pnpm dev:calendar:full
pnpm dev:contacts:full

# Build & quality
pnpm run build
pnpm run type-check
pnpm run format

See CLAUDE.md for comprehensive development documentation.

Architecture

mana-monorepo/
├── apps/                    # Product applications
├── services/                # Microservices (auth, search, LLM, bots)
├── packages/                # Shared packages
├── docker/                  # Docker configuration
└── scripts/                 # Development & deployment scripts

Tooling

  • Package Manager: pnpm 9.15.0
  • Build System: Turborepo
  • Formatting: Prettier (tabs, single quotes, 100 char width)
  • Hosting: Mac Mini (self-hosted) via Docker + Cloudflare Tunnel
  • Analytics: Umami (stats.mana.how)

License

Private - All rights reserved