added instant sync and fixed saving debounce

This commit is contained in:
2026-02-04 16:21:41 -06:00
parent e7b0967bb3
commit 54a0da9896
2 changed files with 131 additions and 22 deletions
+111 -20
View File
@@ -287,6 +287,94 @@ export const getCombinedScore = (task: Task): number => {
let heartbeatInterval: number | undefined;
const mapRecordToTask = (r: any): Task => {
// Check if it's a template? The caller handles filtering templates usually,
// but here we just map fields.
const tags = r.tags || [];
return {
id: r.id,
title: r.title,
startDate: r.startDate,
dueDate: r.dueDate,
priority: r.priority,
completed: r.completed,
tags: tags,
content: r.content,
deletedAt: r.deletedAt ? new Date(r.deletedAt).getTime() : undefined,
created: r.created,
updated: r.updated,
recurrence: r.recurrence
};
};
export const subscribeToRealtime = async () => {
// Unsubscribe first to avoid duplicates
await pb.collection(TASGRID_COLLECTION).unsubscribe('*');
pb.collection(TASGRID_COLLECTION).subscribe('*', (e) => {
console.log("Realtime event:", e.action, e.record.id);
if (e.action === 'create') {
// Check if it's a template
const isTemplate = e.record.tags?.includes("__template__");
if (isTemplate) {
// We don't auto-add templates to the main task list usually?
// Wait, `store.templates` is separate.
// We should handle templates too if we want sync on templates.
let meta: any = {};
try { meta = JSON.parse(e.record.content || "{}"); } catch (e) { }
const newTemplate: TaskTemplate = {
id: e.record.id,
name: e.record.title,
title: meta.title || "",
priority: e.record.priority,
urgency: meta.urgency || 5,
tags: meta.tags || [],
content: meta.content || ""
};
setStore("templates", t => [...(t || []), newTemplate]);
return;
}
// Regular Task
const exists = store.tasks.find(t => t.id === e.record.id);
if (!exists) {
const newTask = mapRecordToTask(e.record);
setStore("tasks", t => [newTask, ...t]);
}
}
if (e.action === 'update') {
// Is it a template?
const isTemplate = e.record.tags?.includes("__template__");
if (isTemplate) {
// Update template store
let meta: any = {};
try { meta = JSON.parse(e.record.content || "{}"); } catch (e) { }
setStore("templates", t => t.id === e.record.id, {
name: e.record.title,
title: meta.title || "",
priority: e.record.priority,
urgency: meta.urgency || 5,
tags: meta.tags || [],
content: meta.content || ""
});
return;
}
// Normal Task
const updatedTask = mapRecordToTask(e.record);
setStore("tasks", t => t.id === e.record.id, updatedTask);
}
if (e.action === 'delete') {
// Try deleting from both just in case
setStore("tasks", t => t.filter(x => x.id !== e.record.id));
setStore("templates", t => (t || []).filter(x => x.id !== e.record.id));
}
});
};
export const initStore = async () => {
if (!pb.authStore.isValid) return;
@@ -305,7 +393,10 @@ export const initStore = async () => {
// Heartbeat - Ensure only one interval is running
if (heartbeatInterval) clearInterval(heartbeatInterval);
heartbeatInterval = setInterval(() => setNow(Date.now()), 60000);
heartbeatInterval = setInterval(() => {
setNow(Date.now());
checkRecurringTasks(); // Check for recurrence triggers (minutely, etc.)
}, 60000);
try {
// 1. Fetch Preferences from User Profile (Weights, Scale, Tags)
@@ -367,19 +458,7 @@ export const initStore = async () => {
content: meta.content || ""
});
} else {
allTasks.push({
id: r.id,
title: r.title,
startDate: r.startDate,
dueDate: r.dueDate,
priority: r.priority,
completed: r.completed,
tags: tags,
content: r.content,
deletedAt: r.deletedAt ? new Date(r.deletedAt).getTime() : undefined,
created: r.created,
updated: r.updated
});
allTasks.push(mapRecordToTask(r));
}
});
@@ -389,6 +468,9 @@ export const initStore = async () => {
// Check recurring tasks after load
checkRecurringTasks();
// 3. Subscribe to Realtime Updates
await subscribeToRealtime();
// 4. Reconstructive Migration: Verify all tags in tasks have definitions
const foundTagNames = new Set<string>();
allTasks.forEach(t => t.tags?.forEach(tag => foundTagNames.add(tag)));
@@ -458,12 +540,21 @@ export const addTask = async (title: string, options?: { dueDate?: Date | string
});
// Replace temp task with real record (merging server fields like id, created, updated)
setStore("tasks", (t) => t.map(task => task.id === tempId ? {
...task,
id: record.id,
created: record.created,
updated: record.updated
} : task));
setStore("tasks", (t) => {
// Check if REAL ID exists already (from subscription race)
const realExists = t.some(x => x.id === record.id);
if (realExists) {
// If real exists, just remove temp.
return t.filter(x => x.id !== tempId);
}
// Otherwise, replace temp with real
return t.map(task => task.id === tempId ? {
...task,
id: record.id,
created: record.created,
updated: record.updated
} : task);
});
} catch (err) {
console.error("create failed", err);
toast.error("Failed to save task.");