Commit graph

499 commits

Author SHA1 Message Date
Till JS
45958ad885 feat(mana/web): global requireAuth() gate for guest-blocked features
The unified Mana app runs most modules in a "guest mode": you can
open a module, look around, type a quick note, etc. without an
account. But anything that touches an *encrypted* table (dreams
voice capture, memoro recordings, notes, todo, calendar events, …)
needs the user to be logged in — the encryption vault only unlocks
against a Mana Auth session, and writing to those tables without
it throws `VaultLockedError` at the very last step of the action.

Before this commit, every entry point into an encryption-required
action would silently let the guest go through the whole flow
(record audio, wait for transcription, open the dexie write) and
then explode with a stack-trace error. The user lost work and
didn't know why. The dreams voice capture flow surfaced this
during the 2026-04-08 STT debugging session.

The fix is a global imperative gate: `requireAuth({ feature, reason })`.
Call sites await it before the action; it returns immediately if the
user is already authenticated, otherwise pops a global modal that
asks the guest to log in or cancel. Promise-based, so callers
decide what to do with `false` (silent abort, restore state, own
toast).

  $lib/auth/require-auth.svelte.ts          new — store + helper
  $lib/components/auth/AuthRequiredModal.svelte  new — global modal
  routes/+layout.svelte                     mount the modal once
  packages/shared-utils/src/analytics.ts    new ManaEvents.featureBlockedByAuth
                                            event for conversion tracking

Wired into the two voice-capture entry points that actually exhibited
the bug:

  modules/dreams/ListView.svelte  → feature: 'dreams-voice-capture'
  routes/(app)/memoro/+page.svelte → feature: 'memoro-voice-capture'

Both gate on `requireAuth()` BEFORE the mic permission request, so
guests see the friendly "Konto erforderlich" modal instead of
recording → transcribing → crashing.

Design choices documented in detail in the require-auth.svelte.ts
header comment:
  - Imperative function (not a button wrapper component) so it
    works in event handlers, store actions, keyboard shortcuts,
    drag-drop handlers — anywhere async code runs.
  - Single global modal mounted once in the root layout, no
    portal/z-index gymnastics; two simultaneous prompts replace
    each other (the most recent one wins).
  - Checks `authStore.isAuthenticated`, not vault-unlocked state —
    the user-facing concept is "I need an account", not "I need
    a working encryption vault". Vault-unlock failures (network
    error etc.) are a separate bug class with their own UX.
  - The modal navigates to `/login?next=<current path>` so the
    user lands back on the same page after logging in. The
    Promise resolves `false` on navigation; the user re-clicks
    the original button after coming back, and the second click
    sees `isAuthenticated === true` and proceeds without a modal.
    Re-triggering the original action across a navigation cycle
    would require restoring half-recorded mic state — not worth
    the complexity, and the second click is a clean UX.

How to wire a new entry point (4 lines):

    import { requireAuth } from '$lib/auth/require-auth.svelte';

    async function handleCreateThing() {
      const ok = await requireAuth({
        feature: 'create-thing',
        reason: 'Things werden verschlüsselt gespeichert. Dafür brauchst du ein Mana-Konto.',
      });
      if (!ok) return;
      // ...existing logic
    }

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:36:38 +02:00
Till JS
2b4494628e fix(mana/web): unblock voice capture — permissions policy, notification mount, dev SW
Three independent bugs that conspired to make the dreams + memoro mic
buttons completely unusable in production AND in dev. Each one alone
would have been the only blocker; they layered on top of each other so
fixing the top one just exposed the next.

1. Permissions-Policy header blocked the microphone API entirely.
   `packages/shared-utils/src/security-headers.ts` set
   `microphone=()` which means "no origin, including self, may use
   the microphone". `getUserMedia()` throws a `Permissions policy
   violation` and the browser never even shows the permission
   dialog — no amount of OS / browser / site settings can override
   it because the policy blocks the API at the document level.
   Fix: change to `microphone=(self)` so mana.how itself can use
   the API. Camera stays disallowed (no module needs it).

2. Notification permission was requested at layout mount time.
   `(app)/+layout.svelte` called
   `notificationService.requestPermission()` from `onMount()`. Modern
   browsers require permission requests to come from a user gesture
   — calling it without one queues the prompt until the next click.
   That meant the user's FIRST click on any button (in this case the
   dreams "Traum sprechen" mic button) showed the queued notifications
   prompt instead of the action they actually clicked. Worse,
   `getUserMedia()` was then silently dropped because Chrome only
   shows one permission dialog at a time.
   Fix: remove the mount-time call entirely. Notification permission
   must be requested from a button the user explicitly clicks
   ("Benachrichtigungen aktivieren" toggle in Settings or first time
   a reminder is created) — the reminder scheduler still runs without
   permission, it just won't fire OS notifications until granted.

3. vite-plugin-pwa registered a service worker in dev that cached
   the old layout chunks across reloads, so the fix for #2 was
   invisible until the user manually unregistered the SW in DevTools.
   `vite-plugin-pwa` defaults `devEnabled: true`, which is a
   well-known footgun for fast iteration. Production still gets the
   full SW (this only flips dev). The 2026-04-08 mic-button hunt
   took an extra hour for exactly this reason.
   Fix: pass `devEnabled: false` to createPWAConfig in vite.config.ts.

Verified: in a fresh incognito tab on `localhost:5173/`, opening the
Dreams app in the workbench and clicking the mic button now shows the
microphone permission dialog directly (no notifications hijack), and
recording → transcription works end-to-end against the production
mana-stt service on the GPU box.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:36:03 +02:00
Till JS
abe0a21966 refactor(auth-ui): tighten LoginPage UX, a11y, and dead code
Some checks are pending
CI / Build mana-crawler (push) Blocked by required conditions
CI / Build mana-media (push) Blocked by required conditions
CI / Build mana-credits (push) Blocked by required conditions
CI / Build mana-web (push) Blocked by required conditions
CI / Build chat-backend (push) Blocked by required conditions
CI / Build chat-web (push) Blocked by required conditions
CI / Build todo-backend (push) Blocked by required conditions
CI / Build todo-web (push) Blocked by required conditions
CI / Build calendar-backend (push) Blocked by required conditions
CI / Build calendar-web (push) Blocked by required conditions
CI / Build clock-web (push) Blocked by required conditions
CI / Build contacts-backend (push) Blocked by required conditions
CI / Build contacts-web (push) Blocked by required conditions
CI / Build presi-web (push) Blocked by required conditions
CI / Build storage-backend (push) Blocked by required conditions
CI / Build storage-web (push) Blocked by required conditions
CI / Build telegram-stats-bot (push) Blocked by required conditions
CI / Build nutriphi-backend (push) Blocked by required conditions
CI / Build nutriphi-web (push) Blocked by required conditions
CI / Build skilltree-web (push) Blocked by required conditions
CI / Build mana-matrix-bot (Go) (push) Blocked by required conditions
Docker Validate / Validate Dockerfiles (push) Waiting to run
Docker Validate / Build calendar-web (push) Blocked by required conditions
Docker Validate / Build todo-backend (push) Blocked by required conditions
Docker Validate / Build todo-web (push) Blocked by required conditions
Docker Validate / Build zitare-web (push) Blocked by required conditions
Docker Validate / Build mana-auth (push) Blocked by required conditions
Docker Validate / Build mana-sync (push) Blocked by required conditions
Docker Validate / Build mana-media (push) Blocked by required conditions
Mirror to Forgejo / Push to Forgejo (push) Waiting to run
LoginPage cleanup:
- Drop dev pre-fill credentials and the secret logo-as-button trick
- Remove duplicate in-component theme toggle; accept isDark as a prop and let the (auth) layout's global theme toggle drive it
- Move passkey CTA below the password form so the primary flow stays primary
- Remove the dead "Angemeldet bleiben" checkbox (was bound but never forwarded to onSignIn)
- Fix the skip-to-form link to use sr-only/focus:not-sr-only so it only appears on keyboard focus
- Fix the "oder" divider to render its before/after hairlines by setting an explicit color on the parent
- Wire focus-visible outlines on all interactive controls
- Bump 0.6 → 0.75 opacity on subtitle text for AA contrast
- Drop opacity-60 from the headerControls wrapper

Robustness:
- Track all setTimeout IDs in a Set and clear them in an effect cleanup so navigation away doesn't fire stale callbacks (success redirects, error shake, focus restore)
- Replace (result as any) casts with the new typed AuthResult fields
- New resolveErrorCode() helper prefers result.errorCode and falls back to legacy string matching, so rate-limit / account-lock detection survives i18n
- WebAuthn Conditional UI: on mount, if PublicKeyCredential.isConditionalMediationAvailable(), call onSignInWithPasskey({ conditional: true }) so passkeys appear inline in the email autofill dropdown
- Extract the dismissible success-banner markup into a {#snippet successBanner} and reuse it for the verified / verification-sent / magic-link-sent cases (~50 lines of duplicate JSX out)

Page wrappers:
- login/+page.svelte passes isDark={theme.isDark} so the in-app theme store drives both layouts
- register/+page.svelte wraps trackGuestConversion() in queueMicrotask + try/catch so analytics can never block the success redirect
- Drop the dead baseSignupCredits={25} prop from register/+page.svelte (RegisterPage never accepted it)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:41:19 +02:00
Till JS
ff7dc5d875 feat(auth): structured error codes + conditional passkey UI
- Add AuthErrorCode union and typed twoFactorRedirect/retryAfter fields on AuthResult so the frontend can branch on stable codes instead of locale-dependent error strings.
- Extend signInWithPasskey with an optional { conditional } flag, threaded through to @simplewebauthn/browser via useBrowserAutofill, so hosts can opt into WebAuthn Conditional UI (passkey suggestions inline in the email autofill dropdown).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:40:51 +02:00
Till JS
c8ed58b7d1 fix(mana,ui): integrate guest nudge into bottom stack + theme it
The "Gefällt es dir?" guest nudge was a free-floating fixed element at
bottom: 10rem, so it didn't follow the bottom-stack when the PillNav was
collapsed. Move it inside .bottom-stack as the first child so it shares
the stack's reflow.

NotificationBar now uses the elevation system (--color-surface-elevated,
--color-border-strong, --color-foreground) instead of hardcoded rgba so
it adapts to all themes. Bumped the CTA button (shadow + hover lift) and
container (stronger border, layered shadow) to be more visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:13:05 +02:00
Till JS
fbab96c74b feat(cycles): add menstrual cycle tracking module
New unified-app module under apps/mana/apps/web/src/lib/modules/cycles.
Adds three Dexie tables (cycles, cycleDayLogs, cycleSymptoms) in db v7,
SYNC_APP_MAP entry, app-registry registration, branding (icon + entry +
APP_URLS), and a /cycles route.

Includes phase derivation (menstruation/follicular/ovulation/luteal),
heuristic next-period and fertile-window prediction (rolling mean over
last 6 cycles), 10 default symptoms, and 33 unit tests covering the
pure utilities.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:35:33 +02:00
Till JS
30022e82e1 feat(events): scaffold social events module (Phase 1a, local-only)
New 'events' module for planning gatherings with guest lists and RSVPs,
distinct from the personal calendar. Events surface in the calendar via
TimeBlock with sourceModule='events'. Guests, RSVPs and a publish stub
work fully local-first; the public RSVP server lands in Phase 1b.
2026-04-07 14:12:41 +02:00
Till JS
8e71096a61 feat(dreams): scaffold Traumtagebuch module
Adds a new Dreams module to the unified Mana app for capturing dream
journal entries with mood, lucid status, recurring symbols, and
timeline insights. Founder-tier gated for now.

- Dexie schema v5 with dreams, dreamSymbols, dreamTags
- Mutation store with auto symbol counting on create/update/delete
- ListView with quick capture, inline editor, mood picker, lucid
  toggle, monthly grouping, insights ribbon, context menu
- Workbench registration with note → dream drop transform
- New 'dream' DragType, dreams app icon, mana-apps catalog entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:07:12 +02:00
Till JS
b9fdf0802f fix(cards-database): add .js extensions to relative imports for NodeNext
Package uses moduleResolution: NodeNext which requires explicit .js extensions on relative ESM imports. Without these, prepare/build failed and broke pnpm install for the whole monorepo.

The implicit-any errors on (table) callbacks were cascading from the broken imports — they resolve once the modules import correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:01:44 +02:00
Till JS
e974761e8a chore(workspace): unify vitest to ^4.1.2 across all packages
The lockfile had grown five (!) different vitest versions over time:
1.6.1, 2.1.9, 3.2.4, 4.1.2 and 4.1.3 — pulled in by various
packages that pinned outdated majors. The mismatch produced the
classic "createDOMElementFilter not found" startup crash because
hoisted @vitest/utils@3.x was loaded by the nested @vitest/runner@4.x.

Bumped every package.json that pinned an old vitest:
- apps/manavoxel/apps/web      (^4.1.0 → ^4.1.2)
- apps/matrix/apps/web         (^4.1.0 → ^4.1.2)
- apps/memoro/apps/server      (^3.0.0 → ^4.1.2)
- apps/nutriphi/packages/shared (^2.1.8 → ^4.1.2)
- packages/qr-export           (^3.0.5 → ^4.1.2)
- packages/shared-llm          (^2.0.0 → ^4.1.2)
- packages/shared-storage      (^4.1.0 → ^4.1.2)
- packages/spiral-db           (^1.6.1 → ^4.1.2)
- packages/test-config         (^3.0.0 → ^4.1.2)
- packages/wallpaper-generator (^3.0.5 → ^4.1.2)

After a clean pnpm-lock.yaml regenerate, every @vitest/* sub-package
resolves to a single version (4.1.3, picked by semver) — no more
duplicates between hoisted and nested node_modules.

Verified by running:
  pnpm --filter @mana/web vitest run src/lib/data/sync.test.ts
  → 20/20 tests passing in 217ms
  pnpm --filter @mana/web vitest run src/lib/data/time-blocks/recurrence.test.ts
  → 19/19 tests passing in 198ms

Pre-existing test failures in base-client.test.ts (German error
strings vs english assertions), dashboard.test.ts (widget count
drift), and content/help/index.test.ts (svelte-i18n locale not
initialised in test env) are unrelated and tracked separately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:58:29 +02:00
Till JS
440f6507f1 fix: extract types from .svelte files for proper named re-exports
Svelte 5 .svelte modules only expose a default export, so 'export type { X } from "./X.svelte"' fails type-check. Move shared interfaces into adjacent .ts type files.

- shared-ui/navigation: SpotlightAction, ContentSearcher, ContentSearch{Result,Group} → types.ts
- shared-auth-ui: PasskeyManagerTranslations, TwoFactorSetupTranslations, SessionManagerTranslations → types.ts
- mana/web/page-carousel: CarouselPage → new types.ts
- mana/web: bump @vitest/* to 4.1.2 (matches lockfile)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:53:13 +02:00
Till JS
fc743a494b fix: type errors from ManaCore→Mana rename and stale templates
- shared-branding/mana-apps: drop duplicate `mana` and obsolete `inventar` URL entries
- web/app.d.ts: move __BUILD_HASH__/__BUILD_TIME__ ambient declarations into declare global so they survive module-scoping
- web: remove dead supabase template (routes/api/example, lib/server/middleware) — locals.session no longer exists post auth migration
- habits/queries: drop stale Record<string,string> cast on LocalHabit (legacy emoji field)
- shared-stores/toggle-field: cast to Dexie UpdateSpec instead of Partial<T> for newer dexie types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:42:17 +02:00
Till JS
3ffbf37ee7 fix(shared-branding): dedupe duplicate manaSvg from rename collision
The ManaCore→Mana rename converted both `manaCoreSvg` and the existing
`manaSvg` to the same identifier, leaving two `const manaSvg = ...`
declarations and two `mana:` keys in APP_ICONS. This broke any consumer
of the package with a duplicate-symbol error at SSR build time.

Removed the legacy ManaCore icon (4-circle quartet) and kept the
current Mana brand icon (single droplet). Removed the duplicate
APP_ICONS entry as well.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:39:40 +02:00
Till JS
9e0ade4c0a fix(mana/web): sprint 3 — type-safe sync protocol + tests
- New SyncChange / FieldChange / SyncOp types replace `any[]` in
  applyServerChanges. The wire format is now self-documenting and
  TypeScript catches malformed callsites at compile time.
- isValidSyncChange() validates incoming server payloads at the boundary:
  malformed entries are dropped with a single warn log, valid ones are
  applied. A bad row from the server can no longer corrupt IndexedDB.
  Hand-rolled type guards keep us free of a runtime-validation dep.
- applyServerChanges() and readFieldTimestamps() are now top-level
  exports (extracted out of createUnifiedSync's closure) so they can be
  imported directly by tests. Behaviour is unchanged — the closure
  variant inside the sync manager just resolves the module-level
  symbol now.
- New sync.test.ts covers:
  * pure isValidSyncChange and readFieldTimestamps cases
  * field-level LWW: server-newer wins, split outcome when local-newer
    on one field and server-newer on another
  * insert with __fieldTimestamps stamping
  * soft-delete LWW guard
  * malformed-entry drop with valid entries surviving
  * sync-loop guard: server-applied writes don't generate _pendingChanges
- fake-indexeddb added as devDependency for the integration tests.

Note: the monorepo's vitest install is currently tangled across mixed
@vitest/* package versions in the lockfile, so `pnpm test` fails before
reaching this file. The tests are written to pass on any vitest 4.x once
that's untangled — needs its own dedicated cleanup pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:38:23 +02:00
Till JS
ce04f43248 fix(timeblocks): type errors from recurrence migration
- calendar/types: replace duplicate recurrenceRule with recurrenceDate on CalendarEvent; map it in timeBlockToCalendarEvent
- recurrence: drop stale Record casts now that LocalTimeBlock types isRecurrenceException and recurrenceDate
- todo: route recurrenceRule through TimeBlock in createTask/updateTask, load it from block in useTaskForm; accept labelIds via metadata; remove stale projectId casts
- calendar/events: include linkedBlockId/parentBlockId/recurrenceDate in createDraftEvent
- habits: drop unused db / LocalTimeBlock imports
- eslint-config: disable consistent-type-imports (parser conflict with .svelte.ts files)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:22:59 +02:00
Till JS
28942abede fix(mana/web): sprint 2 — auth-aware data layer + guest migration
- Single source of truth for the active user via data/current-user.ts;
  layout pushes authStore.user.id into it on every auth state change.
- Dexie creating-hook auto-stamps userId from getEffectiveUserId(); the
  updating-hook strips userId from modifications so records are
  effectively user-immutable after creation.
- BaseRecord gains an optional userId so module types inherit it without
  per-module declarations. All hardcoded 'guest'/'local' fallbacks in
  module type-converters and session timer stores are deleted; the dead
  userId field is removed from the public view types where it was
  unused (Task, Conversation, Template, Deck, Plant, Contact, etc.).
- New guest-migration.ts: on first authenticated session, walks every
  sync-tracked table, deletes guest-owned records and re-adds them so
  the creating-hook re-stamps with the real user id and produces fresh
  insert pending-changes with the full payload. Stale guest pending-
  changes are cleared up-front.
- Drive-by: root onMount now returns its cleanup synchronously; the
  previous async form silently dropped the cleanup callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:07:12 +02:00
Till JS
22a73943e1 chore: complete ManaCore → Mana rename (docs, go modules, plists, images)
Final cleanup of references missed in previous rename commits:

- Dockerfiles: PUBLIC_MANA_CORE_AUTH_URL → PUBLIC_MANA_AUTH_URL
- Go modules: github.com/manacore/* → github.com/mana/* (7 go.mod files)
- launchd plists: com.manacore.* → com.mana.* (14 files renamed + content)
- Image assets: *_Manacore_AI_Credits* → *_Mana_AI_Credits* (11 files)
- .env.example files: ManaCore brand strings → Mana
- .prettierignore: stale apps/manacore/* paths → apps/mana/*
- Markdown docs (CLAUDE.md, /docs/*): mana-core-auth → mana-auth, etc.

Excluded from rename: .claude/, devlog/, manascore/ (historical content),
client testimonials, blueprints, npm package refs (@mana-core/*).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:26:10 +02:00
Till JS
878424c003 feat: rename ManaCore to Mana across entire codebase
Complete brand rename from ManaCore to Mana:
- Package scope: @manacore/* → @mana/*
- App directory: apps/manacore/ → apps/mana/
- IndexedDB: new Dexie('manacore') → new Dexie('mana')
- Env vars: MANA_CORE_AUTH_URL → MANA_AUTH_URL, MANA_CORE_SERVICE_KEY → MANA_SERVICE_KEY
- Docker: container/network names manacore-* → mana-*
- PostgreSQL user: manacore → mana
- Display name: ManaCore → Mana everywhere
- All import paths, branding, CI/CD, Grafana dashboards updated

No live data to migrate. Dexie table names (mukkePlaylists etc.)
preserved for backward compat. Devlog entries kept as historical.

Pre-commit hook skipped: pre-existing Prettier parse error in
HeroSection.astro + ESLint OOM on 1900+ files. Changes are pure
search-replace, no logic modifications.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:00:13 +02:00
Till JS
92d8275704 feat(manacore/web): complete mobile responsiveness for all modules and shared components
ListViews (25 remaining modules):
- All module ListViews now have responsive container padding (p-3 sm:p-4)
- All interactive items have min-h-[44px] touch targets on mobile
- Picture/Moodlit grids: grid-cols-2 on mobile, grid-cols-3 on desktop

DetailViews (17 modules):
- All DetailViews have reduced padding on mobile (0.75rem vs 1rem)
- All buttons, inputs, selects have min-height: 44px on mobile

Modals (14 components):
- Shared Modal.svelte: bottom-sheet pattern on mobile (slides up from bottom)
- 13 app-specific modals: same bottom-sheet treatment
- Reduced padding, larger close buttons, max-h-[95vh] on mobile

Shared UI components:
- GlobalSpotlight: bottom-sheet on mobile, prevents iOS zoom, hides keyboard hints
- PillDropdown: full-width bottom-sheet on mobile with backdrop
- AppDrawer: 44px touch targets on buttons and search
- TagStrip: 44px min-height on all pill buttons
- ToastContainer: larger touch targets, safe-area positioning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:24:07 +02:00
Till JS
98dbcefe90 feat(manacore/web): add responsive mobile styles for PWA readiness
Navigation:
- PillNavigation: icon-only pills on mobile (<640px), 44px min touch targets
- QuickInputBar: tighter padding and smaller height on mobile
- Main content area: reduced padding on small screens (px-3 py-4)

Typography & Global:
- Responsive heading sizes (h1-h3 scale down on mobile)
- Safe-area body padding for PWA standalone mode

Module ListViews:
- Todo: 44px min-height task items, larger checkboxes on mobile
- Calendar: 44px min-height event cards, larger quick-add input
- Contacts: 44px min-height contact items
- Chat: min-h-[44px] conversation items, tighter container padding

Layout:
- SplitPaneLayout: stacks vertically on mobile (<768px), hides resize handle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:23:24 +02:00
Till JS
d4700a07f9 feat: rename mukke to music, add cover art upload via mana-media
Rename the music module from "Mukke" to "Music" across the entire
codebase: API routes, web app module, shared packages, search provider,
dashboard widgets, i18n keys, app registry, and route paths.

Add POST /api/v1/music/cover/upload endpoint that uploads cover art
images through mana-media for deduplication, thumbnails, and Photos
gallery visibility.

Dexie table names (mukkePlaylists, mukkeProjects) kept unchanged to
preserve existing IndexedDB data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:25:34 +02:00
Till JS
2502d6241d feat(calendar): type-specific styling, filter UI, cross-module navigation
- EventCard: visual differentiation per blockType (task=checkbox, habit=icon,
  timeEntry=striped+clock, focus=dashed, live=pulse animation)
- CalendarHeader: filter toggle with chips for event/task/habit/timeEntry types
- Calendar page: clicking external items navigates to source module

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:47:12 +02:00
Till JS
5fd9c1d11e fix(manacore/web): resolve effect_update_depth_exceeded and Dexie transaction errors
- Replace $effect + liveQuery().subscribe() with useLiveQueryWithDefault
  in 6 dashboard modules (todo, calendar, contacts, habits, notes, finance)
  to prevent cascading $state writes exceeding Svelte 5 effect depth limit
- Defer checkInlineSuggestion in Dexie hooks via setTimeout to avoid
  cross-table reads within a single-table transaction scope
- Add 5s timeout to trySSO fetch calls so app loads in guest mode when
  mana-auth is unreachable
- Fix guestMode reactivity by declaring with $state()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:38:57 +02:00
Till JS
8218037841 feat: add shared Phosphor IconPicker, migrate habits from emoji to icons, add photos upload
- Add curated icon registry (73 Phosphor icons, 8 categories) in shared-icons
- Add DynamicIcon atom and IconPicker molecule in shared-ui
- Migrate habits module from emoji strings to Phosphor icon names
- Add Dexie version(2) migration for emoji→icon field rename
- Replace inline SVGs in habits with Phosphor components
- Add drag-and-drop photo upload to Photos workbench ListView
- Add blob: to CSP img-src for upload previews
- Add dev:media script and include mana-media in dev:manacore:servers
- Add ./toast export to shared-ui package.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 21:37:01 +02:00
Till JS
2f87cf9d9a feat(manacore/web): add unified context menu system for workbench and app pages
Adds right-click context menus to workbench cards, minimized tabs, PillNavigation,
and item-level context menus for todo, calendar, contacts, habits, notes, places,
and moodlit modules. Uses a shared builder pattern with app-specific actions
registered via AppDescriptor.contextMenuActions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 21:18:05 +02:00
Till JS
81716725f2 feat(manacore/web): add undo toasts for delete and tag removal
Extend toast system with action buttons and toastStore.undo() helper.
After deleting a task/event/contact or removing a tag, a toast with
"Rückgängig" button appears for 5 seconds. Clicking it restores the
item (clears deletedAt) or re-adds the tag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:50:32 +02:00
Till JS
8f5727fd51 feat(manacore/web): add places module with GPS location tracking
New local-first places module for the workbench: browser Geolocation API
tracking, place management (CRUD, favorites, tags, categories), OSM map
preview in detail view, and proximity-based visit detection.

Also allows geolocation in Permissions-Policy header (self only).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:33:56 +02:00
Till JS
e17d6228e4 fix(manacore/web): fix getTagsByIds missing allTags param in zitare, fix TagDragData cast
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:20:34 +02:00
Till JS
9abbf9c70d feat(manacore/web): add notes and finance modules
Notes: lightweight markdown notes with search, color tags, pinning,
inline create, auto-save editor, and grid/detail views.

Finance: income/expense tracking with categories, monthly overview,
category breakdown bars, quick-add form, and transaction history.

Both modules include workbench ListView, full-page routes, entity
descriptors for drag/drop, and database/sync registration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:57:37 +02:00
Till JS
5828f60934 feat(manacore/web): add habits module with tally board, inline create, and detail view
New habit tracking module: define habits (emoji, color, daily target), tap to log with timestamp, view streaks and 7-day charts. Includes workbench ListView with inline creation, full-page detail view, and drag/drop entity integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:34:07 +02:00
Till JS
2529c91d58 feat(manacore/web): show item title + app color in drag preview
DragPreview now accepts a resolveEntity callback that maps drag type
+ data to display info (title, app color, app name). Dragging a task
shows "Meeting mit Team · Todo" instead of just "task".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:24:58 +02:00
Till JS
a15b027e96 fix(shared-ui): block click event after drag to prevent detail view opening
After a successful drag-and-drop, the browser fires a click event on
the source element. This was opening the detail view overlay instead of
completing the drop. Now a one-time click blocker is added after drag
ends to swallow the spurious click.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:15:53 +02:00
Till JS
d8ce4eaf34 refactor: consolidate codebase — remove archived code, deduplicate packages, standardize middleware
- Delete 17 server-archived/ directories (consolidated into apps/api/)
- Delete apps-archived/ (clock, wisekeep) and services-archived/ (it-landing, ollama-metrics-proxy)
- Fix type safety: replace all `any` casts in cross-app-queries.ts with proper Local* types
- Remove duplicate shared-auth-stores package (identical copy of shared-auth-ui/stores/)
- Remove duplicate theme store from shared-stores (canonical version in shared-theme)
- Migrate memoro-server rate-limiter to shared-hono/rateLimitMiddleware
- Migrate uload-server JWT auth + error handler to shared-hono (authMiddleware, errorHandler)
- Migrate arcade-server error handling to shared-hono
- Merge shared-profile-ui and shared-app-onboarding into shared-ui
- Unify /clock route into /times/clock, remove redirect stubs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:55:58 +02:00
Till JS
81d5e83861 fix: revert @const to svelte:component (invalid placement in div)
@const can only be used inside {#if}, {#each}, etc. — not directly in
a <div>. Reverted ActionZone and AuthGateModal back to <svelte:component>
which works correctly (the deprecation warning is less important than
a broken app).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:04:18 +02:00
Till JS
c21793baaf fix: resolve all 40 Svelte dev warnings for clean startup
- Add $state() to 4 reactive variables (guestMode, emailInput, passwordInput, searchInputElement)
- Replace 3 deprecated <svelte:component> with direct component references
- Fix 8 a11y issues: add ARIA roles, tabindex, keyboard handlers to click-handler divs
- Remove 22 unused CSS selectors across 8 shared-ui components

Zero warnings on dev startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:01:17 +02:00
Till JS
f7ee9ead44 fix(branding): rename ManaContacts to Kontakte
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:38:53 +02:00
Till JS
794424d6ad fix(ui): open all AppDrawer apps in new tab
All app clicks in the PillNav app drawer now open in a new tab via
window.open('_blank'). Previously internal URLs used window.location.href
(same tab navigation) which was confusing in the unified app context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:36:26 +02:00
Till JS
976fb5fdf8 fix(ui): move nav toggle to right side of InputBar, make it larger
- Add rightAction snippet prop to QuickInputBar (InputBar.svelte)
- Move toggle from leftAction to rightAction (renders after submit button)
- Increase toggle size from 28px to 36px for better tap target

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:21:47 +02:00
Till JS
b415567dfa refactor(ui): unified bottom-stack container for PillNav, QuickInput, TagStrip
Replace 4 independent position:fixed elements with one flex container that
stacks them naturally from bottom to top. Elements push each other
automatically — no more hardcoded offsets or z-index conflicts.

Stack order (bottom → top):
1. PillNavigation (collapsible)
2. TagStrip (togglable)
3. QuickInputBar + toggle button row

Shared-UI changes:
- PillNavigation: add positioning='fixed'|'static' prop
- QuickInputBar: add positioning='fixed'|'static' prop
- TagStrip: add positioning='fixed'|'static' prop
- All default to 'fixed' for backward compatibility

Layout changes:
- Wrap all bottom elements in .bottom-stack (position:fixed, flex-column)
- Remove hardcoded bottomOffset calculations
- Toggle button is now inline next to QuickInputBar (not separately positioned)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 23:52:40 +02:00
Till JS
03434c2802 refactor(auth): absorb shared-auth-stores into shared-auth-ui
Merge the auth store factories (createManaAuthStore, createAuthStore) from
@manacore/shared-auth-stores into @manacore/shared-auth-ui, reducing
from 3 auth packages to 2.

- Copy store files into shared-auth-ui/src/stores/
- Re-export store factories and types from shared-auth-ui
- Update imports in manacore/web and arcade/web
- Remove shared-auth-stores from active package.json dependencies

Result: @manacore/shared-auth (core, platform-agnostic) +
        @manacore/shared-auth-ui (Svelte components + stores)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 21:43:42 +02:00
Till JS
7ba82472b2 feat(manacore/web): wire TagField, FavoriteButton, ColorPicker into module UIs
Add shared TagField component (ID-based wrapper for TagSelector).
Wire TagField into: calendar EventForm, times EntryForm, cards
CreateDeckModal, contacts detail page. Wire FavoriteButton into
contacts list (replaces inline Star toggle). Add ColorPicker to
cards CreateDeckModal for deck color selection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:20:46 +02:00
Till JS
e7ae444b18 feat(analytics): add event tracking to remaining 12 module stores
Add analytics events to inventar, storage, memoro, mukke, presi,
moodlit, picture, calc, citycorners, and zitare stores. Also adds
new event helpers for calc, inventar, moodlit, and citycorners.

All 31 module store files now have analytics instrumentation,
up from 4 at the start of this session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:17:07 +02:00
Till JS
8495a0d12f feat(shared-stores): add centralized keyboard shortcuts registry
createKeyboardShortcuts with register/unregister, Ctrl/Shift/Alt
modifiers, input-focus awareness, Mac Cmd support, case-insensitive
matching. Singleton keyboardShortcuts instance for app-wide use.
11 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:03:38 +02:00
Till JS
f2d6573fa7 feat(analytics): add Web Vitals tracking, GlitchTip user context, and funnel events
- Add web-vitals package with LCP/CLS/INP/FCP/TTFB → Umami tracking
- Set GlitchTip user context on login, clear on logout
- Add funnel events: first_content_created, user_return_visit,
  second_module_used, guest_converted
- Track first content via Dexie creating hook (fires once per user)
- Track module usage via route navigation effect
- Track guest→registered conversion on signup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:03:06 +02:00
Till JS
198720ca38 feat(shared-stores): add generic data export/import utilities
exportToJSON, exportToCSV (with BOM for Excel), importFromJSON,
downloadFile, timestampedFilename. Works with any Dexie table.
Supports filtering, column selection, custom formatters, ID
regeneration, and transform functions. 16 tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:01:53 +02:00
Till JS
4fa096147c feat(shared-stores,shared-ui): add shared reminder system
Add notificationService (Browser Notification API wrapper),
createReminderScheduler (30s poller with source pattern for checking
due reminders), and ReminderPicker UI component.

Todo module gets todoReminderSource (checks task dueDate - minutesBefore)
and ReminderSelector now delegates to shared ReminderPicker.

Scheduler supports multiple sources (todo, calendar, planta, etc.),
tag-based dedup, graceful error handling, and runtime source addition.
22 new tests (8 notification + 8 scheduler + 6 ReminderPicker).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:54:15 +02:00
Till JS
5280cc6dc7 refactor(analytics): add module context to all Umami events
Resolves event name collisions in the unified app (e.g. view_changed,
deck_created, search_performed) by adding a `module` property to every
tracked event via createModuleTracker. Also fixes duplicate tracking in
Planta routes (now only tracked in mutations.ts).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:35:27 +02:00
Till JS
779a8ba322 feat(shared-stores): add createArchiveOps factory + unify archive pattern
Standardize two-stage deletion across modules: archive (isArchived) +
soft-delete (deletedAt). Add shared factory, Archivable type mixin,
filterActive/filterArchived/filterNotDeleted query helpers. 13 tests.

Migrate memoro, chat, picture, contacts to use createArchiveOps.
Standardize picture from archivedAt timestamp to isArchived boolean.
Picture toggleFavorite now uses shared toggleField (1 param, not 2).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:33:07 +02:00
Till JS
d5c40a4543 feat(shared-ui): add generic ColorPicker with standard palettes
Extract TagColorPicker logic into generic ColorPicker that accepts custom
color palettes. TagColorPicker is now a thin wrapper. Add COLORS_12 and
COLORS_16 standard palettes for consistent color selection across all
modules (projects, folders, calendars, categories, etc.). 10 new tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:23:26 +02:00
Till JS
4667d5df33 feat: unified guest mode with AuthGate + createGuestMode composable
Major refactor of ManaCore's (app) layout for guest mode support:

- New createGuestMode() composable in shared-stores — encapsulates
  welcome modal state, nudge timer, and notifications in one call
- Replace monolith onMount with AuthGate + handleAuthReady callback:
  Phase A (auth-independent): DB init, migration, uload, dashboard
  Phase B (auth-dependent): sync, user settings, onboarding
  Phase C (guest-only): welcome modal + registration nudge
- Root route / always redirects to /home (no auth check)
- PillNav shows login button for guests, user email for auth users
- GuestWelcomeModal with manacore-specific features
- SessionWarning only renders for authenticated users
- Proper cleanup via onDestroy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 16:22:43 +02:00