mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 03:01:09 +02:00
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>
This commit is contained in:
parent
502813f49c
commit
5fd9c1d11e
13 changed files with 61 additions and 105 deletions
|
|
@ -412,9 +412,14 @@ for (const [appId, tables] of Object.entries(SYNC_APP_MAP)) {
|
|||
});
|
||||
trackFirstContent(appId);
|
||||
fireTrigger(appId, tableName, 'insert', { ...obj });
|
||||
checkInlineSuggestion(appId, tableName, { ...obj }).then((sug) => {
|
||||
if (sug) window.dispatchEvent(new CustomEvent('mana:automation-suggest', { detail: sug }));
|
||||
});
|
||||
// Defer cross-table reads outside the Dexie hook's transaction scope
|
||||
const objCopy = { ...obj };
|
||||
setTimeout(() => {
|
||||
checkInlineSuggestion(appId, tableName, objCopy).then((sug) => {
|
||||
if (sug)
|
||||
window.dispatchEvent(new CustomEvent('mana:automation-suggest', { detail: sug }));
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
table.hook('updating', function (modifications, primKey) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
Clicking an event opens the detail view.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { liveQuery } from 'dexie';
|
||||
import { useLiveQueryWithDefault } from '@manacore/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalEvent } from './types';
|
||||
import { eventsStore } from './stores/events.svelte';
|
||||
|
|
@ -29,7 +29,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
let events = $state<LocalEvent[]>([]);
|
||||
let events$ = useLiveQueryWithDefault(async () => {
|
||||
return db
|
||||
.table<LocalEvent>('events')
|
||||
.toArray()
|
||||
.then((all) => all.filter((e) => !e.deletedAt));
|
||||
}, [] as LocalEvent[]);
|
||||
let events = $derived(events$.value);
|
||||
|
||||
const now = new Date();
|
||||
const todayStr = now.toISOString().split('T')[0];
|
||||
|
|
@ -52,18 +58,6 @@
|
|||
.sort((a, b) => a.startDate.localeCompare(b.startDate))
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
const sub = liveQuery(async () => {
|
||||
return db
|
||||
.table<LocalEvent>('events')
|
||||
.toArray()
|
||||
.then((all) => all.filter((e) => !e.deletedAt));
|
||||
}).subscribe((val) => {
|
||||
events = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
|
||||
function formatTime(iso: string): string {
|
||||
return new Date(iso).toLocaleTimeString('de', { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
Clicking a contact opens the detail view.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { liveQuery } from 'dexie';
|
||||
import { useLiveQueryWithDefault } from '@manacore/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalContact } from './types';
|
||||
import { contactsStore } from './stores/contacts.svelte';
|
||||
|
|
@ -29,21 +29,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
let contacts = $state<LocalContact[]>([]);
|
||||
let contacts$ = useLiveQueryWithDefault(async () => {
|
||||
return db
|
||||
.table<LocalContact>('contacts')
|
||||
.toArray()
|
||||
.then((all) => all.filter((c) => !c.deletedAt && !c.isArchived));
|
||||
}, [] as LocalContact[]);
|
||||
let contacts = $derived(contacts$.value);
|
||||
let search = $state('');
|
||||
|
||||
$effect(() => {
|
||||
const sub = liveQuery(async () => {
|
||||
return db
|
||||
.table<LocalContact>('contacts')
|
||||
.toArray()
|
||||
.then((all) => all.filter((c) => !c.deletedAt && !c.isArchived));
|
||||
}).subscribe((val) => {
|
||||
contacts = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
|
||||
const filtered = $derived(() => {
|
||||
if (!search.trim()) return contacts;
|
||||
const q = search.toLowerCase();
|
||||
|
|
|
|||
|
|
@ -22,22 +22,8 @@
|
|||
|
||||
let txs$ = useAllTransactions();
|
||||
let cats$ = useAllCategories();
|
||||
let txs = $state<Transaction[]>([]);
|
||||
let categories = $state<FinanceCategory[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
const sub = txs$.subscribe((val) => {
|
||||
txs = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const sub = cats$.subscribe((val) => {
|
||||
categories = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
let txs = $derived(txs$.value);
|
||||
let categories = $derived(cats$.value);
|
||||
|
||||
let month = currentMonth();
|
||||
let monthTxs = $derived(getTransactionsForMonth(txs, month));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Reactive Queries & Pure Helpers for Finance module.
|
||||
*/
|
||||
|
||||
import { liveQuery } from 'dexie';
|
||||
import { useLiveQueryWithDefault } from '@manacore/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import type {
|
||||
LocalTransaction,
|
||||
|
|
@ -43,23 +43,23 @@ export function toCategory(local: LocalFinanceCategory): FinanceCategory {
|
|||
// ─── Live Queries ──────────────────────────────────────────
|
||||
|
||||
export function useAllTransactions() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalTransaction>('transactions').toArray();
|
||||
return locals
|
||||
.filter((t) => !t.deletedAt)
|
||||
.map(toTransaction)
|
||||
.sort((a, b) => b.date.localeCompare(a.date) || b.createdAt.localeCompare(a.createdAt));
|
||||
});
|
||||
}, [] as Transaction[]);
|
||||
}
|
||||
|
||||
export function useAllCategories() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalFinanceCategory>('financeCategories').toArray();
|
||||
return locals
|
||||
.filter((c) => !c.deletedAt)
|
||||
.map(toCategory)
|
||||
.sort((a, b) => a.order - b.order);
|
||||
});
|
||||
}, [] as FinanceCategory[]);
|
||||
}
|
||||
|
||||
// ─── Pure Helpers ──────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -23,22 +23,8 @@
|
|||
|
||||
let habits$ = useAllHabits();
|
||||
let logs$ = useAllHabitLogs();
|
||||
let habits = $state<Habit[]>([]);
|
||||
let logs = $state<HabitLog[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
const sub = habits$.subscribe((val) => {
|
||||
habits = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const sub = logs$.subscribe((val) => {
|
||||
logs = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
let habits = $derived(habits$.value);
|
||||
let logs = $derived(logs$.value);
|
||||
|
||||
let activeHabits = $derived(getActiveHabits(habits));
|
||||
let todayCounts = $derived(getTodayCounts(habits, logs));
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/**
|
||||
* Reactive Queries & Pure Helpers for Habits module.
|
||||
*
|
||||
* Uses Dexie liveQuery on the unified database.
|
||||
* Uses useLiveQueryWithDefault on the unified database.
|
||||
*/
|
||||
|
||||
import { liveQuery } from 'dexie';
|
||||
import { useLiveQueryWithDefault } from '@manacore/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalHabit, LocalHabitLog, Habit, HabitLog } from './types';
|
||||
import { EMOJI_TO_ICON_MAP } from './types';
|
||||
|
|
@ -38,21 +38,21 @@ export function toHabitLog(local: LocalHabitLog): HabitLog {
|
|||
// ─── Live Queries ──────────────────────────────────────────
|
||||
|
||||
export function useAllHabits() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalHabit>('habits').orderBy('order').toArray();
|
||||
return locals.filter((h) => !h.deletedAt).map(toHabit);
|
||||
});
|
||||
}, [] as Habit[]);
|
||||
}
|
||||
|
||||
export function useAllHabitLogs() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalHabitLog>('habitLogs').toArray();
|
||||
return locals.filter((l) => !l.deletedAt).map(toHabitLog);
|
||||
});
|
||||
}, [] as HabitLog[]);
|
||||
}
|
||||
|
||||
export function useHabitLogsForHabit(habitId: string) {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db
|
||||
.table<LocalHabitLog>('habitLogs')
|
||||
.where('habitId')
|
||||
|
|
@ -62,7 +62,7 @@ export function useHabitLogsForHabit(habitId: string) {
|
|||
.filter((l) => !l.deletedAt)
|
||||
.map(toHabitLog)
|
||||
.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
||||
});
|
||||
}, [] as HabitLog[]);
|
||||
}
|
||||
|
||||
// ─── Pure Helpers ──────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -13,14 +13,7 @@
|
|||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
let notes$ = useAllNotes();
|
||||
let notes = $state<Note[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
const sub = notes$.subscribe((val) => {
|
||||
notes = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
let notes = $derived(notes$.value);
|
||||
|
||||
let searchQuery = $state('');
|
||||
let editingId = $state<string | null>(null);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Reactive Queries & Pure Helpers for Notes module.
|
||||
*/
|
||||
|
||||
import { liveQuery } from 'dexie';
|
||||
import { useLiveQueryWithDefault } from '@manacore/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalNote, Note } from './types';
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ export function toNote(local: LocalNote): Note {
|
|||
// ─── Live Queries ──────────────────────────────────────────
|
||||
|
||||
export function useAllNotes() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalNote>('notes').toArray();
|
||||
return locals
|
||||
.filter((n) => !n.deletedAt && !n.isArchived)
|
||||
|
|
@ -33,7 +33,7 @@ export function useAllNotes() {
|
|||
if (a.isPinned !== b.isPinned) return a.isPinned ? -1 : 1;
|
||||
return b.updatedAt.localeCompare(a.updatedAt);
|
||||
});
|
||||
});
|
||||
}, [] as Note[]);
|
||||
}
|
||||
|
||||
// ─── Pure Helpers ──────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -50,14 +50,7 @@
|
|||
let filter = $state<ViewFilter>('inbox');
|
||||
let newTitle = $state('');
|
||||
let tasks$ = useAllTasks();
|
||||
let tasks = $state<import('./types').Task[]>([]);
|
||||
|
||||
$effect(() => {
|
||||
const sub = tasks$.subscribe((val) => {
|
||||
tasks = val ?? [];
|
||||
});
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
let tasks = $derived(tasks$.value);
|
||||
|
||||
const stats = $derived(getTaskStats(tasks));
|
||||
const filtered = $derived(() => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Reactive queries & pure helpers for Todo — uses Dexie liveQuery on the unified DB.
|
||||
*/
|
||||
|
||||
import { liveQuery } from 'dexie';
|
||||
import { useLiveQueryWithDefault } from '@manacore/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import type {
|
||||
LocalTask,
|
||||
|
|
@ -43,34 +43,34 @@ export function toTask(local: LocalTask): Task {
|
|||
// ─── Live Queries ──────────────────────────────────────────
|
||||
|
||||
export function useAllTasks() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalTask>('tasks').orderBy('order').toArray();
|
||||
return locals.filter((t) => !t.deletedAt).map(toTask);
|
||||
});
|
||||
}, [] as Task[]);
|
||||
}
|
||||
|
||||
// Labels/Tags: use shared global tags from @manacore/shared-stores
|
||||
export { useAllTags as useAllLabels } from '@manacore/shared-stores';
|
||||
|
||||
export function useAllBoardViews() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalBoardView>('boardViews').orderBy('order').toArray();
|
||||
return locals.filter((v) => !v.deletedAt);
|
||||
});
|
||||
}, [] as LocalBoardView[]);
|
||||
}
|
||||
|
||||
export function useAllReminders() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalReminder>('reminders').toArray();
|
||||
return locals.filter((r) => !r.deletedAt);
|
||||
});
|
||||
}, [] as LocalReminder[]);
|
||||
}
|
||||
|
||||
export function useAllProjects() {
|
||||
return liveQuery(async () => {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const locals = await db.table<LocalTodoProject>('todoProjects').orderBy('order').toArray();
|
||||
return locals.filter((p) => !p.deletedAt);
|
||||
});
|
||||
}, [] as LocalTodoProject[]);
|
||||
}
|
||||
|
||||
// ─── Pure Filter Functions ────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@
|
|||
}
|
||||
|
||||
// ── Guest Mode ──────────────────────────────────────────
|
||||
let guestMode: GuestMode | null = null;
|
||||
let guestMode = $state<GuestMode | null>(null);
|
||||
|
||||
// ── Onboarding ──────────────────────────────────────────
|
||||
function handleOnboardingComplete() {
|
||||
|
|
|
|||
|
|
@ -1110,12 +1110,15 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
}
|
||||
|
||||
// Try to get session from cookie (credentials: 'include' sends cookies)
|
||||
// Use AbortController with timeout so the app doesn't hang when auth is unreachable
|
||||
const ssoAbort = AbortSignal.timeout(5000);
|
||||
const response = await fetch(`${baseUrl}${endpoints.getSession}`, {
|
||||
method: 'GET',
|
||||
credentials: 'include', // Send cookies cross-origin
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
signal: ssoAbort,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -1132,12 +1135,14 @@ export function createAuthService(config: AuthServiceConfig): AuthServiceInterfa
|
|||
|
||||
// Now get tokens by signing in with the session
|
||||
// We need to exchange the session for JWT tokens
|
||||
const tokenAbort = AbortSignal.timeout(5000);
|
||||
const tokenResponse = await fetch(`${baseUrl}/api/v1/auth/session-to-token`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
signal: tokenAbort,
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue