Commit graph

2 commits

Author SHA1 Message Date
Till JS
ae04c9e194 chore(mana): citycorners + food + wardrobe aus unified-App entfernen
Citycorners-Reste vom vorherigen Sprint mit committet. food → Nutriphi,
wardrobe → Werdrobe sind als Standalone-Apps live; die mana.how-unified-
App trägt die Modul-Surfaces nicht mehr.

Gelöscht / abgebaut:
- Module: apps/mana/.../modules/{food,wardrobe} + Routen + Locales
- Landing-Apps: apps/{food,citycorners}/ Top-Level
- Backend: apps/api/src/modules/{food,wardrobe} + MCP-Tools log_meal /
  nutrition_summary, picture-routes verifyMediaOwnership-Allowlist
- shared-branding: APP_BRANDING, APP_ICONS, MANA_APPS, Logos, Onboarding
- shared-ai, mana-tool-registry, credits, shared-types/spaces,
  shared-utils/analytics, spiral-db/MANA_APP_INDEX, website-blocks
- Cross-Module: Body-CalorieWeightChart, Comic-CharacterPicker-Wardrobe,
  website-Embed wardrobe.outfits, DaySnapshot.nutrition, FoodEventType,
  MealLogged/Meal*-Streaks/Goals/Companion/Trigger, AI-Agent-Policy,
  GoalEditor MealLogged, MyDay/RitualRunner/Rules nutrition-Refs,
  Crypto-Registry meals/wardrobeGarments/wardrobeOutfits
- Generic: PlaceCategory 'food' (places + geocoding + Locales),
  spaces.ts 'food'/'wardrobe' Modul-IDs
- Infrastruktur: cloudflared, docker-compose CORS, nginx-Landing,
  prometheus-Probe, load-tests, package.json dev-Scripts,
  generate-env, mac-mini/build-landings, dependabot

Dexie v62 Migration:
- droppt meals, goals, foodFavorites, mealTags, wardrobeGarments,
  wardrobeOutfits Tabellen
- entfernt wardrobeOutfitId / wardrobeGarmentId aus images-Index
- Upgrade-Callback strippt die toten FK-Properties aus alten image-Rows

Test/Doku:
- module-registry.test.ts: Snapshot refresht auf aktuellen Stand mit
  56 Modulen (vorher 32, statisch eingefroren pre-refactor). Plus
  LEGACY_TABLES-Exclusion für nicht-mehr-registrierte Tabellen aus
  cards/citycorners/moodlit/rituals/wishes/who.
- streaks.test.ts: MealLogged-Test in TaskCompleted-Test umgebaut
- apps/mana/CLAUDE.md: food-Refs in AI-Tool-Tabelle und
  AiProposalInbox-Liste entfernt
- validate-i18n-keys.mjs + validate-no-recursive-turbo.mjs:
  existsSync-Guard, damit die Skripte mit gestaged-aber-rm'ten Dateien
  klarkommen

mana-web svelte-check 0 errors / 7436 files, betroffene Tests grün
(streaks, dashboard, module-registry), validate:pg-schema,
validate:turbo, validate:i18n-parity grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 12:47:33 +02:00
Till JS
fbbadc91f0 feat(calendar): M8.3 — calendar pilot for unlisted-share end-to-end
Wires Calendar through the M8.1+M8.2 backbone: flipping an event to
'unlisted' now publishes a server-side snapshot, the visible link in
the DetailView/EventDetailModal opens a real /share/[token] page, and
recipients can download an .ics file for their own calendar.

Changes:
- lib/data/unlisted/resolvers.ts (new):
    buildUnlistedBlob(collection, recordId) dispatcher.
    buildEventBlob: load LocalEvent + linked TimeBlock, decrypt
    client-side, return { title, location, startTime, endTime,
    isAllDay, timezone }. Description, reminders, tagIds, calendarId,
    color stay out of the blob — sensitive context the user didn't
    consent to share by flipping a single flag.
- modules/calendar/types: CalendarEvent gains `unlistedToken: string`
  (empty string when no active token). timeBlockToCalendarEvent
  forwards from LocalEvent. Draft-event scaffold initializes empty.
- modules/calendar/stores/events:
    setVisibility now coordinates with mana-api. Flip-to-unlisted:
      build blob -> publishUnlistedSnapshot -> store server-issued
      token in patch.unlistedToken -> commit local update. If the
      server call fails, no local change happens (no drift).
    Flip-from-unlisted: revoke server snapshot first, then clear
      local token + commit visibility change.
    deleteEvent: revoke active unlisted snapshot before tombstoning,
      so the share-link dies in lock-step with the local delete.
    updateEvent + updateSingleInstance fire-and-forget
      refreshUnlistedSnapshot(id) so the published blob tracks any
      whitelist-field edits. Failures log; the next successful
      refresh heals.
    New regenerateUnlistedToken(id): revoke + republish in one call,
      returns the fresh token. Powers the "Neu erzeugen" UI.
- routes/share/[token]/+layout.svelte: minimal anonymous chrome —
  no app nav, no auth, no Dexie. Light/dark via prefers-color-scheme.
  Footer carries "Geteilt via Mana" + signup CTA.
- routes/share/[token]/+page.server.ts: SSR loader. Fetches
  /api/v1/unlisted/public/:token, dispatches 404/410 cleanly,
  sets Cache-Control: private, max-age=60 + X-Robots-Tag: noindex.
- routes/share/[token]/+page.svelte: dispatcher; renders
  SharedEventView for collection='events', stub message otherwise.
- modules/calendar/SharedEventView.svelte: standalone public render —
  big date, location, "Zum eigenen Kalender hinzufügen" .ics link,
  optional expiry note. OG/Twitter meta tags for WhatsApp/Slack
  preview embedding. Uses $derived everywhere so prop updates
  propagate through reactive recompute.
- routes/share/[token]/ical/+server.ts: RFC 5545 builder. No npm
  library — small enough to inline. Escapes per spec, CRLF endings,
  DTSTART/DTEND swap between VALUE=DATE and UTC depending on isAllDay.
  Wrong-collection requests get 400.
- modules/calendar/views/DetailView (Workbench) + components/
  EventDetailModal (/calendar route): SharedLinkControls dropped in
  below the visibility row when event.visibility === 'unlisted'
  AND event.unlistedToken AND shareUrl computed. The URL is built
  client-side via buildShareUrl(window.location.origin, token) so it
  stays in sync with whichever host the editor is open on.

Verified:
- pnpm check (web): 7541 files, 0 errors, 0 warnings
- pnpm test calendar + website: 26/26
- typecheck of new resolver, store hooks, SSR loader, iCal builder

Manual test path:
1. Open /calendar event in Detail view, flip Sichtbarkeit -> "Per Link"
2. Server publishes snapshot, Dexie record gets the server token
3. SharedLinkControls appear with copy + regenerate + revoke buttons
4. Open the URL in incognito → SSR fetches snapshot, renders
   SharedEventView with date / location / .ics download
5. Edit the event title back in the main app → snapshot auto-refreshes
   (refreshUnlistedSnapshot fires after updateEvent succeeds)
6. Flip back to "Bereich" → snapshot revoked server-side; subsequent
   incognito reloads return 410 Gone

Next: M8.4 — same wiring for Library + Places. Uses the same
infra (resolvers dispatcher, share dispatcher) — just adds two new
buildXBlob functions, two SharedXView components, and the store
hooks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 11:40:53 +02:00