diff --git a/apps/mana/apps/web/src/lib/byok/vault.ts b/apps/mana/apps/web/src/lib/byok/vault.ts index cfe73ff87..54f3fc9ef 100644 --- a/apps/mana/apps/web/src/lib/byok/vault.ts +++ b/apps/mana/apps/web/src/lib/byok/vault.ts @@ -12,6 +12,7 @@ */ import { db } from '$lib/data/database'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { wrapValue, unwrapValue } from '$lib/data/crypto/aes'; import { getActiveKey } from '$lib/data/crypto/key-provider'; import type { ByokProviderId } from '@mana/shared-llm'; @@ -48,7 +49,7 @@ async function recordToPlain(rec: ByokKeyRecord): Promise { model: rec.model, isDefault: rec.isDefault, createdAt: rec.createdAt, - updatedAt: rec.updatedAt, + updatedAt: deriveUpdatedAt(rec), lastUsedAt: rec.lastUsedAt, usageCount: rec.usageCount, totalTokens: rec.totalTokens, @@ -75,7 +76,7 @@ export const byokVault = { model: r.model, isDefault: r.isDefault, createdAt: r.createdAt, - updatedAt: r.updatedAt, + updatedAt: deriveUpdatedAt(r), lastUsedAt: r.lastUsedAt, usageCount: r.usageCount, totalTokens: r.totalTokens, diff --git a/apps/mana/apps/web/src/lib/data/ai/agents/bootstrap.ts b/apps/mana/apps/web/src/lib/data/ai/agents/bootstrap.ts index 8d8d6ccf0..aed012764 100644 --- a/apps/mana/apps/web/src/lib/data/ai/agents/bootstrap.ts +++ b/apps/mana/apps/web/src/lib/data/ai/agents/bootstrap.ts @@ -141,7 +141,7 @@ export async function backfillMissionsAgentId(targetAgentId: string): Promise { for (const m of pending) { - await table.update(m.id, { agentId: targetAgentId, updatedAt: now }); + await table.update(m.id, { agentId: targetAgentId }); } }); diff --git a/apps/mana/apps/web/src/lib/data/ai/agents/kontext.ts b/apps/mana/apps/web/src/lib/data/ai/agents/kontext.ts index 9d6eaf545..d0bc3e4d3 100644 --- a/apps/mana/apps/web/src/lib/data/ai/agents/kontext.ts +++ b/apps/mana/apps/web/src/lib/data/ai/agents/kontext.ts @@ -54,7 +54,6 @@ export async function saveAgentKontext(agentId: string, content: string): Promis if (existing) { const diff: Partial = { content, - updatedAt: new Date().toISOString(), }; await encryptRecord(TABLE, diff); await db.table(TABLE).update(existing.id, diff); diff --git a/apps/mana/apps/web/src/lib/data/ai/agents/store.ts b/apps/mana/apps/web/src/lib/data/ai/agents/store.ts index ef8c8a840..6ab56f5ad 100644 --- a/apps/mana/apps/web/src/lib/data/ai/agents/store.ts +++ b/apps/mana/apps/web/src/lib/data/ai/agents/store.ts @@ -147,7 +147,6 @@ export async function updateAgent(id: string, patch: AgentPatch): Promise } const mods: Partial = { ...deepClone(patch), - updatedAt: new Date().toISOString(), }; await encryptRecord(AGENTS_TABLE, mods); await table().update(id, mods); @@ -156,15 +155,15 @@ export async function updateAgent(id: string, patch: AgentPatch): Promise // ── Lifecycle ────────────────────────────────────────────── export async function archiveAgent(id: string): Promise { - await table().update(id, { state: 'archived', updatedAt: new Date().toISOString() }); + await table().update(id, { state: 'archived' }); } export async function pauseAgent(id: string): Promise { - await table().update(id, { state: 'paused', updatedAt: new Date().toISOString() }); + await table().update(id, { state: 'paused' }); } export async function resumeAgent(id: string): Promise { - await table().update(id, { state: 'active', updatedAt: new Date().toISOString() }); + await table().update(id, { state: 'active' }); } /** Soft-delete. Missions owned by the agent keep running; the Workbench diff --git a/apps/mana/apps/web/src/lib/data/ai/missions/store.ts b/apps/mana/apps/web/src/lib/data/ai/missions/store.ts index f48211c5c..0aabed4cf 100644 --- a/apps/mana/apps/web/src/lib/data/ai/missions/store.ts +++ b/apps/mana/apps/web/src/lib/data/ai/missions/store.ts @@ -119,7 +119,6 @@ export async function updateMission(id: string, patch: MissionPatch): Promise = { ...deepClone(patch), - updatedAt: new Date().toISOString(), }; if (patch.cadence) { mods.nextRunAt = nextRunForCadence(patch.cadence, new Date()); @@ -130,7 +129,7 @@ export async function updateMission(id: string, patch: MissionPatch): Promise { - await table().update(id, { state: 'paused', updatedAt: new Date().toISOString() }); + await table().update(id, { state: 'paused' }); } export async function resumeMission(id: string): Promise { @@ -139,7 +138,6 @@ export async function resumeMission(id: string): Promise { await table().update(id, { state: 'active', nextRunAt: nextRunForCadence(mission.cadence, new Date()), - updatedAt: new Date().toISOString(), }); } @@ -147,12 +145,11 @@ export async function completeMission(id: string): Promise { await table().update(id, { state: 'done', nextRunAt: undefined, - updatedAt: new Date().toISOString(), }); } export async function archiveMission(id: string): Promise { - await table().update(id, { state: 'archived', updatedAt: new Date().toISOString() }); + await table().update(id, { state: 'archived' }); } export async function deleteMission(id: string): Promise { @@ -173,7 +170,6 @@ export async function setMissionGrant( // attached — matches the pattern used in createMission / updateMission. await table().update(id, { grant: deepClone(grant), - updatedAt: new Date().toISOString(), }); } @@ -183,7 +179,6 @@ export async function setMissionGrant( export async function revokeMissionGrant(id: string): Promise { await table().update(id, { grant: undefined, - updatedAt: new Date().toISOString(), }); } @@ -213,7 +208,6 @@ export async function startIteration( }; await table().update(missionId, { iterations: [...mission.iterations, iteration], - updatedAt: now, }); return iteration; } @@ -242,7 +236,7 @@ export async function setIterationPhase( } : it ); - await table().update(missionId, { iterations: updated, updatedAt: now }); + await table().update(missionId, { iterations: updated }); } catch (err) { console.warn('[mission-store] setIterationPhase failed:', err); } @@ -263,7 +257,6 @@ export async function requestIterationCancel( ); await table().update(missionId, { iterations: updated, - updatedAt: new Date().toISOString(), }); } @@ -317,7 +310,6 @@ export async function finishIteration( iterations: updatedIterations, // Advance nextRunAt now that this iteration is done nextRunAt: nextRunForCadence(mission.cadence, new Date()), - updatedAt: new Date().toISOString(), }); } @@ -334,6 +326,5 @@ export async function addIterationFeedback( ); await table().update(missionId, { iterations: updatedIterations, - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/data/conflict-store.svelte.ts b/apps/mana/apps/web/src/lib/data/conflict-store.svelte.ts index 80f60a705..4214704c7 100644 --- a/apps/mana/apps/web/src/lib/data/conflict-store.svelte.ts +++ b/apps/mana/apps/web/src/lib/data/conflict-store.svelte.ts @@ -153,7 +153,7 @@ async function restore(id: string): Promise { await new Promise((resolve) => setTimeout(resolve, 0)); const now = new Date().toISOString(); - const updates: Record = { updatedAt: now }; + const updates: Record = {}; for (const [field, info] of Object.entries(conflict.fields)) { updates[field] = info.wasLocal; diff --git a/apps/mana/apps/web/src/lib/data/database.ts b/apps/mana/apps/web/src/lib/data/database.ts index 62ee154d9..57c8784b0 100644 --- a/apps/mana/apps/web/src/lib/data/database.ts +++ b/apps/mana/apps/web/src/lib/data/database.ts @@ -1157,10 +1157,10 @@ db.version(48).upgrade(async (tx) => { } const survivorAppCount = Array.isArray(survivor.openApps) ? survivor.openApps.length : 0; if (merged.length !== survivorAppCount) { - await tx.table('workbenchScenes').update(survivor.id, { openApps: merged, updatedAt: now }); + await tx.table('workbenchScenes').update(survivor.id, { openApps: merged }); } for (const loser of losers) { - await tx.table('workbenchScenes').update(loser.id, { deletedAt: now, updatedAt: now }); + await tx.table('workbenchScenes').update(loser.id, { deletedAt: now }); removed += 1; } } @@ -1260,6 +1260,97 @@ db.version(52).stores({ lastsCooldown: 'id, refTable, dismissedAt, [refTable+refId]', }); +// v53 — Sync Field-Meta Overhaul F3 (docs/plans/sync-field-meta-overhaul.md). +// `updatedAt` is no longer a synced data field; replaced by a non-synced +// shadow column `_updatedAtIndex` that the Dexie creating/updating hook +// stamps on every write. All 22 tables that previously indexed `updatedAt` +// now index `_updatedAtIndex` so module queries can keep using +// `.orderBy(...)` for sort. The upgrade step copies the existing +// updatedAt value into _updatedAtIndex so existing local rows stay +// orderable across the version bump. +db.version(53) + .stores({ + conversations: 'id, isArchived, isPinned, spaceId, templateId, _updatedAtIndex', + images: + 'id, isFavorite, isPublic, isArchived, prompt, _updatedAtIndex, wardrobeOutfitId, wardrobeGarmentId', + songs: 'id, artist, album, genre, favorite, title, _updatedAtIndex', + mukkePlaylists: 'id, name, _updatedAtIndex', + presiDecks: 'id, isPublic, _updatedAtIndex', + documents: 'id, contextSpaceId, type, pinned, title, [contextSpaceId+type], _updatedAtIndex', + playgroundConversations: 'id, model, isPinned, _updatedAtIndex', + journalEntries: 'id, entryDate, mood, isPinned, isArchived, isFavorite, _updatedAtIndex', + dreams: 'id, dreamDate, mood, isLucid, isPinned, isArchived, _updatedAtIndex', + dreamSymbols: 'id, name, count, _updatedAtIndex', + periods: 'id, startDate, endDate, isPredicted, isArchived, _updatedAtIndex', + periodSymptoms: 'id, name, category, count, _updatedAtIndex', + notes: 'id, isPinned, isArchived, color, title, _updatedAtIndex', + quizzes: 'id, isPinned, isArchived, _updatedAtIndex', + userTagPresets: 'id, userId, isDefault, _updatedAtIndex', + websites: 'id, slug, publishedVersion, _updatedAtIndex, deletedAt', + websitePages: 'id, siteId, [siteId+order], [siteId+path], _updatedAtIndex, deletedAt', + websiteBlocks: + 'id, pageId, parentBlockId, [pageId+order], [pageId+parentBlockId+order], type, _updatedAtIndex, deletedAt', + writingDrafts: 'id, kind, status, _updatedAtIndex, isFavorite', + writingStyles: 'id, source, isSpaceDefault, isFavorite, _updatedAtIndex', + }) + .upgrade(async (tx) => { + // Copy the existing `updatedAt` value into `_updatedAtIndex` for every + // row in the affected tables so post-upgrade sorts continue to land + // in the right order. Rows without an updatedAt fall back to + // `__fieldMeta` argmax, then to createdAt, then to empty string. + const tables = [ + 'conversations', + 'images', + 'songs', + 'mukkePlaylists', + 'presiDecks', + 'documents', + 'playgroundConversations', + 'journalEntries', + 'dreams', + 'dreamSymbols', + 'periods', + 'periodSymptoms', + 'notes', + 'quizzes', + 'userTagPresets', + 'websites', + 'websitePages', + 'websiteBlocks', + 'writingDrafts', + 'writingStyles', + ]; + for (const tableName of tables) { + await tx + .table(tableName) + .toCollection() + .modify((row: Record) => { + const updatedAt = + (typeof row.updatedAt === 'string' ? row.updatedAt : undefined) ?? + deriveFromFieldMeta(row) ?? + (typeof row.createdAt === 'string' ? row.createdAt : undefined) ?? + ''; + row._updatedAtIndex = updatedAt; + // Keep `updatedAt` on the row for now — the F3 store/type + // sweep below renames every read. The next-version + // upgrade (or a follow-up cleanup) can drop it once all + // modules read via the type-converter helper. + }); + } + }); + +/** Local helper for the v53 upgrade — read `__fieldMeta` argmax `at` + * without importing the sync layer (which would create a cycle here). */ +function deriveFromFieldMeta(row: Record): string | undefined { + const meta = row[FIELD_META_KEY]; + if (!meta || typeof meta !== 'object') return undefined; + let max = ''; + for (const fm of Object.values(meta as Record)) { + if (fm && typeof fm.at === 'string' && fm.at > max) max = fm.at; + } + return max || undefined; +} + // ─── Sync Routing ────────────────────────────────────────── // SYNC_APP_MAP, TABLE_TO_SYNC_NAME, TABLE_TO_APP, SYNC_NAME_TO_TABLE, // toSyncName() and fromSyncName() are now derived from per-module @@ -1403,8 +1494,17 @@ function trackActivity( */ export const FIELD_META_KEY = '__fieldMeta'; +/** + * Local-only shadow column the Dexie hook stamps on every create + update + * write. Holds the latest `now` ISO so module queries can `.orderBy(...)` + * by it instead of the (now derived) `updatedAt`. Stripped from + * pending-change payloads so it never travels to mana-sync — it is rebuilt + * locally on each write, including server-replays via applyServerChanges. + */ +export const UPDATED_AT_INDEX_KEY = '_updatedAtIndex'; + function isInternalKey(key: string): boolean { - return key === 'id' || key === FIELD_META_KEY; + return key === 'id' || key === FIELD_META_KEY || key === UPDATED_AT_INDEX_KEY; } /** @@ -1513,9 +1613,16 @@ for (const [appId, tables] of Object.entries(SYNC_APP_MAP)) { fieldMeta[key] = makeFieldMeta(now, actor, origin); } objRecord[FIELD_META_KEY] = fieldMeta; + // Stamp the local-only shadow column for indexed sorts. Stripped + // from `dataForSync` below so it never lands in pending-changes. + objRecord[UPDATED_AT_INDEX_KEY] = now; - // Build payload for pending-change WITHOUT the internal bookkeeping field. - const { [FIELD_META_KEY]: _fm, ...dataForSync } = obj as Record; + // Build payload for pending-change WITHOUT the internal bookkeeping fields. + const { + [FIELD_META_KEY]: _fm, + [UPDATED_AT_INDEX_KEY]: _uai, + ...dataForSync + } = obj as Record; trackPendingChange(tableName, { appId, @@ -1604,9 +1711,12 @@ for (const [appId, tables] of Object.entries(SYNC_APP_MAP)) { // Returning an object from a Dexie 'updating' hook merges it into the // modifications applied to the record — use this to persist the merged - // __fieldMeta alongside the user's data update. + // __fieldMeta alongside the user's data update, plus refresh the + // local-only `_updatedAtIndex` shadow so indexed sorts see the + // latest write. return { [FIELD_META_KEY]: newMeta, + [UPDATED_AT_INDEX_KEY]: now, }; }); } diff --git a/apps/mana/apps/web/src/lib/data/detail-entity.svelte.ts b/apps/mana/apps/web/src/lib/data/detail-entity.svelte.ts index 2698ec77b..b7dcfb1da 100644 --- a/apps/mana/apps/web/src/lib/data/detail-entity.svelte.ts +++ b/apps/mana/apps/web/src/lib/data/detail-entity.svelte.ts @@ -175,7 +175,6 @@ export function useDetailEntity( toastStore.undo(label, () => { db.table(opts.table!).update(id, { deletedAt: undefined, - updatedAt: new Date().toISOString(), }); }); } else { diff --git a/apps/mana/apps/web/src/lib/data/local-store.ts b/apps/mana/apps/web/src/lib/data/local-store.ts index 2cee8f121..973750001 100644 --- a/apps/mana/apps/web/src/lib/data/local-store.ts +++ b/apps/mana/apps/web/src/lib/data/local-store.ts @@ -74,21 +74,18 @@ function createCollectionWrapper(tableName: string) { await table.put({ ...record, createdAt: record.createdAt ?? now, - updatedAt: now, }); }, async update(id: string, changes: Partial): Promise { await table.update(id, { ...changes, - updatedAt: new Date().toISOString(), } as any); }, async delete(id: string): Promise { await table.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), } as any); }, diff --git a/apps/mana/apps/web/src/lib/data/seeds/workbench-home.ts b/apps/mana/apps/web/src/lib/data/seeds/workbench-home.ts index a7c524771..485b12b72 100644 --- a/apps/mana/apps/web/src/lib/data/seeds/workbench-home.ts +++ b/apps/mana/apps/web/src/lib/data/seeds/workbench-home.ts @@ -65,7 +65,6 @@ export async function seedWorkbenchHomeOn( openApps: DEFAULT_HOME_APPS, order: 0, createdAt: now, - updatedAt: now, spaceId, }; await table.add(row); diff --git a/apps/mana/apps/web/src/lib/data/sync.ts b/apps/mana/apps/web/src/lib/data/sync.ts index 53dd7a6ce..e5517093d 100644 --- a/apps/mana/apps/web/src/lib/data/sync.ts +++ b/apps/mana/apps/web/src/lib/data/sync.ts @@ -21,6 +21,7 @@ import { fromSyncName, beginApplyingTables, FIELD_META_KEY, + UPDATED_AT_INDEX_KEY, setPendingChangeListener, } from './database'; import { isQuotaError, cleanupTombstones, notifyQuotaExceeded } from './quota'; @@ -44,6 +45,30 @@ export function readFieldMeta(record: unknown): Record { return fm && typeof fm === 'object' ? (fm as Record) : {}; } +/** + * Derive a record's "last modified" timestamp from its `__fieldMeta` + * map. Returns the max `at` across all per-field entries — empty + * string when the record has no field-meta yet (only seen on records + * mid-write or pre-hook). + * + * Replaces the older synced `updatedAt` data field. F3 of the + * sync-field-meta overhaul moved updatedAt from a wire-protocol + * property to a read-side computed property — type converters call + * this to populate the public-facing `updatedAt` from the merged + * field-meta map. + * + * For Dexie indexed sorting, prefer the non-synced `_updatedAtIndex` + * shadow column the creating/updating hook stamps on every write. + */ +export function deriveUpdatedAt(record: unknown): string { + const meta = readFieldMeta(record); + let max = ''; + for (const fm of Object.values(meta)) { + if (fm.at > max) max = fm.at; + } + return max; +} + // ─── Types ──────────────────────────────────────────────────── /** Operations the sync protocol supports. */ @@ -370,12 +395,11 @@ export async function applyServerChanges( const tombActor: Actor = change.actor ?? USER_ACTOR; await table.update(recordId, { deletedAt: serverTime, - updatedAt: serverTime, [FIELD_META_KEY]: { ...localMeta, deletedAt: makeFieldMeta(serverTime, tombActor, replayOrigin), - updatedAt: makeFieldMeta(serverTime, tombActor, replayOrigin), }, + [UPDATED_AT_INDEX_KEY]: serverTime, }); } } else { @@ -405,17 +429,16 @@ export async function applyServerChanges( ...changeData, id: recordId, [FIELD_META_KEY]: fieldMeta, + [UPDATED_AT_INDEX_KEY]: recordTime, }); } else { const localMeta = readFieldMeta(existing); - const localUpdatedAt = - ((existing as Record).updatedAt as string | undefined) ?? ''; const updates: Record = {}; const newMeta: Record = { ...localMeta }; for (const [key, val] of Object.entries(changeData)) { if (key === 'id' || key === FIELD_META_KEY) continue; - const localFieldTime = localMeta[key]?.at ?? localUpdatedAt; + const localFieldTime = localMeta[key]?.at ?? ''; if (recordTime >= localFieldTime) { // Conflict signal: server STRICTLY wins (>), the local // field had a non-empty value that differs from the new @@ -448,6 +471,7 @@ export async function applyServerChanges( } if (Object.keys(updates).length > 0) { updates[FIELD_META_KEY] = newMeta; + updates[UPDATED_AT_INDEX_KEY] = recordTime; await table.update(recordId, updates); } } @@ -464,23 +488,26 @@ export async function applyServerChanges( const record: Record = { id: recordId }; const fieldMeta: Record = {}; const fallback = new Date().toISOString(); + let maxAt = ''; for (const [key, fc] of Object.entries(serverFields)) { record[key] = fc.value; - fieldMeta[key] = makeFieldMeta(fc.at ?? fallback, changeActor, replayOrigin); + const at = fc.at ?? fallback; + fieldMeta[key] = makeFieldMeta(at, changeActor, replayOrigin); + if (at > maxAt) maxAt = at; } record[FIELD_META_KEY] = fieldMeta; + record[UPDATED_AT_INDEX_KEY] = maxAt || fallback; await table.put(record); } else { // Per-field comparison. const localMeta = readFieldMeta(existing); - const localUpdatedAt = - ((existing as Record).updatedAt as string | undefined) ?? ''; const updates: Record = {}; const newMeta: Record = { ...localMeta }; + let maxApplied = ''; for (const [key, fc] of Object.entries(serverFields)) { const serverTime = fc.at ?? ''; - const localFieldTime = localMeta[key]?.at ?? localUpdatedAt; + const localFieldTime = localMeta[key]?.at ?? ''; if (serverTime >= localFieldTime) { // Same conflict criteria as the insert-as-update path: // strictly newer + non-empty local + actually different @@ -506,10 +533,12 @@ export async function applyServerChanges( } updates[key] = fc.value; newMeta[key] = makeFieldMeta(serverTime, changeActor, replayOrigin); + if (serverTime > maxApplied) maxApplied = serverTime; } } if (Object.keys(updates).length > 0) { updates[FIELD_META_KEY] = newMeta; + if (maxApplied) updates[UPDATED_AT_INDEX_KEY] = maxApplied; await table.update(recordId, updates); } } diff --git a/apps/mana/apps/web/src/lib/data/tag-presets/store.svelte.ts b/apps/mana/apps/web/src/lib/data/tag-presets/store.svelte.ts index b210e9a97..25fa7090c 100644 --- a/apps/mana/apps/web/src/lib/data/tag-presets/store.svelte.ts +++ b/apps/mana/apps/web/src/lib/data/tag-presets/store.svelte.ts @@ -70,7 +70,7 @@ async function clearDefaultFlag(userId: string, exceptId?: string): Promise p.isDefault && p.id !== exceptId) .toArray(); for (const row of rows) { - await table.update(row.id, { isDefault: false, updatedAt: now() }); + await table.update(row.id, { isDefault: false }); } } @@ -85,7 +85,6 @@ export const tagPresetsStore = { isDefault: input.isDefault ?? false, tags: input.tags ?? [], createdAt: timestamp, - updatedAt: timestamp, }; if (newLocal.isDefault) await clearDefaultFlag(userId, newLocal.id); @@ -108,21 +107,20 @@ export const tagPresetsStore = { ...(input.name !== undefined && { name: input.name }), ...(input.tags !== undefined && { tags: input.tags }), ...(input.isDefault !== undefined && { isDefault: input.isDefault }), - updatedAt: now(), }; await encryptRecord('userTagPresets', diff); await table.update(id, diff); }, async deletePreset(id: string): Promise { - await table.update(id, { deletedAt: now(), updatedAt: now() }); + await table.update(id, { deletedAt: now() }); }, async setDefault(id: string): Promise { const existing = await table.get(id); if (!existing) throw new Error(`Preset ${id} not found`); await clearDefaultFlag(existing.userId, id); - await table.update(id, { isDefault: true, updatedAt: now() }); + await table.update(id, { isDefault: true }); }, /** diff --git a/apps/mana/apps/web/src/lib/data/tag-presets/types.ts b/apps/mana/apps/web/src/lib/data/tag-presets/types.ts index 0497f031e..aa8e6124f 100644 --- a/apps/mana/apps/web/src/lib/data/tag-presets/types.ts +++ b/apps/mana/apps/web/src/lib/data/tag-presets/types.ts @@ -1,3 +1,4 @@ +import { deriveUpdatedAt } from '$lib/data/sync'; /** * User-level tag presets. * @@ -33,11 +34,15 @@ export interface LocalUserTagPreset { isDefault: boolean; tags: TagPresetEntry[]; createdAt: string; - updatedAt: string; deletedAt?: string; } -export type UserTagPreset = Omit; +export type UserTagPreset = Omit & { + /** Computed read-side property: max(__fieldMeta[*].at). The Local + * record stamps `_updatedAtIndex` for sort indexes; consumers see + * `updatedAt` here via {@link toUserTagPreset}. */ + updatedAt: string; +}; export function toUserTagPreset(local: LocalUserTagPreset): UserTagPreset { return { @@ -47,7 +52,7 @@ export function toUserTagPreset(local: LocalUserTagPreset): UserTagPreset { isDefault: local.isDefault, tags: local.tags ?? [], createdAt: local.createdAt, - updatedAt: local.updatedAt, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/data/time-blocks/collections.ts b/apps/mana/apps/web/src/lib/data/time-blocks/collections.ts index c7f251773..6582ea931 100644 --- a/apps/mana/apps/web/src/lib/data/time-blocks/collections.ts +++ b/apps/mana/apps/web/src/lib/data/time-blocks/collections.ts @@ -58,7 +58,6 @@ export const TIME_BLOCK_GUEST_SEED = { icon: null, projectId: null, createdAt: nowISO, - updatedAt: nowISO, }, { id: 'sample-tb-event-2', @@ -79,7 +78,6 @@ export const TIME_BLOCK_GUEST_SEED = { icon: null, projectId: null, createdAt: nowISO, - updatedAt: nowISO, }, ] satisfies LocalTimeBlock[]; })(), diff --git a/apps/mana/apps/web/src/lib/data/time-blocks/queries.ts b/apps/mana/apps/web/src/lib/data/time-blocks/queries.ts index 4c15f7642..5450c60e5 100644 --- a/apps/mana/apps/web/src/lib/data/time-blocks/queries.ts +++ b/apps/mana/apps/web/src/lib/data/time-blocks/queries.ts @@ -9,6 +9,7 @@ */ import { liveQuery } from 'dexie'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; import { db } from '$lib/data/database'; import { decryptRecords } from '$lib/data/crypto'; @@ -46,7 +47,7 @@ export function toTimeBlock(local: LocalTimeBlock): TimeBlock { icon: local.icon ?? null, projectId: local.projectId ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts b/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts index 805bae8f6..8be2eed2d 100644 --- a/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts +++ b/apps/mana/apps/web/src/lib/data/time-blocks/recurrence.ts @@ -10,6 +10,7 @@ */ import { RRule } from 'rrule'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { timeBlockTable } from './collections'; import { createBlock, deleteBlock } from './service'; @@ -311,7 +312,7 @@ export function expandTemplatesVirtually( linkedBlockId: null, isVirtual: true, createdAt: template.createdAt ?? new Date().toISOString(), - updatedAt: template.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(template), }); } } diff --git a/apps/mana/apps/web/src/lib/data/time-blocks/service.ts b/apps/mana/apps/web/src/lib/data/time-blocks/service.ts index 4b06fc28e..5a1a5cdc6 100644 --- a/apps/mana/apps/web/src/lib/data/time-blocks/service.ts +++ b/apps/mana/apps/web/src/lib/data/time-blocks/service.ts @@ -45,7 +45,6 @@ export async function createBlock(input: CreateTimeBlockInput): Promise icon: input.icon ?? null, projectId: input.projectId ?? null, createdAt: now, - updatedAt: now, }; // Encrypt configured fields (title + description) before write. @@ -60,7 +59,6 @@ export async function updateBlock(id: string, input: UpdateTimeBlockInput): Prom const now = new Date().toISOString(); const diff: Partial = { ...input, - updatedAt: now, }; await encryptRecord('timeBlocks', diff); await timeBlockTable.update(id, diff); @@ -71,7 +69,6 @@ export async function deleteBlock(id: string): Promise { const now = new Date().toISOString(); await timeBlockTable.update(id, { deletedAt: now, - updatedAt: now, }); } @@ -85,8 +82,8 @@ export async function linkBlocks(scheduledId: string, loggedId: string): Promise if (scheduled.kind !== 'scheduled') throw new Error('First block must be scheduled'); if (logged.kind !== 'logged') throw new Error('Second block must be logged'); - await timeBlockTable.update(scheduledId, { linkedBlockId: loggedId, updatedAt: now }); - await timeBlockTable.update(loggedId, { linkedBlockId: scheduledId, updatedAt: now }); + await timeBlockTable.update(scheduledId, { linkedBlockId: loggedId }); + await timeBlockTable.update(loggedId, { linkedBlockId: scheduledId }); }); } @@ -125,7 +122,7 @@ export async function startFromScheduled( }); // Link back from scheduled → logged - await timeBlockTable.update(scheduledId, { linkedBlockId: loggedId, updatedAt: now }); + await timeBlockTable.update(scheduledId, { linkedBlockId: loggedId }); return loggedId; } diff --git a/apps/mana/apps/web/src/lib/modules/articles/migrations/from-news.ts b/apps/mana/apps/web/src/lib/modules/articles/migrations/from-news.ts index 4b670ccd1..4fdf832cf 100644 --- a/apps/mana/apps/web/src/lib/modules/articles/migrations/from-news.ts +++ b/apps/mana/apps/web/src/lib/modules/articles/migrations/from-news.ts @@ -138,7 +138,7 @@ export async function runArticlesFromNewsMigration(): Promise { // the server + other devices. Keep it in the local table so // if someone later rolls back the migration they can still // see what was there. - await newsTable.update(row.id, { deletedAt: now, updatedAt: now }); + await newsTable.update(row.id, { deletedAt: now }); moved++; } catch (rowErr) { console.warn(`[articles/from-news] skipping row ${row.id} — ${(rowErr as Error).message}`); diff --git a/apps/mana/apps/web/src/lib/modules/articles/queries.ts b/apps/mana/apps/web/src/lib/modules/articles/queries.ts index 586de1b5a..a8ce472cd 100644 --- a/apps/mana/apps/web/src/lib/modules/articles/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/articles/queries.ts @@ -7,6 +7,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { scopedForModule, scopedGet } from '$lib/data/scope'; import { articleTagOps } from './stores/tags.svelte'; @@ -37,7 +38,7 @@ export function toArticle(local: LocalArticle): Article { userNote: local.userNote ?? null, extractedVersion: local.extractedVersion ?? 1, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -54,7 +55,7 @@ export function toHighlight(local: LocalHighlight): Highlight { contextBefore: local.contextBefore ?? null, contextAfter: local.contextAfter ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/articles/stores/articles.svelte.ts b/apps/mana/apps/web/src/lib/modules/articles/stores/articles.svelte.ts index c584b110b..348faa0e2 100644 --- a/apps/mana/apps/web/src/lib/modules/articles/stores/articles.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/articles/stores/articles.svelte.ts @@ -20,7 +20,6 @@ export const articlesStore = { async setStatus(id: string, status: ArticleStatus): Promise { const diff: Partial = { status, - updatedAt: new Date().toISOString(), }; if (status === 'finished') { const existing = await articleTable.get(id); @@ -34,7 +33,6 @@ export const articlesStore = { if (!existing) return; await articleTable.update(id, { isFavorite: !existing.isFavorite, - updatedAt: new Date().toISOString(), }); }, @@ -42,14 +40,12 @@ export const articlesStore = { const clamped = Math.max(0, Math.min(1, progress)); await articleTable.update(id, { readingProgress: clamped, - updatedAt: new Date().toISOString(), }); }, async updateNote(id: string, note: string | null): Promise { const diff: Partial = { userNote: note, - updatedAt: new Date().toISOString(), }; await encryptRecord('articles', diff as LocalArticle); await articleTable.update(id, diff); @@ -58,7 +54,6 @@ export const articlesStore = { async deleteArticle(id: string): Promise { await articleTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, diff --git a/apps/mana/apps/web/src/lib/modules/articles/stores/highlights.svelte.ts b/apps/mana/apps/web/src/lib/modules/articles/stores/highlights.svelte.ts index 79a7a598c..a72b1cfc7 100644 --- a/apps/mana/apps/web/src/lib/modules/articles/stores/highlights.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/articles/stores/highlights.svelte.ts @@ -44,14 +44,12 @@ export const highlightsStore = { async setColor(id: string, color: HighlightColor): Promise { await articleHighlightTable.update(id, { color, - updatedAt: new Date().toISOString(), }); }, async setNote(id: string, note: string | null): Promise { const diff: Partial = { note, - updatedAt: new Date().toISOString(), }; await encryptRecord('articleHighlights', diff as LocalHighlight); await articleHighlightTable.update(id, diff); @@ -60,7 +58,6 @@ export const highlightsStore = { async deleteHighlight(id: string): Promise { await articleHighlightTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/augur/queries.ts b/apps/mana/apps/web/src/lib/modules/augur/queries.ts index 150abbf16..54d2fd058 100644 --- a/apps/mana/apps/web/src/lib/modules/augur/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/augur/queries.ts @@ -1,4 +1,5 @@ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; import { isDue } from './lib/reminders'; @@ -32,7 +33,7 @@ export function toAugurEntry(local: LocalAugurEntry): AugurEntry { unlistedToken: local.unlistedToken ?? '', unlistedExpiresAt: local.unlistedExpiresAt ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/augur/stores/entries.svelte.ts b/apps/mana/apps/web/src/lib/modules/augur/stores/entries.svelte.ts index 37a706e15..f9f496f65 100644 --- a/apps/mana/apps/web/src/lib/modules/augur/stores/entries.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/augur/stores/entries.svelte.ts @@ -95,7 +95,6 @@ export const augurStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('augurEntries', diff); await augurEntriesTable.update(id, diff); @@ -106,7 +105,6 @@ export const augurStore = { outcome, outcomeNote: note ?? null, resolvedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await encryptRecord('augurEntries', diff); await augurEntriesTable.update(id, diff); @@ -115,7 +113,6 @@ export const augurStore = { async archiveEntry(id: string) { await augurEntriesTable.update(id, { isArchived: true, - updatedAt: new Date().toISOString(), }); }, @@ -141,7 +138,6 @@ export const augurStore = { await augurEntriesTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -161,7 +157,6 @@ export const augurStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId() ?? undefined, - updatedAt: now, }; if (next === 'unlisted') { @@ -230,7 +225,6 @@ export const augurStore = { }); await augurEntriesTable.update(id, { unlistedToken: token, - updatedAt: new Date().toISOString(), }); return token; } catch (e) { @@ -264,7 +258,6 @@ export const augurStore = { }); await augurEntriesTable.update(id, { unlistedExpiresAt: expiresAt ? expiresAt.toISOString() : null, - updatedAt: new Date().toISOString(), }); } catch (e) { console.error('[augur] setUnlistedExpiry failed', e); diff --git a/apps/mana/apps/web/src/lib/modules/automations/queries.ts b/apps/mana/apps/web/src/lib/modules/automations/queries.ts index 9ac32a158..a7ec09199 100644 --- a/apps/mana/apps/web/src/lib/modules/automations/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/automations/queries.ts @@ -5,6 +5,7 @@ */ import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { automationTable } from './collections'; import type { LocalAutomation } from './types'; @@ -42,7 +43,7 @@ export function toAutomation(local: LocalAutomation): Automation { targetAction: local.targetAction, targetParams: local.targetParams, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/automations/stores/automations.svelte.ts b/apps/mana/apps/web/src/lib/modules/automations/stores/automations.svelte.ts index 1ff3fa9ee..24379f4b4 100644 --- a/apps/mana/apps/web/src/lib/modules/automations/stores/automations.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/automations/stores/automations.svelte.ts @@ -37,7 +37,6 @@ export const automationsStore = { targetAction: data.targetAction, targetParams: data.targetParams, createdAt: now, - updatedAt: now, }; await automationTable.add(auto); await loadAutomations(); @@ -47,7 +46,6 @@ export const automationsStore = { async update(id: string, data: Partial) { await automationTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); await loadAutomations(); }, @@ -57,7 +55,6 @@ export const automationsStore = { if (!auto) return; await automationTable.update(id, { enabled: !auto.enabled, - updatedAt: new Date().toISOString(), }); await loadAutomations(); }, @@ -65,7 +62,6 @@ export const automationsStore = { async remove(id: string) { await automationTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); await loadAutomations(); }, diff --git a/apps/mana/apps/web/src/lib/modules/body/queries.ts b/apps/mana/apps/web/src/lib/modules/body/queries.ts index 217ffebd3..fdc617151 100644 --- a/apps/mana/apps/web/src/lib/modules/body/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/body/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -39,7 +40,7 @@ export function toBodyExercise(local: LocalBodyExercise): BodyExercise { isArchived: local.isArchived, isPreset: local.isPreset, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -53,7 +54,7 @@ export function toBodyRoutine(local: LocalBodyRoutine): BodyRoutine { order: local.order, isArchived: local.isArchived, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -68,7 +69,7 @@ export function toBodyWorkout(local: LocalBodyWorkout): BodyWorkout { notes: local.notes ?? null, rpe: local.rpe ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -124,7 +125,7 @@ export function toBodyPhase(local: LocalBodyPhase): BodyPhase { targetWeight: local.targetWeight ?? null, notes: local.notes ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts b/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts index 0cd801527..00150f63b 100644 --- a/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/body/stores/body.svelte.ts @@ -82,7 +82,6 @@ export const bodyStore = { const wrapped = await encryptRecord('bodyExercises', { ...patch }); await bodyExerciseTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -95,7 +94,6 @@ export const bodyStore = { if (!exercise || exercise.isPreset) return; await bodyExerciseTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -128,14 +126,12 @@ export const bodyStore = { const wrapped = await encryptRecord('bodyRoutines', { ...patch }); await bodyRoutineTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, async deleteRoutine(id: string) { await bodyRoutineTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -202,7 +198,6 @@ export const bodyStore = { const wrapped = await encryptRecord('bodyWorkouts', { ...update }); await bodyWorkoutTable.update(id, { ...wrapped, - updatedAt: now, }); // Stamp the TimeBlock's endDate so the calendar shows duration. @@ -229,7 +224,6 @@ export const bodyStore = { const wrapped = await encryptRecord('bodyWorkouts', { ...patch }); await bodyWorkoutTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -238,7 +232,7 @@ export const bodyStore = { // stop counting them. Also remove the linked TimeBlock. const workout = await bodyWorkoutTable.get(id); const now = new Date().toISOString(); - await bodyWorkoutTable.update(id, { deletedAt: now, updatedAt: now }); + await bodyWorkoutTable.update(id, { deletedAt: now }); const sets = await bodySetTable.where('workoutId').equals(id).toArray(); for (const s of sets) { await bodySetTable.update(s.id, { deletedAt: now }); @@ -373,7 +367,6 @@ export const bodyStore = { const wrapped = await encryptRecord('bodyChecks', { ...patch }); await bodyCheckTable.update(existing.id, { ...wrapped, - updatedAt: new Date().toISOString(), }); return toBodyCheck({ ...existing, ...patch }); } @@ -437,7 +430,6 @@ export const bodyStore = { async endPhase(id: string) { await bodyPhaseTable.update(id, { endDate: new Date().toISOString().split('T')[0], - updatedAt: new Date().toISOString(), }); }, @@ -453,14 +445,12 @@ export const bodyStore = { const wrapped = await encryptRecord('bodyPhases', { ...patch }); await bodyPhaseTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, async deletePhase(id: string) { await bodyPhaseTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/broadcast/queries.ts b/apps/mana/apps/web/src/lib/modules/broadcast/queries.ts index c2130278e..b1de77330 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcast/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcast/queries.ts @@ -7,6 +7,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { scopedForModule } from '$lib/data/scope'; import { campaignTable, templateTable, settingsTable } from './collections'; @@ -42,7 +43,7 @@ export function toCampaign(local: LocalCampaign): Campaign { serverJobId: local.serverJobId ?? null, stats: local.stats ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/broadcast/stores/campaigns.svelte.ts b/apps/mana/apps/web/src/lib/modules/broadcast/stores/campaigns.svelte.ts index 96f12adf7..5d5fe8fba 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcast/stores/campaigns.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcast/stores/campaigns.svelte.ts @@ -66,7 +66,6 @@ export const broadcastCampaignsStore = { serverJobId: null, stats: null, createdAt: now, - updatedAt: now, }; await encryptRecord('broadcastCampaigns', newLocal); @@ -99,10 +98,7 @@ export const broadcastCampaignsStore = { } const wrapped = { ...patch } as Record; await encryptRecord('broadcastCampaigns', wrapped); - await campaignTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await campaignTable.update(id, wrapped as never); }, /** @@ -119,10 +115,7 @@ export const broadcastCampaignsStore = { } const patch = { content } as Record; await encryptRecord('broadcastCampaigns', patch); - await campaignTable.update(id, { - ...patch, - updatedAt: new Date().toISOString(), - }); + await campaignTable.update(id, patch as never); }, async updateAudience(id: string, audience: AudienceDefinition) { @@ -133,10 +126,7 @@ export const broadcastCampaignsStore = { } const patch = { audience } as Record; await encryptRecord('broadcastCampaigns', patch); - await campaignTable.update(id, { - ...patch, - updatedAt: new Date().toISOString(), - }); + await campaignTable.update(id, patch as never); }, /** @@ -151,7 +141,6 @@ export const broadcastCampaignsStore = { await campaignTable.update(id, { status: 'scheduled' as CampaignStatus, scheduledAt, - updatedAt: new Date().toISOString(), }); emitDomainEvent('BroadcastCampaignScheduled', 'broadcast', 'broadcastCampaigns', id, { campaignId: id, @@ -169,7 +158,6 @@ export const broadcastCampaignsStore = { await campaignTable.update(id, { status: 'cancelled' as CampaignStatus, scheduledAt: null, - updatedAt: new Date().toISOString(), }); emitDomainEvent('BroadcastCampaignCancelled', 'broadcast', 'broadcastCampaigns', id, { campaignId: id, @@ -209,7 +197,6 @@ export const broadcastCampaignsStore = { } await campaignTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('BroadcastCampaignDeleted', 'broadcast', 'broadcastCampaigns', id, { campaignId: id, @@ -232,7 +219,6 @@ export const broadcastCampaignsStore = { ) { await campaignTable.update(id, { ...patch, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/broadcast/stores/settings.svelte.ts b/apps/mana/apps/web/src/lib/modules/broadcast/stores/settings.svelte.ts index 3303792cf..b9b28b9ca 100644 --- a/apps/mana/apps/web/src/lib/modules/broadcast/stores/settings.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/broadcast/stores/settings.svelte.ts @@ -50,7 +50,6 @@ export const broadcastSettingsStore = { await encryptRecord('broadcastSettings', wrapped); await settingsTable.update(BROADCAST_SETTINGS_ID, { ...wrapped, - updatedAt: new Date().toISOString(), }); emitDomainEvent( 'BroadcastSettingsUpdated', diff --git a/apps/mana/apps/web/src/lib/modules/calc/queries.ts b/apps/mana/apps/web/src/lib/modules/calc/queries.ts index 500b9f12a..079c97387 100644 --- a/apps/mana/apps/web/src/lib/modules/calc/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/calc/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import type { LocalCalculation, LocalSavedFormula } from './types'; @@ -31,7 +32,7 @@ export function toSavedFormula(local: LocalSavedFormula): SavedFormula { description: local.description ?? undefined, mode: local.mode, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts b/apps/mana/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts index 60da43def..947eeb1d6 100644 --- a/apps/mana/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts @@ -16,7 +16,6 @@ export const calculationsStore = { result: input.result, skin: input.skin, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); CalcEvents.calculationAdded(); }, @@ -24,7 +23,6 @@ export const calculationsStore = { async deleteCalculation(id: string) { await db.table('calculations').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -32,9 +30,7 @@ export const calculationsStore = { const now = new Date().toISOString(); const all = await db.table('calculations').toArray(); const active = all.filter((c) => !c.deletedAt); - await Promise.all( - active.map((c) => db.table('calculations').update(c.id, { deletedAt: now, updatedAt: now })) - ); + await Promise.all(active.map((c) => db.table('calculations').update(c.id, { deletedAt: now }))); CalcEvents.historyCleared(); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts b/apps/mana/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts index 79bae1ed8..de2f162c9 100644 --- a/apps/mana/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts @@ -16,7 +16,6 @@ export const savedFormulasStore = { description: input.description ?? null, mode: input.mode, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); CalcEvents.formulaSaved(); }, @@ -24,14 +23,12 @@ export const savedFormulasStore = { async updateFormula(id: string, input: UpdateFormulaInput) { await db.table('savedFormulas').update(id, { ...input, - updatedAt: new Date().toISOString(), }); }, async deleteFormula(id: string) { await db.table('savedFormulas').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); CalcEvents.formulaDeleted(); }, diff --git a/apps/mana/apps/web/src/lib/modules/calendar/queries.ts b/apps/mana/apps/web/src/lib/modules/calendar/queries.ts index 74ee996c2..5d997edf5 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/queries.ts @@ -11,6 +11,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule, applyVisibility } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -33,7 +34,7 @@ export function toCalendar(local: LocalCalendar): Calendar { isVisible: local.isVisible, timezone: local.timezone, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/calendar/stores/calendars.svelte.ts b/apps/mana/apps/web/src/lib/modules/calendar/stores/calendars.svelte.ts index 2307fd931..2add8f450 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/stores/calendars.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/stores/calendars.svelte.ts @@ -38,7 +38,6 @@ export const calendarsStore = { isVisible: input.isVisible ?? true, timezone: input.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await db.table('calendars').add(newLocal); @@ -57,7 +56,6 @@ export const calendarsStore = { try { await db.table('calendars').update(id, { ...input, - updatedAt: new Date().toISOString(), }); const updated = await db.table('calendars').get(id); if (updated) { @@ -78,7 +76,6 @@ export const calendarsStore = { try { await db.table('calendars').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); return { success: true }; } catch (e) { @@ -106,13 +103,11 @@ export const calendarsStore = { if (cal.isDefault && cal.id !== id) { await db.table('calendars').update(cal.id, { isDefault: false, - updatedAt: new Date().toISOString(), }); } } await db.table('calendars').update(id, { isDefault: true, - updatedAt: new Date().toISOString(), }); const updated = await db.table('calendars').get(id); return { success: true, data: updated ? toCalendar(updated) : null }; diff --git a/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts b/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts index 490d4d0c9..4fd7166e7 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/stores/events.svelte.ts @@ -89,7 +89,6 @@ export const eventsStore = { reminders: null, visibility: defaultVisibilityFor(getActiveSpace()?.type), createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; // title/description/location are encrypted at rest. createBlock @@ -154,9 +153,7 @@ export const eventsStore = { } // Update LocalEvent for domain fields - const localData: Partial = { - updatedAt: new Date().toISOString(), - }; + const localData: Partial = {}; if (input.title !== undefined) localData.title = input.title; if (input.description !== undefined) localData.description = input.description; if (input.location !== undefined) localData.location = input.location; @@ -213,7 +210,7 @@ export const eventsStore = { await updateBlock(event.timeBlockId, blockUpdates); // Update LocalEvent - const localData: Partial = { updatedAt: new Date().toISOString() }; + const localData: Partial = {}; if (input.title !== undefined) localData.title = input.title; if (input.description !== undefined) localData.description = input.description; if (input.location !== undefined) localData.location = input.location; @@ -277,7 +274,7 @@ export const eventsStore = { .equals(templateBlockId) .first(); if (templateEvent) { - const localData: Partial = { updatedAt: new Date().toISOString() }; + const localData: Partial = {}; if (input.title !== undefined) localData.title = input.title; if (input.description !== undefined) localData.description = input.description; if (input.location !== undefined) localData.location = input.location; @@ -308,7 +305,6 @@ export const eventsStore = { } await db.table('events').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); CalendarEvents.eventDeleted(); return { success: true }; @@ -343,7 +339,7 @@ export const eventsStore = { const now = new Date().toISOString(); for (const ev of allEvents) { if (blockIds.has(ev.timeBlockId) && !ev.deletedAt) { - await db.table('events').update(ev.id, { deletedAt: now, updatedAt: now }); + await db.table('events').update(ev.id, { deletedAt: now }); } } @@ -391,7 +387,6 @@ export const eventsStore = { await db.table('events').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('CalendarEventDeleted', 'calendar', 'events', id, { eventId: id, @@ -412,7 +407,6 @@ export const eventsStore = { async updateTagIds(id: string, tagIds: string[]) { await db.table('events').update(id, { tagIds, - updatedAt: new Date().toISOString(), }); }, @@ -503,7 +497,6 @@ export const eventsStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; // Server-authoritative token. Publish first; local update only @@ -590,7 +583,6 @@ export const eventsStore = { }); await db.table('events').update(id, { unlistedToken: token, - updatedAt: new Date().toISOString(), }); return { success: true, token }; } catch (e) { @@ -625,7 +617,6 @@ export const eventsStore = { }); await db.table('events').update(id, { unlistedExpiresAt: expiresAt ? expiresAt.toISOString() : undefined, - updatedAt: new Date().toISOString(), }); } catch (e) { console.error('[calendar/events] setUnlistedExpiry failed', e); diff --git a/apps/mana/apps/web/src/lib/modules/calendar/types.ts b/apps/mana/apps/web/src/lib/modules/calendar/types.ts index 6529227b4..18b99c436 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/types.ts +++ b/apps/mana/apps/web/src/lib/modules/calendar/types.ts @@ -6,6 +6,7 @@ */ import type { BaseRecord } from '@mana/local-store'; +import { deriveUpdatedAt } from '$lib/data/sync'; import type { VisibilityLevel } from '@mana/shared-privacy'; import type { TimeBlock, TimeBlockType } from '$lib/data/time-blocks/types'; @@ -120,7 +121,7 @@ export function timeBlockToCalendarEvent( unlistedToken: eventData?.unlistedToken ?? '', unlistedExpiresAt: eventData?.unlistedExpiresAt ?? null, createdAt: block.createdAt, - updatedAt: block.updatedAt, + updatedAt: deriveUpdatedAt(block), blockType: block.type, sourceModule: block.sourceModule, sourceId: block.sourceId, diff --git a/apps/mana/apps/web/src/lib/modules/cards/queries.ts b/apps/mana/apps/web/src/lib/modules/cards/queries.ts index 5ecbd4741..1bdf613e0 100644 --- a/apps/mana/apps/web/src/lib/modules/cards/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/cards/queries.ts @@ -5,6 +5,7 @@ */ import { liveQuery } from 'dexie'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecord, decryptRecords } from '$lib/data/crypto'; @@ -22,7 +23,7 @@ export function toDeck(local: LocalDeck): Deck { tags: [], cardCount: local.cardCount, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -37,7 +38,7 @@ export function toCard(local: LocalCard): Card { reviewCount: local.reviewCount, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts index 586c255e5..762073c7a 100644 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts @@ -41,7 +41,6 @@ export const cardStore = { if (deck) { await cardDeckTable.update(input.deckId, { cardCount: (deck.cardCount || 0) + 1, - updatedAt: new Date().toISOString(), }); } @@ -69,7 +68,6 @@ export const cardStore = { const diff: Partial = { ...localUpdates, - updatedAt: new Date().toISOString(), }; await encryptRecord('cards', diff); await cardTable.update(id, diff); @@ -83,7 +81,7 @@ export const cardStore = { error = null; try { const now = new Date().toISOString(); - await cardTable.update(id, { deletedAt: now, updatedAt: now }); + await cardTable.update(id, { deletedAt: now }); CardsEvents.cardDeleted(); // Update deck card count @@ -92,7 +90,6 @@ export const cardStore = { if (deck) { await cardDeckTable.update(deckId, { cardCount: Math.max(0, (deck.cardCount || 0) - 1), - updatedAt: now, }); } } @@ -107,7 +104,7 @@ export const cardStore = { try { const now = new Date().toISOString(); for (let i = 0; i < cardIds.length; i++) { - await cardTable.update(cardIds[i], { order: i, updatedAt: now }); + await cardTable.update(cardIds[i], { order: i }); } } catch (err: any) { error = err.message || 'Failed to reorder cards'; diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/decks.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/decks.svelte.ts index dcac530d6..1f0ad27bb 100644 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/decks.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/cards/stores/decks.svelte.ts @@ -58,7 +58,6 @@ export const deckStore = { const diff: Partial = { ...localUpdates, - updatedAt: new Date().toISOString(), }; await encryptRecord('cardDecks', diff); await cardDeckTable.update(id, diff); @@ -105,9 +104,9 @@ export const deckStore = { await db.transaction('rw', cardDeckTable, cardTable, async () => { const cards = await cardTable.where('deckId').equals(id).toArray(); for (const card of cards) { - await cardTable.update(card.id, { deletedAt: now, updatedAt: now }); + await cardTable.update(card.id, { deletedAt: now }); } - await cardDeckTable.update(id, { deletedAt: now, updatedAt: now }); + await cardDeckTable.update(id, { deletedAt: now }); }); CardsEvents.deckDeleted(); } catch (err: any) { @@ -142,7 +141,6 @@ export const deckStore = { await cardDeckTable.update(deckId, { activeStudyBlockId: timeBlockId, lastStudied: now, - updatedAt: now, }); return timeBlockId; @@ -160,7 +158,6 @@ export const deckStore = { await cardDeckTable.update(deckId, { activeStudyBlockId: null, - updatedAt: now, }); }, diff --git a/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte index cacda7926..74ddc3433 100644 --- a/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte @@ -54,7 +54,6 @@ // Color is not in UpdateDeckInput, update directly await db.table('decks').update(deckId, { color: editColor, - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/modules/chat/queries.ts b/apps/mana/apps/web/src/lib/modules/chat/queries.ts index c2954018d..e86152314 100644 --- a/apps/mana/apps/web/src/lib/modules/chat/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/chat/queries.ts @@ -8,6 +8,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -34,7 +35,7 @@ export function toConversation(local: LocalConversation): Conversation { isArchived: local.isArchived, isPinned: local.isPinned, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -50,7 +51,7 @@ export function toTemplate(local: LocalTemplate): Template { isDefault: local.isDefault, documentMode: local.documentMode, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -61,7 +62,7 @@ export function toMessage(local: LocalMessage): Message { sender: local.sender, messageText: local.messageText, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? undefined, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts b/apps/mana/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts index 747699bfc..17c0fbc2a 100644 --- a/apps/mana/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts @@ -55,7 +55,6 @@ export const conversationsStore = { async update(id: string, updates: Partial) { const diff: Partial = { ...updates, - updatedAt: new Date().toISOString(), }; await encryptRecord('conversations', diff); await conversationTable.update(id, diff); @@ -65,7 +64,6 @@ export const conversationsStore = { async updateTitle(id: string, title: string) { const diff: Partial = { title, - updatedAt: new Date().toISOString(), }; await encryptRecord('conversations', diff); await conversationTable.update(id, diff); @@ -79,7 +77,6 @@ export const conversationsStore = { async pin(id: string) { await conversationTable.update(id, { isPinned: true, - updatedAt: new Date().toISOString(), }); }, @@ -87,7 +84,6 @@ export const conversationsStore = { async unpin(id: string) { await conversationTable.update(id, { isPinned: false, - updatedAt: new Date().toISOString(), }); }, @@ -97,10 +93,10 @@ export const conversationsStore = { // Atomic cascade: conversation + all messages in one Dexie transaction. // Aborts as a unit on failure to avoid orphaned messages. await db.transaction('rw', conversationTable, messageTable, async () => { - await conversationTable.update(id, { deletedAt: now, updatedAt: now }); + await conversationTable.update(id, { deletedAt: now }); const msgs = await messageTable.where('conversationId').equals(id).toArray(); for (const msg of msgs) { - await messageTable.update(msg.id, { deletedAt: now, updatedAt: now }); + await messageTable.update(msg.id, { deletedAt: now }); } }); ChatEvents.conversationDeleted(); diff --git a/apps/mana/apps/web/src/lib/modules/chat/stores/messages.svelte.ts b/apps/mana/apps/web/src/lib/modules/chat/stores/messages.svelte.ts index d0451b5e5..daadfa27c 100644 --- a/apps/mana/apps/web/src/lib/modules/chat/stores/messages.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/chat/stores/messages.svelte.ts @@ -33,9 +33,7 @@ export const messagesStore = { await encryptRecord('messages', newLocal); await messageTable.add(newLocal); // Touch the conversation's updatedAt - await conversationTable.update(conversationId, { - updatedAt: new Date().toISOString(), - }); + await conversationTable.update(conversationId, {}); emitDomainEvent('ChatMessageSent', 'chat', 'messages', newLocal.id, { messageId: newLocal.id, conversationId, @@ -55,9 +53,7 @@ export const messagesStore = { const plaintextSnapshot = toMessage(newLocal); await encryptRecord('messages', newLocal); await messageTable.add(newLocal); - await conversationTable.update(conversationId, { - updatedAt: new Date().toISOString(), - }); + await conversationTable.update(conversationId, {}); return plaintextSnapshot; }, @@ -65,7 +61,6 @@ export const messagesStore = { async updateText(id: string, text: string) { const diff: Partial = { messageText: text, - updatedAt: new Date().toISOString(), }; await encryptRecord('messages', diff); await messageTable.update(id, diff); @@ -74,6 +69,6 @@ export const messagesStore = { /** Soft-delete a message. */ async delete(id: string) { const now = new Date().toISOString(); - await messageTable.update(id, { deletedAt: now, updatedAt: now }); + await messageTable.update(id, { deletedAt: now }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/chat/stores/templates.svelte.ts b/apps/mana/apps/web/src/lib/modules/chat/stores/templates.svelte.ts index b345bc561..cd703835d 100644 --- a/apps/mana/apps/web/src/lib/modules/chat/stores/templates.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/chat/stores/templates.svelte.ts @@ -58,7 +58,6 @@ export const templatesStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('chatTemplates', diff); await chatTemplateTable.update(id, diff); @@ -67,7 +66,7 @@ export const templatesStore = { /** Soft-delete a template. */ async delete(id: string) { const now = new Date().toISOString(); - await chatTemplateTable.update(id, { deletedAt: now, updatedAt: now }); + await chatTemplateTable.update(id, { deletedAt: now }); }, /** Set a template as default (unset all others). */ @@ -77,13 +76,11 @@ export const templatesStore = { if (t.isDefault && t.id !== templateId) { await chatTemplateTable.update(t.id, { isDefault: false, - updatedAt: new Date().toISOString(), }); } } await chatTemplateTable.update(templateId, { isDefault: true, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts b/apps/mana/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts index 1ce7cfffd..d85344c8b 100644 --- a/apps/mana/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts @@ -29,7 +29,6 @@ export const favoritesStore = { if (existing) { await db.table('ccFavorites').update(existing.id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); CityCornersEvents.favoriteToggled(false); } else { @@ -37,7 +36,6 @@ export const favoritesStore = { id: crypto.randomUUID(), locationId, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await db.table('ccFavorites').add(newFav); CityCornersEvents.favoriteToggled(true); diff --git a/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte index 70e0ba425..560c56c8a 100644 --- a/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte @@ -51,14 +51,12 @@ category: editCategory, description: editDescription.trim() || undefined, address: editAddress.trim() || undefined, - updatedAt: new Date().toISOString(), }); } async function handleCategoryChange() { await db.table('ccLocations').update(locationId, { category: editCategory, - updatedAt: new Date().toISOString(), }); } @@ -69,7 +67,6 @@ async function deleteLocation() { await db.table('ccLocations').update(locationId, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/modules/comic/api/generate-character.ts b/apps/mana/apps/web/src/lib/modules/comic/api/generate-character.ts index 89b4b0305..24d92c4a0 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/api/generate-character.ts +++ b/apps/mana/apps/web/src/lib/modules/comic/api/generate-character.ts @@ -187,7 +187,6 @@ export async function runCharacterGenerate( referenceImageIds: referenceMediaIds, comicCharacterId: character.id, createdAt: nowIso, - updatedAt: nowIso, }); await comicCharactersStore.appendVariant(character.id, localImageId); diff --git a/apps/mana/apps/web/src/lib/modules/comic/api/generate-panel.ts b/apps/mana/apps/web/src/lib/modules/comic/api/generate-panel.ts index 3cfcdb833..776778b48 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/api/generate-panel.ts +++ b/apps/mana/apps/web/src/lib/modules/comic/api/generate-panel.ts @@ -220,7 +220,6 @@ export async function runPanelGenerate( comicStoryId: story.id, comicPanelIndex: panelIndex, createdAt: now, - updatedAt: now, }); await comicStoriesStore.appendPanel(story.id, localImageId, { diff --git a/apps/mana/apps/web/src/lib/modules/comic/stores/characters.svelte.ts b/apps/mana/apps/web/src/lib/modules/comic/stores/characters.svelte.ts index 4116cfbb7..07bf59e0a 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/stores/characters.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/comic/stores/characters.svelte.ts @@ -77,14 +77,13 @@ export const comicCharactersStore = { const nextIds = [...(existing.variantMediaIds ?? []), variantMediaId]; const patch: Partial = { variantMediaIds: nextIds, - updatedAt: new Date().toISOString(), }; // Auto-pin the first variant so the cover isn't blank during // build. User can re-pin afterwards. if (!existing.pinnedVariantId) { patch.pinnedVariantId = variantMediaId; } - await comicCharactersTable.update(characterId, patch); + await comicCharactersTable.update(characterId, patch as never); emitDomainEvent('ComicCharacterVariantAdded', 'comic', 'comicCharacters', characterId, { characterId, variantMediaId, @@ -103,7 +102,6 @@ export const comicCharactersStore = { } await comicCharactersTable.update(characterId, { pinnedVariantId: variantMediaId, - updatedAt: new Date().toISOString(), }); emitDomainEvent('ComicCharacterVariantPinned', 'comic', 'comicCharacters', characterId, { characterId, @@ -121,27 +119,23 @@ export const comicCharactersStore = { const nextIds = (existing.variantMediaIds ?? []).filter((id) => id !== variantMediaId); const patch: Partial = { variantMediaIds: nextIds, - updatedAt: new Date().toISOString(), }; if (existing.pinnedVariantId === variantMediaId) { patch.pinnedVariantId = nextIds[0] ?? null; } - await comicCharactersTable.update(characterId, patch); + await comicCharactersTable.update(characterId, patch as never); }, async updateCharacter( id: string, patch: Partial> ): Promise { - const wrapped: Record = { ...patch }; + const wrapped: Partial = { ...patch }; if (Array.isArray(wrapped.tags)) { - wrapped.tags = [...(wrapped.tags as string[])]; + wrapped.tags = [...wrapped.tags]; } - await encryptRecord('comicCharacters', wrapped); - await comicCharactersTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await encryptRecord('comicCharacters', wrapped as Record); + await comicCharactersTable.update(id, wrapped as never); }, async toggleFavorite(id: string): Promise { @@ -149,14 +143,12 @@ export const comicCharactersStore = { if (!existing) return; await comicCharactersTable.update(id, { isFavorite: !existing.isFavorite, - updatedAt: new Date().toISOString(), }); }, async archiveCharacter(id: string, archived: boolean): Promise { await comicCharactersTable.update(id, { isArchived: archived, - updatedAt: new Date().toISOString(), }); }, @@ -164,7 +156,6 @@ export const comicCharactersStore = { const nowIso = new Date().toISOString(); await comicCharactersTable.update(id, { deletedAt: nowIso, - updatedAt: nowIso, }); emitDomainEvent('ComicCharacterDeleted', 'comic', 'comicCharacters', id, { characterId: id, diff --git a/apps/mana/apps/web/src/lib/modules/comic/stores/stories.svelte.ts b/apps/mana/apps/web/src/lib/modules/comic/stores/stories.svelte.ts index dd377f972..1bc5937f5 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/stores/stories.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/comic/stores/stories.svelte.ts @@ -76,18 +76,15 @@ export const comicStoriesStore = { ): Promise { // Same proxy-breaking copy as createStory: any array on the patch // might be a $state proxy if the caller is a Svelte 5 component. - const wrapped: Record = { ...patch }; + const wrapped: Partial = { ...patch }; if (Array.isArray(wrapped.characterMediaIds)) { - wrapped.characterMediaIds = [...(wrapped.characterMediaIds as string[])]; + wrapped.characterMediaIds = [...wrapped.characterMediaIds]; } if (Array.isArray(wrapped.tags)) { - wrapped.tags = [...(wrapped.tags as string[])]; + wrapped.tags = [...wrapped.tags]; } - await encryptRecord('comicStories', wrapped); - await comicStoriesTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await encryptRecord('comicStories', wrapped as Record); + await comicStoriesTable.update(id, wrapped as never); }, async toggleFavorite(id: string): Promise { @@ -95,14 +92,12 @@ export const comicStoriesStore = { if (!existing) return; await comicStoriesTable.update(id, { isFavorite: !existing.isFavorite, - updatedAt: new Date().toISOString(), }); }, async archiveStory(id: string, archived: boolean): Promise { await comicStoriesTable.update(id, { isArchived: archived, - updatedAt: new Date().toISOString(), }); }, @@ -110,7 +105,6 @@ export const comicStoriesStore = { const nowIso = new Date().toISOString(); await comicStoriesTable.update(id, { deletedAt: nowIso, - updatedAt: nowIso, }); emitDomainEvent('ComicStoryDeleted', 'comic', 'comicStories', id, { storyId: id, @@ -133,14 +127,13 @@ export const comicStoriesStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; if (next === 'unlisted' && !existing.unlistedToken) { patch.unlistedToken = generateUnlistedToken(); } else if (next !== 'unlisted' && existing.unlistedToken) { patch.unlistedToken = undefined; } - await comicStoriesTable.update(id, patch); + await comicStoriesTable.update(id, patch as never); emitDomainEvent('VisibilityChanged', 'comic', 'comicStories', id, { recordId: id, @@ -170,10 +163,7 @@ export const comicStoriesStore = { panelMeta: nextMeta, } as Record; await encryptRecord('comicStories', patch); - await comicStoriesTable.update(storyId, { - ...patch, - updatedAt: new Date().toISOString(), - }); + await comicStoriesTable.update(storyId, patch as never); emitDomainEvent('ComicPanelAppended', 'comic', 'comicStories', storyId, { storyId, panelImageId, diff --git a/apps/mana/apps/web/src/lib/modules/comic/types.ts b/apps/mana/apps/web/src/lib/modules/comic/types.ts index 445ad1a52..99de2bd2f 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/types.ts +++ b/apps/mana/apps/web/src/lib/modules/comic/types.ts @@ -13,6 +13,7 @@ */ import type { BaseRecord } from '@mana/local-store'; +import { deriveUpdatedAt } from '$lib/data/sync'; import type { VisibilityLevel } from '@mana/shared-privacy'; // ─── Style ──────────────────────────────────────────────────────── @@ -148,7 +149,7 @@ export function toStory(local: LocalComicStory): ComicStory { isArchived: local.isArchived, visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? '', - updatedAt: local.updatedAt ?? '', + updatedAt: deriveUpdatedAt(local), }; } @@ -235,7 +236,7 @@ export function toCharacter(local: LocalComicCharacter): ComicCharacter { isFavorite: local.isFavorite, isArchived: local.isArchived, createdAt: local.createdAt ?? '', - updatedAt: local.updatedAt ?? '', + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte index d68b8b8de..f9d33e1cd 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte @@ -81,12 +81,8 @@ panelImageIds: nextIds, panelMeta: nextMeta, } as Partial; - const wrapped = { ...patch } as Record; - await encryptRecord('comicStories', wrapped); - await comicStoriesTable.update(story.id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await encryptRecord('comicStories', patch as Record); + await comicStoriesTable.update(story.id, patch as never); } diff --git a/apps/mana/apps/web/src/lib/modules/community/ListView.svelte b/apps/mana/apps/web/src/lib/modules/community/ListView.svelte new file mode 100644 index 000000000..da1834767 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/community/ListView.svelte @@ -0,0 +1,19 @@ + + + +
+ +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/community/components/ItemCard.svelte b/apps/mana/apps/web/src/lib/modules/community/components/ItemCard.svelte new file mode 100644 index 000000000..ace15b9a7 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/community/components/ItemCard.svelte @@ -0,0 +1,217 @@ + + + + + +
+
+
+ {categoryLabel} + {#if item.moduleContext} + {item.moduleContext} + {/if} + + {statusConfig.label} + + {formatDate(item.createdAt)} +
+ {#if item.title} +

{item.title}

+ {/if} +
+ +

{item.feedbackText}

+ + {#if showAdminResponse && item.adminResponse} +
+
Antwort vom Team
+

{item.adminResponse}

+
+ {/if} + +
+ {item.displayName} + +
+
+ + diff --git a/apps/mana/apps/web/src/lib/modules/community/module.config.ts b/apps/mana/apps/web/src/lib/modules/community/module.config.ts new file mode 100644 index 000000000..9657e34d7 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/community/module.config.ts @@ -0,0 +1,12 @@ +/** + * Community module — server-only feedback hub. No Dexie tables, so the + * module-config is mostly nominal (registers the appId for module-context + * tagging on inline FeedbackHook submissions). + */ + +import type { ModuleConfig } from '$lib/data/module-registry'; + +export const communityModuleConfig: ModuleConfig = { + appId: 'community', + tables: [], +}; diff --git a/apps/mana/apps/web/src/lib/modules/community/queries.ts b/apps/mana/apps/web/src/lib/modules/community/queries.ts new file mode 100644 index 000000000..efc4a159b --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/community/queries.ts @@ -0,0 +1,132 @@ +/** + * Community module — SWR-style queries. + * + * Feedback lives server-only (no Dexie / mana-sync), so we expose + * Svelte 5 reactive stores that pull from the feedback service and + * cache results across mounts within a session. + */ + +import type { PublicFeedbackItem, FeedbackQueryParams } from '@mana/feedback'; +import { feedbackService, publicFeedbackService } from '$lib/api/feedback'; +import { authStore } from '$lib/stores/auth.svelte'; + +/** + * Stateful query for the public community feed. + * + * Reads via the auth-enriched endpoint when a user is logged in (so each + * item carries `myReactions` for highlight-state); falls back to the + * anonymous endpoint when guest. Polls on demand via `reload()`. + */ +export function useCommunityFeed(initial: FeedbackQueryParams = {}) { + let items = $state([]); + let loading = $state(false); + let error = $state(null); + let lastQuery = $state(initial); + + async function reload(query?: FeedbackQueryParams) { + const q = query ?? lastQuery; + lastQuery = q; + loading = true; + error = null; + try { + if (authStore.user) { + items = await feedbackService.getPublicFeed(q); + } else { + items = await publicFeedbackService.getFeed(q); + } + } catch (err) { + console.error('[community/queries] reload failed:', err); + error = err instanceof Error ? err.message : 'Laden fehlgeschlagen'; + } finally { + loading = false; + } + } + + // kick off initial load + void reload(initial); + + return { + get items() { + return items; + }, + get loading() { + return loading; + }, + get error() { + return error; + }, + reload, + }; +} + +/** + * Single-item query plus its replies. + */ +export function useCommunityItem(id: string) { + let item = $state(null); + let replies = $state([]); + let loading = $state(true); + let error = $state(null); + + async function reload() { + loading = true; + error = null; + try { + const data = await publicFeedbackService.getItem(id); + item = data.item; + replies = data.replies; + } catch (err) { + console.error('[community/queries] item fetch failed:', err); + error = err instanceof Error ? err.message : 'Laden fehlgeschlagen'; + } finally { + loading = false; + } + } + + void reload(); + + return { + get item() { + return item; + }, + get replies() { + return replies; + }, + get loading() { + return loading; + }, + get error() { + return error; + }, + reload, + }; +} + +/** Toggle a reaction; on success patches `items` in place. */ +export async function toggleReactionOnItem( + itemsRef: { items: PublicFeedbackItem[] }, + id: string, + emoji: string +) { + if (!authStore.user) return; + try { + const res = await feedbackService.toggleReaction(id, emoji as never); + // Patch in place — caller manages reactivity by re-assigning the array + // or relying on $state-deep reactivity in calling component. + const idx = itemsRef.items.findIndex((i) => i.id === id); + if (idx >= 0) { + const old = itemsRef.items[idx]; + const myReactions = res.userHasReacted + ? Array.from(new Set([...(old.myReactions ?? []), emoji])) + : (old.myReactions ?? []).filter((e) => e !== emoji); + itemsRef.items[idx] = { + ...old, + reactions: res.reactions, + score: res.score, + myReactions, + }; + } + } catch (err) { + console.error('[community/queries] toggleReaction failed:', err); + } +} diff --git a/apps/mana/apps/web/src/lib/modules/community/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/community/views/DetailView.svelte new file mode 100644 index 000000000..f5da7c39b --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/community/views/DetailView.svelte @@ -0,0 +1,217 @@ + + + +
+ {#if view.loading} +
Lade…
+ {:else if view.error || !view.item} +
{view.error ?? 'Nicht gefunden'}
+ {:else} + + +
+

+ {view.replies.length} Antwort{view.replies.length === 1 ? '' : 'en'} +

+ + {#each view.replies as reply (reply.id)} + + {/each} + + {#if authStore.user} +
+ + {#if error} +

{error}

+ {/if} + +
+ {:else} + + {/if} +
+ {/if} +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/community/views/ListView.svelte b/apps/mana/apps/web/src/lib/modules/community/views/ListView.svelte new file mode 100644 index 000000000..ebedd5a86 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/community/views/ListView.svelte @@ -0,0 +1,153 @@ + + + +
+
+ + + +
+ + {#if feed.loading && feed.items.length === 0} +
Lade…
+ {:else if feed.error} +
{feed.error}
+ {:else if feed.items.length === 0} +
+ Noch keine Stimmen — sei der erste, der was reinwirft. + {#if !authStore.user} +
+ Login, um mitzumachen. + {/if} +
+ {:else} +
+ {#each feed.items as item (item.id)} + toggleReactionOnItem(feed, id, emoji)} + onClick={handleClickItem} + /> + {/each} +
+ {/if} +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/community/views/RoadmapView.svelte b/apps/mana/apps/web/src/lib/modules/community/views/RoadmapView.svelte new file mode 100644 index 000000000..a93a1da78 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/community/views/RoadmapView.svelte @@ -0,0 +1,131 @@ + + + +
+ {#each queries as { col, feed } (col.status)} +
+
+

{col.label}

+ {feed.items.length} +
+ +
+ {#if feed.loading && feed.items.length === 0} +
Lade…
+ {:else if feed.items.length === 0} +
Nichts hier.
+ {:else} + {#each feed.items as item (item.id)} + handleReact(feed, id, emoji)} + onClick={handleClick} + /> + {/each} + {/if} +
+
+ {/each} +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/companion/queries.ts b/apps/mana/apps/web/src/lib/modules/companion/queries.ts index cc1f2e07e..94142cb81 100644 --- a/apps/mana/apps/web/src/lib/modules/companion/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/companion/queries.ts @@ -14,7 +14,11 @@ export function useConversations() { 'companion', 'companionConversations' ).toArray(); - return all.filter((c) => !c.deletedAt).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); + return all + .filter((c) => !c.deletedAt) + .sort((a, b) => + (b.lastMessageAt ?? b.createdAt).localeCompare(a.lastMessageAt ?? a.createdAt) + ); } catch { return []; } diff --git a/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts b/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts index a1a9f9850..72c683cd8 100644 --- a/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts @@ -22,7 +22,6 @@ export const chatStore = { id: crypto.randomUUID(), title: title ?? 'Neues Gespraech', createdAt: now, - updatedAt: now, }; await db.table(CONV_TABLE).add(conv); emitDomainEvent('CompanionConversationStarted', 'companion', CONV_TABLE, conv.id, { @@ -35,14 +34,12 @@ export const chatStore = { async renameConversation(id: string, title: string): Promise { await db.table(CONV_TABLE).update(id, { title, - updatedAt: new Date().toISOString(), }); }, async deleteConversation(id: string): Promise { await db.table(CONV_TABLE).update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -68,9 +65,10 @@ export const chatStore = { }; await db.table(MSG_TABLE).add(msg); - // Touch conversation updatedAt + // Touch conversation so the chat list re-sorts with the most + // recently active conversation at the top. await db.table(CONV_TABLE).update(conversationId, { - updatedAt: msg.createdAt, + lastMessageAt: msg.createdAt, }); // Emit event only for actual user/assistant messages, not tool plumbing diff --git a/apps/mana/apps/web/src/lib/modules/companion/types.ts b/apps/mana/apps/web/src/lib/modules/companion/types.ts index c5a86a00f..b1d669f1e 100644 --- a/apps/mana/apps/web/src/lib/modules/companion/types.ts +++ b/apps/mana/apps/web/src/lib/modules/companion/types.ts @@ -6,7 +6,11 @@ export interface LocalConversation { id: string; title: string; createdAt: string; - updatedAt: string; + /** Real data field touched on every new message. Used as the sort key + * in the conversation list so the most recently active chat floats + * to the top. Replaces the older `updatedAt` reliance — F3 of the + * sync-field-meta overhaul moved updatedAt off Local records. */ + lastMessageAt?: string; deletedAt?: string; } diff --git a/apps/mana/apps/web/src/lib/modules/contacts/queries.ts b/apps/mana/apps/web/src/lib/modules/contacts/queries.ts index 7e73bc231..731c64ab3 100644 --- a/apps/mana/apps/web/src/lib/modules/contacts/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/contacts/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule, applyVisibility } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -46,7 +47,7 @@ export function toContact(local: LocalContact): Contact { isFavorite: local.isFavorite ?? false, isArchived: local.isArchived ?? false, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts b/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts index 47d67b16e..1109b87fa 100644 --- a/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts @@ -87,7 +87,6 @@ export const contactsStore = { const diff: Partial = { ...updateData, - updatedAt: new Date().toISOString(), }; await encryptRecord('contacts', diff); await contactTable.update(id, diff); @@ -99,7 +98,6 @@ export const contactsStore = { const decrypted = local ? await decryptRecord('contacts', { ...local }) : null; await contactTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('ContactDeleted', 'contacts', 'contacts', id, { contactId: id, @@ -114,7 +112,6 @@ export const contactsStore = { await contactTable.update(id, { isFavorite: !local.isFavorite, - updatedAt: new Date().toISOString(), }); ContactsEvents.contactFavorited(); }, @@ -122,7 +119,6 @@ export const contactsStore = { async updateTagIds(id: string, tagIds: string[]) { await contactTable.update(id, { tagIds, - updatedAt: new Date().toISOString(), }); }, @@ -153,7 +149,6 @@ export const contactsStore = { isFavorite: true, isArchived: false, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await encryptRecord('contacts', self); await contactTable.add(self); @@ -178,7 +173,6 @@ export const contactsStore = { lastName, email: profile.email || undefined, photoUrl: profile.image || undefined, - updatedAt: new Date().toISOString(), }; await encryptRecord('contacts', diff); await contactTable.update(SELF_CONTACT_ID, diff); diff --git a/apps/mana/apps/web/src/lib/modules/core/widgets/TasksTodayWidget.svelte b/apps/mana/apps/web/src/lib/modules/core/widgets/TasksTodayWidget.svelte index e0a6378e2..ed750352c 100644 --- a/apps/mana/apps/web/src/lib/modules/core/widgets/TasksTodayWidget.svelte +++ b/apps/mana/apps/web/src/lib/modules/core/widgets/TasksTodayWidget.svelte @@ -76,7 +76,6 @@ await db.table('tasks').update(task.id, { isCompleted: true, completedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/modules/dreams/queries.ts b/apps/mana/apps/web/src/lib/modules/dreams/queries.ts index 612aed89d..a63eb42c9 100644 --- a/apps/mana/apps/web/src/lib/modules/dreams/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/dreams/queries.ts @@ -1,4 +1,5 @@ import { formatDate } from '$lib/i18n/format'; +import { deriveUpdatedAt } from '$lib/data/sync'; /** * Reactive Queries & Pure Helpers for Dreams module. * @@ -44,7 +45,7 @@ export function toDream(local: LocalDream): Dream { isPinned: local.isPinned, isArchived: local.isArchived, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -56,7 +57,7 @@ export function toDreamSymbol(local: LocalDreamSymbol): DreamSymbol { color: local.color, count: local.count ?? 0, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts b/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts index 943f9ad8a..a5a28e301 100644 --- a/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/dreams/stores/dreams.svelte.ts @@ -198,7 +198,6 @@ export const dreamsStore = { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('dreams', diff); await dreamTable.update(id, diff); @@ -256,7 +255,6 @@ export const dreamsStore = { await dreamTable.update(id, { processingStatus: status, processingError: error, - updatedAt: new Date().toISOString(), }); }, @@ -285,7 +283,6 @@ export const dreamsStore = { content: decryptedExisting.content?.trim() ? decryptedExisting.content : transcript, processingStatus: 'idle', processingError: null, - updatedAt: new Date().toISOString(), }; await encryptRecord('dreams', diff); await dreamTable.update(dreamId, diff); @@ -294,7 +291,6 @@ export const dreamsStore = { await dreamTable.update(dreamId, { processingStatus: 'failed', processingError: msg, - updatedAt: new Date().toISOString(), }); } }, @@ -309,7 +305,6 @@ export const dreamsStore = { } await dreamTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('DreamDeleted', 'dreams', 'dreams', id, { dreamId: id }); }, @@ -319,7 +314,6 @@ export const dreamsStore = { if (!dream) return; await dreamTable.update(id, { isPinned: !dream.isPinned, - updatedAt: new Date().toISOString(), }); }, @@ -328,21 +322,18 @@ export const dreamsStore = { if (!dream) return; await dreamTable.update(id, { isLucid: !dream.isLucid, - updatedAt: new Date().toISOString(), }); }, async setMood(id: string, mood: DreamMood | null) { await dreamTable.update(id, { mood, - updatedAt: new Date().toISOString(), }); }, async setSleepQuality(id: string, quality: SleepQuality | null) { await dreamTable.update(id, { sleepQuality: quality, - updatedAt: new Date().toISOString(), }); }, @@ -372,7 +363,6 @@ export const dreamsStore = { const updated = dream.symbols.map((s) => (s === existing.name ? newName : s)); await dreamTable.update(dream.id, { symbols: updated, - updatedAt: new Date().toISOString(), }); } } @@ -380,7 +370,6 @@ export const dreamsStore = { const symbolDiff: Record = { ...data, ...(data.name ? { name: data.name.trim() } : {}), - updatedAt: new Date().toISOString(), }; await encryptRecord('dreamSymbols', symbolDiff); await dreamSymbolTable.update(id, symbolDiff); @@ -396,13 +385,11 @@ export const dreamsStore = { if (dream.deletedAt || !dream.symbols?.includes(symbol.name)) continue; await dreamTable.update(dream.id, { symbols: dream.symbols.filter((s) => s !== symbol.name), - updatedAt: new Date().toISOString(), }); } await dreamSymbolTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -421,17 +408,14 @@ export const dreamsStore = { set.add(target.name); await dreamTable.update(dream.id, { symbols: Array.from(set), - updatedAt: new Date().toISOString(), }); } await dreamSymbolTable.update(targetId, { count: (target.count ?? 0) + (source.count ?? 0), - updatedAt: new Date().toISOString(), }); await dreamSymbolTable.update(sourceId, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -445,7 +429,6 @@ export const dreamsStore = { const next = Math.max(0, (existing.count ?? 0) + delta); await dreamSymbolTable.update(existing.id, { count: next, - updatedAt: new Date().toISOString(), }); } else if (delta > 0) { await dreamSymbolTable.add({ diff --git a/apps/mana/apps/web/src/lib/modules/drink/queries.ts b/apps/mana/apps/web/src/lib/modules/drink/queries.ts index 9be167827..7d327e850 100644 --- a/apps/mana/apps/web/src/lib/modules/drink/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/drink/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -22,7 +23,7 @@ export function toDrinkEntry(local: LocalDrinkEntry): DrinkEntry { note: local.note ?? null, presetId: local.presetId ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -38,7 +39,7 @@ export function toDrinkPreset(local: LocalDrinkPreset): DrinkPreset { order: local.order, isArchived: local.isArchived, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/drink/stores/drink.svelte.ts b/apps/mana/apps/web/src/lib/modules/drink/stores/drink.svelte.ts index 0072b7704..51934e859 100644 --- a/apps/mana/apps/web/src/lib/modules/drink/stores/drink.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/drink/stores/drink.svelte.ts @@ -69,7 +69,6 @@ export const drinkStore = { await encryptRecord('drinkEntries', wrapped); await drinkEntryTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -77,7 +76,6 @@ export const drinkStore = { const entry = await drinkEntryTable.get(id); await drinkEntryTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); if (entry) { emitDomainEvent('DrinkEntryDeleted', 'drink', 'drinkEntries', id, { @@ -97,7 +95,6 @@ export const drinkStore = { const entry = active[0]; await drinkEntryTable.update(entry.id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('DrinkEntryUndone', 'drink', 'drinkEntries', entry.id, { entryId: entry.id, @@ -146,14 +143,12 @@ export const drinkStore = { await encryptRecord('drinkPresets', wrapped); await drinkPresetTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, async deletePreset(id: string) { await drinkPresetTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -162,7 +157,6 @@ export const drinkStore = { for (let i = 0; i < presetIds.length; i++) { await drinkPresetTable.update(presetIds[i], { order: i, - updatedAt: now, }); } }, diff --git a/apps/mana/apps/web/src/lib/modules/events/discovery/types.ts b/apps/mana/apps/web/src/lib/modules/events/discovery/types.ts index 8080ac733..eee53073f 100644 --- a/apps/mana/apps/web/src/lib/modules/events/discovery/types.ts +++ b/apps/mana/apps/web/src/lib/modules/events/discovery/types.ts @@ -35,7 +35,6 @@ export interface DiscoverySource { lastError: string | null; isActive: boolean; createdAt: string; - updatedAt: string; } export interface DiscoveredEvent { diff --git a/apps/mana/apps/web/src/lib/modules/events/queries.ts b/apps/mana/apps/web/src/lib/modules/events/queries.ts index ccddbc8c3..f09e7cd2e 100644 --- a/apps/mana/apps/web/src/lib/modules/events/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/events/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -47,7 +48,7 @@ export function toSocialEvent(local: LocalSocialEvent, block: LocalTimeBlock | n endTime: block?.endDate ?? block?.startDate ?? now, isAllDay: block?.allDay ?? false, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -64,7 +65,7 @@ export function toEventItem(local: LocalEventItem): EventItem { claimedByName: local.claimedByName ?? null, claimedAt: local.claimedAt ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -82,7 +83,7 @@ export function toEventGuest(local: LocalEventGuest): EventGuest { plusOnes: local.plusOnes ?? 0, note: local.note ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts b/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts index db207abb5..fcf0d85d3 100644 --- a/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/events/stores/events.svelte.ts @@ -71,7 +71,6 @@ export const eventsStore = { status: input.status ?? 'draft', visibility: defaultVisibilityFor(getActiveSpace()?.type), createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; // title / description / location are encrypted at rest. The @@ -125,9 +124,7 @@ export const eventsStore = { await updateBlock(event.timeBlockId, blockUpdates); } - const localData: Partial = { - updatedAt: new Date().toISOString(), - }; + const localData: Partial = {}; if (input.title !== undefined) localData.title = input.title; if (input.description !== undefined) localData.description = input.description; if (input.location !== undefined) localData.location = input.location; @@ -170,7 +167,6 @@ export const eventsStore = { } await db.table('socialEvents').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('SocialEventDeleted', 'events', 'socialEvents', id, { eventId: id }); return { success: true as const }; @@ -199,7 +195,6 @@ export const eventsStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }); emitDomainEvent('VisibilityChanged', 'events', 'socialEvents', id, { @@ -246,7 +241,6 @@ export const eventsStore = { isPublished: true, publicToken: token, status: 'published' satisfies EventStatus, - updatedAt: new Date().toISOString(), }); // Push any pre-existing bring-list items right away so the // public page shows them on first open. @@ -276,7 +270,6 @@ export const eventsStore = { isPublished: false, publicToken: null, status: 'draft' satisfies EventStatus, - updatedAt: new Date().toISOString(), }); return { success: true as const }; } catch (e) { diff --git a/apps/mana/apps/web/src/lib/modules/events/stores/guests.svelte.ts b/apps/mana/apps/web/src/lib/modules/events/stores/guests.svelte.ts index fce43f15d..c036e5de1 100644 --- a/apps/mana/apps/web/src/lib/modules/events/stores/guests.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/events/stores/guests.svelte.ts @@ -38,7 +38,6 @@ export const eventGuestsStore = { plusOnes: input.plusOnes ?? 0, note: input.note ?? null, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; // name / email / phone / note are encrypted at rest. Guest // records stay local-only — they're never pushed to the @@ -68,7 +67,6 @@ export const eventGuestsStore = { try { const data: Partial = { ...input, - updatedAt: new Date().toISOString(), }; if (input.rsvpStatus !== undefined) { data.rsvpAt = new Date().toISOString(); @@ -91,7 +89,6 @@ export const eventGuestsStore = { try { await db.table('eventGuests').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); return { success: true as const }; } catch (e) { diff --git a/apps/mana/apps/web/src/lib/modules/events/stores/items.svelte.ts b/apps/mana/apps/web/src/lib/modules/events/stores/items.svelte.ts index 2ce6146b0..7e32a59e5 100644 --- a/apps/mana/apps/web/src/lib/modules/events/stores/items.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/events/stores/items.svelte.ts @@ -45,7 +45,6 @@ export const eventItemsStore = { claimedByName: null, claimedAt: null, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await db.table('eventItems').add(newItem); void eventsStore.syncItems(input.eventId); @@ -71,7 +70,6 @@ export const eventItemsStore = { try { await db.table('eventItems').update(id, { ...input, - updatedAt: new Date().toISOString(), }); // Push the updated bring list to the server. We need the // parent eventId, so re-read the row first. @@ -103,7 +101,6 @@ export const eventItemsStore = { const item = await db.table('eventItems').get(id); await db.table('eventItems').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); if (item) void eventsStore.syncItems(item.eventId); return { success: true as const }; diff --git a/apps/mana/apps/web/src/lib/modules/finance/queries.ts b/apps/mana/apps/web/src/lib/modules/finance/queries.ts index 899a119dc..cc96987e2 100644 --- a/apps/mana/apps/web/src/lib/modules/finance/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/finance/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -26,7 +27,7 @@ export function toTransaction(local: LocalTransaction): Transaction { date: local.date, note: local.note, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts b/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts index 151fb0907..d31f0728b 100644 --- a/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/finance/stores/finance.svelte.ts @@ -52,7 +52,6 @@ export const financeStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('transactions', diff); await transactionTable.update(id, diff); @@ -61,7 +60,6 @@ export const financeStore = { async deleteTransaction(id: string) { await transactionTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('TransactionDeleted', 'finance', 'transactions', id, { transactionId: id }); }, diff --git a/apps/mana/apps/web/src/lib/modules/firsts/queries.ts b/apps/mana/apps/web/src/lib/modules/firsts/queries.ts index af491e431..79d4f3608 100644 --- a/apps/mana/apps/web/src/lib/modules/firsts/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/firsts/queries.ts @@ -1,4 +1,5 @@ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -28,7 +29,7 @@ export function toFirst(local: LocalFirst): First { isPinned: local.isPinned, isArchived: local.isArchived, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/firsts/stores/firsts.svelte.ts b/apps/mana/apps/web/src/lib/modules/firsts/stores/firsts.svelte.ts index e1c115016..ae17d6a5a 100644 --- a/apps/mana/apps/web/src/lib/modules/firsts/stores/firsts.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/firsts/stores/firsts.svelte.ts @@ -126,7 +126,6 @@ export const firstsStore = { reality: data.reality ?? null, rating: data.rating ?? null, wouldRepeat: data.wouldRepeat ?? null, - updatedAt: new Date().toISOString(), }; if (data.personIds) diff.personIds = data.personIds; if (data.sharedWith !== undefined) diff.sharedWith = data.sharedWith; @@ -164,7 +163,6 @@ export const firstsStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('firsts', diff); await firstTable.update(id, diff); @@ -173,7 +171,6 @@ export const firstsStore = { async deleteFirst(id: string) { await firstTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -182,14 +179,12 @@ export const firstsStore = { if (!first) return; await firstTable.update(id, { isPinned: !first.isPinned, - updatedAt: new Date().toISOString(), }); }, async archiveFirst(id: string) { await firstTable.update(id, { isArchived: true, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/food/mutations.ts b/apps/mana/apps/web/src/lib/modules/food/mutations.ts index d09d125f7..448edcf4f 100644 --- a/apps/mana/apps/web/src/lib/modules/food/mutations.ts +++ b/apps/mana/apps/web/src/lib/modules/food/mutations.ts @@ -70,7 +70,6 @@ export const mealMutations = { photoThumbnailUrl: null, foods: null, createdAt: now, - updatedAt: now, }; const encrypted: Record = { ...row }; await encryptRecord('meals', encrypted); @@ -104,7 +103,6 @@ export const mealMutations = { photoThumbnailUrl: dto.photoThumbnailUrl ?? null, foods: dto.foods ?? null, createdAt: now, - updatedAt: now, }; const encrypted: Record = { ...row }; await encryptRecord('meals', encrypted); @@ -130,9 +128,7 @@ export const mealMutations = { * and decrypts it for the caller. */ async update(id: string, dto: UpdateMealDto): Promise { - const updateData: Record = { - updatedAt: new Date().toISOString(), - }; + const updateData: Record = {}; if (dto.mealType !== undefined) updateData.mealType = dto.mealType; if (dto.description !== undefined) updateData.description = dto.description.trim(); if (dto.nutrition !== undefined) updateData.nutrition = dto.nutrition; @@ -150,7 +146,7 @@ export const mealMutations = { async delete(id: string): Promise { const existing = await db.table('meals').get(id); const now = new Date().toISOString(); - await db.table('meals').update(id, { deletedAt: now, updatedAt: now }); + await db.table('meals').update(id, { deletedAt: now }); emitDomainEvent('MealDeleted', 'food', 'meals', id, { mealId: id, mealType: existing?.mealType ?? '', diff --git a/apps/mana/apps/web/src/lib/modules/guides/queries.ts b/apps/mana/apps/web/src/lib/modules/guides/queries.ts index 46fb28afe..fadb6e137 100644 --- a/apps/mana/apps/web/src/lib/modules/guides/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/guides/queries.ts @@ -6,6 +6,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -34,7 +35,7 @@ export function toGuide(local: LocalGuide): Guide { isPublished: local.isPublished, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -46,7 +47,7 @@ export function toSection(local: LocalSection): Section { content: local.content, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -59,7 +60,7 @@ export function toStep(local: LocalStep): Step { content: local.content, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -71,7 +72,7 @@ export function toRun(local: LocalRun): Run { completedAt: local.completedAt, completedStepIds: local.completedStepIds, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/guides/stores/guides.svelte.ts b/apps/mana/apps/web/src/lib/modules/guides/stores/guides.svelte.ts index c41bfcf7a..817a1dd93 100644 --- a/apps/mana/apps/web/src/lib/modules/guides/stores/guides.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/guides/stores/guides.svelte.ts @@ -57,7 +57,7 @@ export const guidesStore = { }, async updateGuide(id: string, dto: UpdateGuideDto): Promise { - const updates: Record = { updatedAt: new Date().toISOString() }; + const updates: Record = {}; if (dto.title !== undefined) updates.title = dto.title; if (dto.description !== undefined) updates.description = dto.description; if (dto.category !== undefined) updates.category = dto.category; @@ -74,17 +74,17 @@ export const guidesStore = { // Cascade: soft-delete sections, steps, and runs const sections = await sectionTable.where('guideId').equals(id).toArray(); for (const s of sections) { - await sectionTable.update(s.id, { deletedAt: now, updatedAt: now }); + await sectionTable.update(s.id, { deletedAt: now }); } const steps = await stepTable.where('guideId').equals(id).toArray(); for (const s of steps) { - await stepTable.update(s.id, { deletedAt: now, updatedAt: now }); + await stepTable.update(s.id, { deletedAt: now }); } const runs = await runTable.where('guideId').equals(id).toArray(); for (const r of runs) { - await runTable.update(r.id, { deletedAt: now, updatedAt: now }); + await runTable.update(r.id, { deletedAt: now }); } - await guideTable.update(id, { deletedAt: now, updatedAt: now }); + await guideTable.update(id, { deletedAt: now }); }, // ─── Sections ──────────────────────────────────────── @@ -107,7 +107,7 @@ export const guidesStore = { }, async updateSection(id: string, dto: UpdateSectionDto): Promise { - const updates: Record = { updatedAt: new Date().toISOString() }; + const updates: Record = {}; if (dto.title !== undefined) updates.title = dto.title; if (dto.content !== undefined) updates.content = dto.content; await encryptRecord('sections', updates); @@ -116,7 +116,7 @@ export const guidesStore = { async deleteSection(id: string): Promise { const now = new Date().toISOString(); - await sectionTable.update(id, { deletedAt: now, updatedAt: now }); + await sectionTable.update(id, { deletedAt: now }); }, // ─── Steps ─────────────────────────────────────────── @@ -140,7 +140,7 @@ export const guidesStore = { }, async updateStep(id: string, dto: UpdateStepDto): Promise { - const updates: Record = { updatedAt: new Date().toISOString() }; + const updates: Record = {}; if (dto.title !== undefined) updates.title = dto.title; if (dto.content !== undefined) updates.content = dto.content; if (dto.sectionId !== undefined) updates.sectionId = dto.sectionId; @@ -150,7 +150,7 @@ export const guidesStore = { async deleteStep(id: string): Promise { const now = new Date().toISOString(); - await stepTable.update(id, { deletedAt: now, updatedAt: now }); + await stepTable.update(id, { deletedAt: now }); }, // ─── Runs (Progress Tracking) ──────────────────────── @@ -194,7 +194,6 @@ export const guidesStore = { if (run.completedStepIds.includes(stepId)) return; await runTable.update(runId, { completedStepIds: [...run.completedStepIds, stepId], - updatedAt: new Date().toISOString(), }); }, @@ -203,7 +202,6 @@ export const guidesStore = { if (!run) return; await runTable.update(runId, { completedStepIds: run.completedStepIds.filter((id) => id !== stepId), - updatedAt: new Date().toISOString(), }); }, @@ -212,7 +210,6 @@ export const guidesStore = { const run = await runTable.get(runId); await runTable.update(runId, { completedAt: now, - updatedAt: now, }); if (run?.timeBlockId) { await updateBlock(run.timeBlockId, { endDate: now }); @@ -225,6 +222,6 @@ export const guidesStore = { if (run?.timeBlockId) { await deleteBlock(run.timeBlockId); } - await runTable.update(id, { deletedAt: now, updatedAt: now }); + await runTable.update(id, { deletedAt: now }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/habits/queries.ts b/apps/mana/apps/web/src/lib/modules/habits/queries.ts index 5e5df9a8d..5086559ef 100644 --- a/apps/mana/apps/web/src/lib/modules/habits/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/habits/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import type { LocalHabit, LocalHabitLog, Habit, HabitLog } from './types'; @@ -28,7 +29,7 @@ export function toHabit(local: LocalHabit): Habit { // default at create time in habits.svelte.ts. visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts b/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts index 33873aaa1..f053bc283 100644 --- a/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/habits/stores/habits.svelte.ts @@ -126,7 +126,6 @@ export const habitsStore = { ) { await habitTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); }, @@ -148,7 +147,6 @@ export const habitsStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }); emitDomainEvent('VisibilityChanged', 'habits', 'habits', id, { @@ -163,7 +161,6 @@ export const habitsStore = { const habit = await habitTable.get(id); await habitTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('HabitDeleted', 'habits', 'habits', id, { habitId: id, @@ -318,7 +315,6 @@ export const habitsStore = { for (let i = 0; i < habitIds.length; i++) { await habitTable.update(habitIds[i], { order: i, - updatedAt: now, }); } }, @@ -334,7 +330,6 @@ export const habitsStore = { // Update the habit record await habitTable.update(habitId, { schedule, - updatedAt: new Date().toISOString(), }); // Find existing template block for this habit diff --git a/apps/mana/apps/web/src/lib/modules/inventory/queries.ts b/apps/mana/apps/web/src/lib/modules/inventory/queries.ts index 7728f313f..6418b6188 100644 --- a/apps/mana/apps/web/src/lib/modules/inventory/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/inventory/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -104,7 +105,7 @@ export function toCollection(local: LocalCollection): Collection { order: local.order, itemCount: local.itemCount, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -126,7 +127,7 @@ export function toItem(local: LocalItem): Item { tags: local.tags, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -141,7 +142,7 @@ export function toLocation(local: LocalLocation): Location { depth: local.depth, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -154,7 +155,7 @@ export function toCategory(local: LocalCategory): Category { color: local.color ?? undefined, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/inventory/stores/categories.svelte.ts b/apps/mana/apps/web/src/lib/modules/inventory/stores/categories.svelte.ts index a5a03fb59..005d78254 100644 --- a/apps/mana/apps/web/src/lib/modules/inventory/stores/categories.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/inventory/stores/categories.svelte.ts @@ -32,7 +32,6 @@ export const categoriesStore = { async update(id: string, data: Partial>) { await invCategoryTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); }, @@ -47,7 +46,7 @@ export const categoriesStore = { collectIds(id); const now = new Date().toISOString(); for (const deleteId of idsToDelete) { - await invCategoryTable.update(deleteId, { deletedAt: now, updatedAt: now }); + await invCategoryTable.update(deleteId, { deletedAt: now }); } InventoryEvents.categoryDeleted(); }, diff --git a/apps/mana/apps/web/src/lib/modules/inventory/stores/collections.svelte.ts b/apps/mana/apps/web/src/lib/modules/inventory/stores/collections.svelte.ts index 23b075156..56ce87a59 100644 --- a/apps/mana/apps/web/src/lib/modules/inventory/stores/collections.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/inventory/stores/collections.svelte.ts @@ -43,14 +43,12 @@ export const collectionsStore = { ) { await invCollectionTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); }, async delete(id: string) { await invCollectionTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); InventoryEvents.collectionDeleted(); }, @@ -58,14 +56,13 @@ export const collectionsStore = { async reorder(orderedIds: string[]) { const now = new Date().toISOString(); for (let i = 0; i < orderedIds.length; i++) { - await invCollectionTable.update(orderedIds[i], { order: i, updatedAt: now }); + await invCollectionTable.update(orderedIds[i], { order: i }); } }, async updateItemCount(collectionId: string, count: number) { await invCollectionTable.update(collectionId, { itemCount: count, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/inventory/stores/items.svelte.ts b/apps/mana/apps/web/src/lib/modules/inventory/stores/items.svelte.ts index cbfa1f745..782ae9738 100644 --- a/apps/mana/apps/web/src/lib/modules/inventory/stores/items.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/inventory/stores/items.svelte.ts @@ -78,7 +78,6 @@ export const itemsStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('invItems', diff); await invItemTable.update(id, diff); @@ -88,7 +87,6 @@ export const itemsStore = { async delete(id: string) { await invItemTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); InventoryEvents.itemDeleted(); }, @@ -98,7 +96,7 @@ export const itemsStore = { const toDelete = all.filter((i) => !i.deletedAt && i.collectionId === collectionId); const now = new Date().toISOString(); for (const item of toDelete) { - await invItemTable.update(item.id, { deletedAt: now, updatedAt: now }); + await invItemTable.update(item.id, { deletedAt: now }); } }, @@ -109,7 +107,6 @@ export const itemsStore = { const note = { id: crypto.randomUUID(), content, createdAt: now }; await invItemTable.update(itemId, { notes: [...item.notes, note], - updatedAt: now, }); }, @@ -118,7 +115,6 @@ export const itemsStore = { if (!item) return; await invItemTable.update(itemId, { notes: item.notes.filter((n) => n.id !== noteId), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/inventory/stores/locations.svelte.ts b/apps/mana/apps/web/src/lib/modules/inventory/stores/locations.svelte.ts index b82a8395a..ab89d7c8e 100644 --- a/apps/mana/apps/web/src/lib/modules/inventory/stores/locations.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/inventory/stores/locations.svelte.ts @@ -49,7 +49,6 @@ export const locationsStore = { async update(id: string, data: Partial>) { await invLocationTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); }, @@ -64,7 +63,7 @@ export const locationsStore = { collectIds(id); const now = new Date().toISOString(); for (const deleteId of idsToDelete) { - await invLocationTable.update(deleteId, { deletedAt: now, updatedAt: now }); + await invLocationTable.update(deleteId, { deletedAt: now }); } InventoryEvents.locationDeleted(); }, diff --git a/apps/mana/apps/web/src/lib/modules/invoices/queries.ts b/apps/mana/apps/web/src/lib/modules/invoices/queries.ts index fe4ece2f8..06c9fb3ee 100644 --- a/apps/mana/apps/web/src/lib/modules/invoices/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/invoices/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { scopedForModule } from '$lib/data/scope'; import type { @@ -48,7 +49,7 @@ export function toInvoice(local: LocalInvoice): Invoice { pdfBlobKey: local.pdfBlobKey ?? null, totals: local.totals, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/invoices/stores/invoices.svelte.ts b/apps/mana/apps/web/src/lib/modules/invoices/stores/invoices.svelte.ts index d71cb08c8..f69be9023 100644 --- a/apps/mana/apps/web/src/lib/modules/invoices/stores/invoices.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/invoices/stores/invoices.svelte.ts @@ -137,10 +137,7 @@ export const invoicesStore = { } const wrapped = { ...patch } as Record; await encryptRecord('invoices', wrapped); - await invoiceTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await invoiceTable.update(id, wrapped as never); }, /** @@ -159,10 +156,7 @@ export const invoicesStore = { // `lines` is in the encryption allowlist; `totals` is not. encryptRecord // only touches allowlisted keys, so a single call is correct for both. await encryptRecord('invoices', patch); - await invoiceTable.update(id, { - ...patch, - updatedAt: new Date().toISOString(), - }); + await invoiceTable.update(id, patch as never); }, /** @@ -178,7 +172,6 @@ export const invoicesStore = { await invoiceTable.update(id, { status: 'sent', sentAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('InvoiceSent', 'invoices', 'invoices', id, { invoiceId: id, @@ -198,7 +191,6 @@ export const invoicesStore = { await invoiceTable.update(id, { status: 'paid', paidAt: stamp, - updatedAt: new Date().toISOString(), }); emitDomainEvent('InvoicePaid', 'invoices', 'invoices', id, { invoiceId: id, @@ -243,7 +235,6 @@ export const invoicesStore = { } await invoiceTable.update(id, { status: 'void', - updatedAt: new Date().toISOString(), }); emitDomainEvent('InvoiceVoided', 'invoices', 'invoices', id, { invoiceId: id, @@ -294,7 +285,6 @@ export const invoicesStore = { } await invoiceTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('InvoiceDeleted', 'invoices', 'invoices', id, { invoiceId: id }); }, diff --git a/apps/mana/apps/web/src/lib/modules/invoices/stores/settings.svelte.ts b/apps/mana/apps/web/src/lib/modules/invoices/stores/settings.svelte.ts index a20060b3c..e1a050c51 100644 --- a/apps/mana/apps/web/src/lib/modules/invoices/stores/settings.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/invoices/stores/settings.svelte.ts @@ -134,7 +134,6 @@ export const invoiceSettingsStore = { out = formatNumber(prefix, nextN, padding); await invoiceSettingsTable.update(INVOICE_SETTINGS_ID, { nextNumber: nextN + 1, - updatedAt: new Date().toISOString(), }); }); return out; @@ -146,7 +145,6 @@ export const invoiceSettingsStore = { await encryptRecord('invoiceSettings', wrapped); await invoiceSettingsTable.update(INVOICE_SETTINGS_ID, { ...wrapped, - updatedAt: new Date().toISOString(), }); emitDomainEvent('InvoiceSettingsUpdated', 'invoices', 'invoiceSettings', INVOICE_SETTINGS_ID, { fields: Object.keys(patch), diff --git a/apps/mana/apps/web/src/lib/modules/journal/queries.ts b/apps/mana/apps/web/src/lib/modules/journal/queries.ts index 9a0fc4cab..090b0318b 100644 --- a/apps/mana/apps/web/src/lib/modules/journal/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/journal/queries.ts @@ -1,4 +1,5 @@ import { formatDate } from '$lib/i18n/format'; +import { deriveUpdatedAt } from '$lib/data/sync'; /** * Reactive Queries & Pure Helpers for Journal module. * @@ -29,7 +30,7 @@ export function toJournalEntry(local: LocalJournalEntry): JournalEntry { wordCount: local.wordCount ?? 0, transcriptModel: local.transcriptModel ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts b/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts index 174c90056..724920a36 100644 --- a/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/journal/stores/journal.svelte.ts @@ -76,7 +76,6 @@ export const journalStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; // Recompute word count when content changes @@ -117,7 +116,6 @@ export const journalStore = { content: transcript, transcriptModel: result.model, wordCount: countWords(transcript), - updatedAt: new Date().toISOString(), }; await encryptRecord('journalEntries', diff); await journalEntryTable.update(entryId, diff); @@ -133,7 +131,6 @@ export const journalStore = { async deleteEntry(id: string) { await journalEntryTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('JournalEntryDeleted', 'journal', 'journalEntries', id, { entryId: id }); }, @@ -143,7 +140,6 @@ export const journalStore = { if (!entry) return; await journalEntryTable.update(id, { isPinned: !entry.isPinned, - updatedAt: new Date().toISOString(), }); }, @@ -152,14 +148,12 @@ export const journalStore = { if (!entry) return; await journalEntryTable.update(id, { isFavorite: !entry.isFavorite, - updatedAt: new Date().toISOString(), }); }, async setMood(id: string, mood: JournalMood | null) { await journalEntryTable.update(id, { mood, - updatedAt: new Date().toISOString(), }); if (mood) emitDomainEvent('JournalMoodSet', 'journal', 'journalEntries', id, { entryId: id, mood }); @@ -168,7 +162,6 @@ export const journalStore = { async archiveEntry(id: string) { await journalEntryTable.update(id, { isArchived: true, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/kontext/queries.ts b/apps/mana/apps/web/src/lib/modules/kontext/queries.ts index 1f6fe8d6a..9ebe01481 100644 --- a/apps/mana/apps/web/src/lib/modules/kontext/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/kontext/queries.ts @@ -10,6 +10,7 @@ */ import { useLiveQueryWithDefault } from '@mana/local-store/svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { scopedTable } from '$lib/data/scope/scoped-db'; import type { KontextDoc, LocalKontextDoc } from './types'; @@ -19,7 +20,7 @@ export function toKontextDoc(local: LocalKontextDoc): KontextDoc { id: local.id, content: local.content ?? '', createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/kontext/stores/kontext.svelte.ts b/apps/mana/apps/web/src/lib/modules/kontext/stores/kontext.svelte.ts index 9d9bd6766..58e001240 100644 --- a/apps/mana/apps/web/src/lib/modules/kontext/stores/kontext.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/kontext/stores/kontext.svelte.ts @@ -45,7 +45,6 @@ export const kontextStore = { const row = await this.ensureDoc(); const diff: Partial = { content, - updatedAt: new Date().toISOString(), }; await encryptRecord('kontextDoc', diff); await kontextDocTable.update(row.id, diff); diff --git a/apps/mana/apps/web/src/lib/modules/lasts/queries.ts b/apps/mana/apps/web/src/lib/modules/lasts/queries.ts index 892352d72..f4f2acc07 100644 --- a/apps/mana/apps/web/src/lib/modules/lasts/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/lasts/queries.ts @@ -1,4 +1,5 @@ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; import type { Last, LastStatus, LocalLast } from './types'; @@ -34,7 +35,7 @@ export function toLast(local: LocalLast): Last { unlistedToken: local.unlistedToken ?? '', unlistedExpiresAt: local.unlistedExpiresAt ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/lasts/stores/items.svelte.ts b/apps/mana/apps/web/src/lib/modules/lasts/stores/items.svelte.ts index 27994f416..1ad36d6ef 100644 --- a/apps/mana/apps/web/src/lib/modules/lasts/stores/items.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/lasts/stores/items.svelte.ts @@ -150,7 +150,6 @@ export const lastsStore = { whatIKnowNow: data.whatIKnowNow ?? null, tenderness: data.tenderness ?? null, wouldReclaim: data.wouldReclaim ?? null, - updatedAt: nowIso(), }; await encryptRecord('lasts', diff); await lastTable.update(id, diff); @@ -165,7 +164,6 @@ export const lastsStore = { status: 'reclaimed', reclaimedAt: nowIso(), reclaimedNote, - updatedAt: nowIso(), }; await encryptRecord('lasts', diff); await lastTable.update(id, diff); @@ -198,7 +196,6 @@ export const lastsStore = { ) { const diff: Partial = { ...data, - updatedAt: nowIso(), }; await encryptRecord('lasts', diff); await lastTable.update(id, diff); @@ -207,7 +204,6 @@ export const lastsStore = { async deleteLast(id: string) { await lastTable.update(id, { deletedAt: nowIso(), - updatedAt: nowIso(), }); }, @@ -216,14 +212,12 @@ export const lastsStore = { if (!last) return; await lastTable.update(id, { isPinned: !last.isPinned, - updatedAt: nowIso(), }); }, async archiveLast(id: string) { await lastTable.update(id, { isArchived: true, - updatedAt: nowIso(), }); }, @@ -289,7 +283,6 @@ export const lastsStore = { async acceptCandidate(id: string) { await lastTable.update(id, { inferredFrom: null, - updatedAt: nowIso(), }); }, @@ -329,7 +322,6 @@ export const lastsStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId() ?? undefined, - updatedAt: now, }; if (next === 'unlisted') { @@ -407,7 +399,6 @@ export const lastsStore = { }); await lastTable.update(id, { unlistedToken: token, - updatedAt: nowIso(), }); return token; }, @@ -438,7 +429,6 @@ export const lastsStore = { await lastTable.update(id, { unlistedToken: token, unlistedExpiresAt: expiresAt ? expiresAt.toISOString() : null, - updatedAt: nowIso(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/library/queries.ts b/apps/mana/apps/web/src/lib/modules/library/queries.ts index 43c560d40..fb4abd8c5 100644 --- a/apps/mana/apps/web/src/lib/modules/library/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/library/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -39,7 +40,7 @@ export function toLibraryEntry(local: LocalLibraryEntry): LibraryEntry { unlistedToken: local.unlistedToken ?? '', unlistedExpiresAt: local.unlistedExpiresAt ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts b/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts index 182f00c2a..a62678ca1 100644 --- a/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/library/stores/entries.svelte.ts @@ -132,10 +132,7 @@ export const libraryEntriesStore = { ) { const wrapped = { ...patch } as Record; await encryptRecord('libraryEntries', wrapped); - await libraryEntryTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await libraryEntryTable.update(id, wrapped as never); // Keep the share-link snapshot in sync if this entry is unlisted. void this.refreshUnlistedSnapshot(id); }, @@ -152,7 +149,6 @@ export const libraryEntriesStore = { } await libraryEntryTable.update(id, { ...patch, - updatedAt: new Date().toISOString(), }); if (status === 'completed') { emitDomainEvent('LibraryEntryCompleted', 'library', 'libraryEntries', id, { @@ -178,14 +174,12 @@ export const libraryEntriesStore = { status: 'active', startedAt: nowDate, completedAt: null, - updatedAt: new Date().toISOString(), }); }, async rate(id: string, rating: number | null) { await libraryEntryTable.update(id, { rating, - updatedAt: new Date().toISOString(), }); }, @@ -194,7 +188,6 @@ export const libraryEntriesStore = { if (!existing) return; await libraryEntryTable.update(id, { isFavorite: !existing.isFavorite, - updatedAt: new Date().toISOString(), }); }, @@ -220,7 +213,6 @@ export const libraryEntriesStore = { await libraryEntryTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('LibraryEntryDeleted', 'library', 'libraryEntries', id, { entryId: id }); }, @@ -241,7 +233,6 @@ export const libraryEntriesStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; if (next === 'unlisted') { @@ -274,7 +265,7 @@ export const libraryEntriesStore = { patch.unlistedExpiresAt = undefined; } - await libraryEntryTable.update(id, patch); + await libraryEntryTable.update(id, patch as never); emitDomainEvent('VisibilityChanged', 'library', 'libraryEntries', id, { recordId: id, @@ -317,7 +308,6 @@ export const libraryEntriesStore = { }); await libraryEntryTable.update(id, { unlistedToken: token, - updatedAt: new Date().toISOString(), }); return token; } catch (e) { @@ -351,7 +341,6 @@ export const libraryEntriesStore = { }); await libraryEntryTable.update(id, { unlistedExpiresAt: expiresAt ? expiresAt.toISOString() : undefined, - updatedAt: new Date().toISOString(), }); } catch (e) { console.error('[library] setUnlistedExpiry failed', e); diff --git a/apps/mana/apps/web/src/lib/modules/mail/queries.ts b/apps/mana/apps/web/src/lib/modules/mail/queries.ts index 339fdf5b0..237762dea 100644 --- a/apps/mana/apps/web/src/lib/modules/mail/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/mail/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -22,7 +23,7 @@ export function toMailDraft(local: LocalMailDraft): MailDraft { htmlBody: local.htmlBody ?? '', replyToMessageId: local.replyToMessageId ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/mail/stores/drafts.svelte.ts b/apps/mana/apps/web/src/lib/modules/mail/stores/drafts.svelte.ts index 5a88cd982..6ee6cd8fd 100644 --- a/apps/mana/apps/web/src/lib/modules/mail/stores/drafts.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/mail/stores/drafts.svelte.ts @@ -25,7 +25,6 @@ export const draftsStore = { await encryptRecord('mailDrafts', wrapped); await mailDraftTable.update(input.existingDraftId, { ...wrapped, - updatedAt: new Date().toISOString(), }); return input.existingDraftId; } @@ -49,7 +48,6 @@ export const draftsStore = { async deleteDraft(id: string) { await mailDraftTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/meditate/queries.ts b/apps/mana/apps/web/src/lib/modules/meditate/queries.ts index cd2bfdfb4..d37c10245 100644 --- a/apps/mana/apps/web/src/lib/modules/meditate/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/meditate/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -34,7 +35,7 @@ export function toMeditatePreset(local: LocalMeditatePreset): MeditatePreset { isArchived: local.isArchived, order: local.order, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/meditate/stores/meditate.svelte.ts b/apps/mana/apps/web/src/lib/modules/meditate/stores/meditate.svelte.ts index 4d36f42a9..5255cd597 100644 --- a/apps/mana/apps/web/src/lib/modules/meditate/stores/meditate.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/meditate/stores/meditate.svelte.ts @@ -72,14 +72,12 @@ export const meditateStore = { await encryptRecord('meditatePresets', wrapped); await meditatePresetTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, async deletePreset(id: string) { await meditatePresetTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -126,14 +124,12 @@ export const meditateStore = { await encryptRecord('meditateSessions', wrapped); await meditateSessionTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, async deleteSession(id: string) { await meditateSessionTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -151,7 +147,6 @@ export const meditateStore = { if (existing) { await meditateSettingsTable.update('settings', { ...patch, - updatedAt: new Date().toISOString(), }); } else { const newSettings: LocalMeditateSettings = { diff --git a/apps/mana/apps/web/src/lib/modules/memoro/llm-watcher.svelte.ts b/apps/mana/apps/web/src/lib/modules/memoro/llm-watcher.svelte.ts index b00eb384a..89618826d 100644 --- a/apps/mana/apps/web/src/lib/modules/memoro/llm-watcher.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/memoro/llm-watcher.svelte.ts @@ -169,7 +169,6 @@ async function applyRow(row: QueuedTask): Promise { const diff: Partial = { title: titleToWrite, - updatedAt: new Date().toISOString(), metadata: { ...existingMetadata, titleSource: row.source, diff --git a/apps/mana/apps/web/src/lib/modules/memoro/queries.ts b/apps/mana/apps/web/src/lib/modules/memoro/queries.ts index 4de56d5a3..f3aa07176 100644 --- a/apps/mana/apps/web/src/lib/modules/memoro/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/memoro/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -38,7 +39,7 @@ export function toMemo(local: LocalMemo): Memo { visibility: local.visibility ?? 'space', language: local.language, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -49,7 +50,7 @@ export function toMemory(local: LocalMemory): Memory { title: local.title, content: local.content, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -60,7 +61,7 @@ export function toSpace(local: LocalSpace): Space { description: local.description, ownerId: local.ownerId, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts b/apps/mana/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts index 28c62886f..cea32a96d 100644 --- a/apps/mana/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts @@ -31,7 +31,6 @@ export const memoriesStore = { async update(id: string, data: Partial>) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('memories', diff); await memoryTable.update(id, diff); @@ -40,7 +39,7 @@ export const memoriesStore = { /** Soft-delete a memory. */ async delete(id: string) { const now = new Date().toISOString(); - await memoryTable.update(id, { deletedAt: now, updatedAt: now }); + await memoryTable.update(id, { deletedAt: now }); MemoroEvents.memoDeleted(); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts b/apps/mana/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts index 0f126d40c..f8ec084b0 100644 --- a/apps/mana/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts @@ -55,7 +55,6 @@ export const memosStore = { // stores have to set their own createdAt/updatedAt explicitly // (consistent with the rest of the Mana modules). createdAt: now, - updatedAt: now, } as LocalMemo; const plaintextSnapshot = toMemo(newLocal); await encryptRecord('memos', newLocal); @@ -101,7 +100,6 @@ export const memosStore = { transcriptModel: result.model, language: existing.language ?? result.language ?? null, processingStatus: 'completed', - updatedAt: new Date().toISOString(), }; await encryptRecord('memos', diff); await memoTable.update(memoId, diff); @@ -132,7 +130,6 @@ export const memosStore = { await memoTable.update(memoId, { processingStatus: 'failed', metadata: { ...(((await memoTable.get(memoId))?.metadata as object) ?? {}), error: msg }, - updatedAt: new Date().toISOString(), }); } }, @@ -144,7 +141,6 @@ export const memosStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; // If the user is overwriting the title manually, clear the @@ -201,7 +197,6 @@ export const memosStore = { async pin(id: string) { await memoTable.update(id, { isPinned: true, - updatedAt: new Date().toISOString(), }); }, @@ -209,7 +204,6 @@ export const memosStore = { async unpin(id: string) { await memoTable.update(id, { isPinned: false, - updatedAt: new Date().toISOString(), }); }, diff --git a/apps/mana/apps/web/src/lib/modules/mood/stores/mood.svelte.ts b/apps/mana/apps/web/src/lib/modules/mood/stores/mood.svelte.ts index b9b8fdf76..089508416 100644 --- a/apps/mana/apps/web/src/lib/modules/mood/stores/mood.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/mood/stores/mood.svelte.ts @@ -49,7 +49,6 @@ export const moodStore = { const wrapped = await encryptRecord('moodEntries', { ...patch }); await moodEntryTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -57,10 +56,12 @@ export const moodStore = { await moodEntryTable.update(id, { deletedAt: new Date().toISOString() }); }, - async updateSettings(patch: Partial>) { + async updateSettings( + patch: Partial> + ) { const existing = (await moodSettingsTable.toArray()).find((s) => !s.deletedAt); if (existing) { - await moodSettingsTable.update(existing.id, { ...patch, updatedAt: new Date().toISOString() }); + await moodSettingsTable.update(existing.id, { ...patch }); return; } const newLocal: LocalMoodSettings = { diff --git a/apps/mana/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts b/apps/mana/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts index 02458f5c3..9f05945df 100644 --- a/apps/mana/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts @@ -135,7 +135,6 @@ function createMoodsStore() { animation: data.animation, isDefault: false, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); MoodlitEvents.moodCreated(); }, @@ -143,7 +142,6 @@ function createMoodsStore() { async deleteMood(id: string) { await db.table('moods').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); MoodlitEvents.moodDeleted(); }, diff --git a/apps/mana/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts b/apps/mana/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts index 3b6ed3e28..d03c569c4 100644 --- a/apps/mana/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts @@ -136,7 +136,6 @@ function createSequencesStore() { moodIds: data.moodIds, duration: data.duration, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); MoodlitEvents.sequenceCreated(); }, @@ -144,7 +143,6 @@ function createSequencesStore() { async deleteSequence(id: string) { await db.table('sequences').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); MoodlitEvents.sequenceDeleted(); }, diff --git a/apps/mana/apps/web/src/lib/modules/music/ListView.svelte b/apps/mana/apps/web/src/lib/modules/music/ListView.svelte index 8ea78821b..eb7a173b8 100644 --- a/apps/mana/apps/web/src/lib/modules/music/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/music/ListView.svelte @@ -169,7 +169,6 @@ playCount: 0, fileSize: uploadFiles[i]!.file.size, createdAt: now, - updatedAt: now, } as LocalSong); uploadFiles[i]!.status = 'success'; diff --git a/apps/mana/apps/web/src/lib/modules/music/queries.ts b/apps/mana/apps/web/src/lib/modules/music/queries.ts index 389e82f54..34c882fa0 100644 --- a/apps/mana/apps/web/src/lib/modules/music/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/music/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -41,7 +42,7 @@ export function toSong(local: LocalSong): Song { playCount: local.playCount, lastPlayedAt: local.lastPlayedAt ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -52,7 +53,7 @@ export function toPlaylist(local: LocalPlaylist): Playlist { description: local.description ?? null, coverArtPath: local.coverArtPath ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -63,7 +64,7 @@ export function toProject(local: LocalProject): Project { description: local.description ?? null, songId: local.songId ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts index 9bc7c0d75..0fb4e6dc1 100644 --- a/apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts @@ -20,7 +20,6 @@ export const libraryStore = { const newState = !local.favorite; await songTable.update(id, { favorite: newState, - updatedAt: new Date().toISOString(), }); MusicEvents.songFavorited(newState); } @@ -34,7 +33,6 @@ export const libraryStore = { await songTable.update(id, { playCount: (local.playCount || 0) + 1, lastPlayedAt: now, - updatedAt: now, }); const decrypted = await decryptRecord('songs', { ...local }); @@ -71,7 +69,6 @@ export const libraryStore = { ) { const diff: Record = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('songs', diff); await songTable.update(id, diff); @@ -80,7 +77,7 @@ export const libraryStore = { /** Soft-delete a song. */ async delete(id: string) { const now = new Date().toISOString(); - await songTable.update(id, { deletedAt: now, updatedAt: now }); + await songTable.update(id, { deletedAt: now }); MusicEvents.songDeleted(); }, diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts index 3eb5d92de..77d2353b7 100644 --- a/apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts @@ -34,7 +34,6 @@ export const playlistsStore = { async update(id: string, data: Partial>) { const diff: Record = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('mukkePlaylists', diff); await musicPlaylistTable.update(id, diff); @@ -45,10 +44,10 @@ export const playlistsStore = { const now = new Date().toISOString(); // Atomic cascade: playlist + playlistSongs in one Dexie transaction. await db.transaction('rw', musicPlaylistTable, playlistSongTable, async () => { - await musicPlaylistTable.update(id, { deletedAt: now, updatedAt: now }); + await musicPlaylistTable.update(id, { deletedAt: now }); const allPS = await playlistSongTable.where('playlistId').equals(id).toArray(); for (const ps of allPS) { - await playlistSongTable.update(ps.id, { deletedAt: now, updatedAt: now }); + await playlistSongTable.update(ps.id, { deletedAt: now }); } }); MusicEvents.playlistDeleted(); @@ -76,7 +75,7 @@ export const playlistsStore = { const toRemove = allPS.find((ps) => ps.songId === songId && !ps.deletedAt); if (toRemove) { const now = new Date().toISOString(); - await playlistSongTable.update(toRemove.id, { deletedAt: now, updatedAt: now }); + await playlistSongTable.update(toRemove.id, { deletedAt: now }); } }, @@ -87,7 +86,7 @@ export const playlistsStore = { for (let i = 0; i < songIds.length; i++) { const ps = allPS.find((p) => p.songId === songIds[i] && !p.deletedAt); if (ps) { - await playlistSongTable.update(ps.id, { sortOrder: i, updatedAt: now }); + await playlistSongTable.update(ps.id, { sortOrder: i }); } } }, diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts index c51c8227c..93c48ee4c 100644 --- a/apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts @@ -28,14 +28,13 @@ export const projectsStore = { async update(id: string, data: Partial>) { await musicProjectTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); }, /** Soft-delete a project. */ async delete(id: string) { const now = new Date().toISOString(); - await musicProjectTable.update(id, { deletedAt: now, updatedAt: now }); + await musicProjectTable.update(id, { deletedAt: now }); MusicEvents.projectDeleted(); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/news/queries.ts b/apps/mana/apps/web/src/lib/modules/news/queries.ts index 02c089d99..41cb86f52 100644 --- a/apps/mana/apps/web/src/lib/modules/news/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/news/queries.ts @@ -1,4 +1,5 @@ import { formatDate } from '$lib/i18n/format'; +import { deriveUpdatedAt } from '$lib/data/sync'; /** * Reactive queries + type converters for News. * @@ -53,7 +54,7 @@ export function toArticle(local: LocalArticle): Article { isRead: local.isRead, isFavorite: local.isFavorite, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -65,7 +66,7 @@ export function toCategory(local: LocalCategory): Category { icon: local.icon, sortOrder: local.sortOrder, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/news/stores/articles.svelte.ts b/apps/mana/apps/web/src/lib/modules/news/stores/articles.svelte.ts index 6087d0382..479616c0c 100644 --- a/apps/mana/apps/web/src/lib/modules/news/stores/articles.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/news/stores/articles.svelte.ts @@ -61,7 +61,6 @@ export const articlesStore = { async markRead(id: string, isRead = true): Promise { await articleTable.update(id, { isRead, - updatedAt: new Date().toISOString(), }); }, @@ -70,28 +69,24 @@ export const articlesStore = { if (!a) return; await articleTable.update(id, { isFavorite: !a.isFavorite, - updatedAt: new Date().toISOString(), }); }, async archive(id: string): Promise { await articleTable.update(id, { isArchived: true, - updatedAt: new Date().toISOString(), }); }, async setCategory(id: string, categoryId: string | null): Promise { await articleTable.update(id, { categoryId, - updatedAt: new Date().toISOString(), }); }, async delete(id: string): Promise { await articleTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/news/stores/categories.svelte.ts b/apps/mana/apps/web/src/lib/modules/news/stores/categories.svelte.ts index 7aab6532f..876d91b9c 100644 --- a/apps/mana/apps/web/src/lib/modules/news/stores/categories.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/news/stores/categories.svelte.ts @@ -50,7 +50,6 @@ export const categoriesStore = { if (!trimmed) return; const diff: Partial = { name: trimmed, - updatedAt: new Date().toISOString(), }; await encryptRecord('newsCategories', diff); await categoryTable.update(id, diff); @@ -59,14 +58,12 @@ export const categoriesStore = { async setColor(id: string, color: string): Promise { await categoryTable.update(id, { color, - updatedAt: new Date().toISOString(), }); }, async setIcon(id: string, icon: string): Promise { await categoryTable.update(id, { icon, - updatedAt: new Date().toISOString(), }); }, @@ -76,7 +73,7 @@ export const categoriesStore = { // counts (typically <20). const now = new Date().toISOString(); for (let i = 0; i < ids.length; i++) { - await categoryTable.update(ids[i], { sortOrder: i, updatedAt: now }); + await categoryTable.update(ids[i], { sortOrder: i }); } }, @@ -87,7 +84,6 @@ export const categoriesStore = { // don't disappear. A subsequent re-categorize cleans them up. await categoryTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts b/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts index f63db1ba2..c75e323c6 100644 --- a/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/news/stores/preferences.svelte.ts @@ -41,7 +41,6 @@ export const preferencesStore = { preferredLanguages: [...input.languages], blockedSources: [...(input.blockedSources ?? [])], onboardingCompleted: true, - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); await preferencesTable.update(PREFERENCES_ID, diff); @@ -51,7 +50,6 @@ export const preferencesStore = { await ensureRow(); const diff: Partial = { selectedTopics: [...topics], - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); await preferencesTable.update(PREFERENCES_ID, diff); @@ -61,7 +59,6 @@ export const preferencesStore = { await ensureRow(); const diff: Partial = { preferredLanguages: [...languages], - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); await preferencesTable.update(PREFERENCES_ID, diff); @@ -73,7 +70,6 @@ export const preferencesStore = { const next = blocked.includes(slug) ? blocked.filter((s) => s !== slug) : [...blocked, slug]; const diff: Partial = { blockedSources: next, - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); await preferencesTable.update(PREFERENCES_ID, diff); @@ -91,7 +87,6 @@ export const preferencesStore = { await ensureRow(); const update: Partial = { ...diff, - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', update); await preferencesTable.update(PREFERENCES_ID, update); @@ -113,7 +108,6 @@ export const preferencesStore = { ]; const diff: Partial = { customFeeds: next, - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); await preferencesTable.update(PREFERENCES_ID, diff); @@ -126,7 +120,6 @@ export const preferencesStore = { if (next.length === existing.length) return; const diff: Partial = { customFeeds: next, - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); await preferencesTable.update(PREFERENCES_ID, diff); @@ -137,7 +130,6 @@ export const preferencesStore = { const diff: Partial = { topicWeights: {}, sourceWeights: {}, - updatedAt: new Date().toISOString(), }; await encryptRecord('newsPreferences', diff); await preferencesTable.update(PREFERENCES_ID, diff); diff --git a/apps/mana/apps/web/src/lib/modules/news/stores/reactions.svelte.ts b/apps/mana/apps/web/src/lib/modules/news/stores/reactions.svelte.ts index 4ab79efb6..bcf556da6 100644 --- a/apps/mana/apps/web/src/lib/modules/news/stores/reactions.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/news/stores/reactions.svelte.ts @@ -56,7 +56,6 @@ export const reactionsStore = { // suppression. await reactionTable.update(reactionId, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/notes/queries.ts b/apps/mana/apps/web/src/lib/modules/notes/queries.ts index 92e9d372d..51e67135a 100644 --- a/apps/mana/apps/web/src/lib/modules/notes/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/notes/queries.ts @@ -1,4 +1,5 @@ import { formatDate } from '$lib/i18n/format'; +import { deriveUpdatedAt } from '$lib/data/sync'; /** * Reactive Queries & Pure Helpers for Notes module. * @@ -35,7 +36,7 @@ export function toNote(local: LocalNote): Note { isPinned: local.isPinned, isArchived: local.isArchived, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts b/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts index 8707ef8e8..d3da975d4 100644 --- a/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/notes/stores/notes.svelte.ts @@ -76,7 +76,6 @@ export const notesStore = { title, content: transcript, transcriptModel: result.model, - updatedAt: new Date().toISOString(), }; await encryptRecord('notes', diff); await noteTable.update(noteId, diff); @@ -97,7 +96,6 @@ export const notesStore = { // allowlist (color, isPinned, isArchived) pass through untouched. const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('notes', diff); await noteTable.update(id, diff); @@ -106,7 +104,6 @@ export const notesStore = { async deleteNote(id: string) { await noteTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('NoteDeleted', 'notes', 'notes', id, { noteId: id }); }, @@ -116,14 +113,12 @@ export const notesStore = { if (!note) return; await noteTable.update(id, { isPinned: !note.isPinned, - updatedAt: new Date().toISOString(), }); }, async archiveNote(id: string) { await noteTable.update(id, { isArchived: true, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/notes/tools.ts b/apps/mana/apps/web/src/lib/modules/notes/tools.ts index 699829ca2..2f9743f56 100644 --- a/apps/mana/apps/web/src/lib/modules/notes/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/notes/tools.ts @@ -10,6 +10,7 @@ */ import type { ModuleTool } from '$lib/data/tools/types'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { notesStore } from './stores/notes.svelte'; import { noteTagOps } from './stores/tags.svelte'; import { db } from '$lib/data/database'; @@ -122,7 +123,7 @@ export const notesTools: ModuleTool[] = [ excerpt: excerptOf(n.content ?? ''), isPinned: n.isPinned, isArchived: n.isArchived, - updatedAt: n.updatedAt, + updatedAt: deriveUpdatedAt(n), })); return { diff --git a/apps/mana/apps/web/src/lib/modules/period/queries.ts b/apps/mana/apps/web/src/lib/modules/period/queries.ts index 33f8ff046..dba89e69e 100644 --- a/apps/mana/apps/web/src/lib/modules/period/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/period/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecord, decryptRecords } from '$lib/data/crypto'; @@ -28,7 +29,7 @@ export function toPeriod(local: LocalPeriod): Period { isArchived: local.isArchived, notes: local.notes, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -46,7 +47,7 @@ export function toPeriodDayLog(local: LocalPeriodDayLog): PeriodDayLog { sexualActivity: local.sexualActivity, notes: local.notes, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -58,7 +59,7 @@ export function toPeriodSymptom(local: LocalPeriodSymptom): PeriodSymptom { color: local.color, count: local.count ?? 0, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/period/stores/dayLogs.svelte.ts b/apps/mana/apps/web/src/lib/modules/period/stores/dayLogs.svelte.ts index a4c2e7f5e..2358c24e0 100644 --- a/apps/mana/apps/web/src/lib/modules/period/stores/dayLogs.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/period/stores/dayLogs.svelte.ts @@ -67,7 +67,6 @@ export const dayLogsStore = { const updateDiff: Partial = { ...data, logDate, - updatedAt: new Date().toISOString(), }; await encryptRecord('periodDayLogs', updateDiff); await periodDayLogTable.update(existing.id, updateDiff); @@ -125,7 +124,6 @@ export const dayLogsStore = { } await periodDayLogTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -138,7 +136,6 @@ export const dayLogsStore = { if (periodId) { await periodDayLogTable.update(log.id, { periodId, - updatedAt: new Date().toISOString(), }); } } diff --git a/apps/mana/apps/web/src/lib/modules/period/stores/periods.svelte.ts b/apps/mana/apps/web/src/lib/modules/period/stores/periods.svelte.ts index c8fedaa80..d040b8773 100644 --- a/apps/mana/apps/web/src/lib/modules/period/stores/periods.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/period/stores/periods.svelte.ts @@ -37,7 +37,6 @@ export const periodsStore = { await periodTable.update(prev.id, { endDate, length, - updatedAt: new Date().toISOString(), }); } @@ -88,7 +87,6 @@ export const periodsStore = { ) { const diff: Partial = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('periods', diff); await periodTable.update(id, diff); @@ -99,7 +97,6 @@ export const periodsStore = { const period = await periodTable.get(id); await periodTable.update(id, { periodEndDate, - updatedAt: new Date().toISOString(), }); // Update the TimeBlock's endDate to reflect the period duration if (period?.timeBlockId && periodEndDate) { @@ -116,14 +113,12 @@ export const periodsStore = { } await periodTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, async archivePeriod(id: string) { await periodTable.update(id, { isArchived: true, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/period/stores/symptoms.svelte.ts b/apps/mana/apps/web/src/lib/modules/period/stores/symptoms.svelte.ts index 257e17215..d23f5d8e4 100644 --- a/apps/mana/apps/web/src/lib/modules/period/stores/symptoms.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/period/stores/symptoms.svelte.ts @@ -24,14 +24,12 @@ export const symptomsStore = { ) { await periodSymptomTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); }, async deleteSymptom(id: string) { await periodSymptomTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -43,7 +41,6 @@ export const symptomsStore = { const next = Math.max(0, (existing.count ?? 0) + delta); await periodSymptomTable.update(id, { count: next, - updatedAt: new Date().toISOString(), }); } }, diff --git a/apps/mana/apps/web/src/lib/modules/photos/queries.ts b/apps/mana/apps/web/src/lib/modules/photos/queries.ts index cf65d1e98..eaa9c4399 100644 --- a/apps/mana/apps/web/src/lib/modules/photos/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/photos/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import type { LocalAlbum, LocalAlbumItem, LocalFavorite, Album, AlbumItem } from './types'; @@ -23,7 +24,7 @@ export function toAlbum(local: LocalAlbum): Album { autoGenerateValue: local.autoGenerateValue ?? undefined, itemCount: 0, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/photos/stores/albums.svelte.ts b/apps/mana/apps/web/src/lib/modules/photos/stores/albums.svelte.ts index 55bbc0454..46e1644b7 100644 --- a/apps/mana/apps/web/src/lib/modules/photos/stores/albums.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/photos/stores/albums.svelte.ts @@ -23,7 +23,6 @@ export const albumMutations = { autoGenerateType: null, autoGenerateValue: null, createdAt: now, - updatedAt: now, }; await db.table('albums').add(newLocal); PhotosEvents.albumCreated(); @@ -39,7 +38,7 @@ export const albumMutations = { data: { name?: string; description?: string } ): Promise { try { - const updateData: Record = { updatedAt: new Date().toISOString() }; + const updateData: Record = {}; if (data.name !== undefined) updateData.name = data.name; if (data.description !== undefined) updateData.description = data.description ?? null; @@ -58,9 +57,9 @@ export const albumMutations = { // Soft-delete album items first const items = await db.table('albumItems').toArray(); for (const item of items.filter((i) => i.albumId === id)) { - await db.table('albumItems').update(item.id, { deletedAt: now, updatedAt: now }); + await db.table('albumItems').update(item.id, { deletedAt: now }); } - await db.table('albums').update(id, { deletedAt: now, updatedAt: now }); + await db.table('albums').update(id, { deletedAt: now }); PhotosEvents.albumDeleted(); return true; } catch (e) { @@ -85,7 +84,6 @@ export const albumMutations = { mediaId, sortOrder: nextOrder++, createdAt: now, - updatedAt: now, }); } return true; @@ -103,7 +101,7 @@ export const albumMutations = { ); if (item) { const now = new Date().toISOString(); - await db.table('albumItems').update(item.id, { deletedAt: now, updatedAt: now }); + await db.table('albumItems').update(item.id, { deletedAt: now }); } return true; } catch (e) { @@ -116,7 +114,6 @@ export const albumMutations = { try { await db.table('albums').update(albumId, { coverMediaId: mediaId, - updatedAt: new Date().toISOString(), }); return true; } catch (e) { diff --git a/apps/mana/apps/web/src/lib/modules/photos/stores/photos.svelte.ts b/apps/mana/apps/web/src/lib/modules/photos/stores/photos.svelte.ts index 002a7d297..49e71bde1 100644 --- a/apps/mana/apps/web/src/lib/modules/photos/stores/photos.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/photos/stores/photos.svelte.ts @@ -145,14 +145,12 @@ export const photoStore = { if (fav) { await db.table('photoFavorites').update(fav.id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } else { await db.table('photoFavorites').add({ id: crypto.randomUUID(), mediaId, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte b/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte index 90bafde0c..ebcd33118 100644 --- a/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/picture/ListView.svelte @@ -199,7 +199,6 @@ isFavorite: false, downloadCount: 0, createdAt: nowIso, - updatedAt: nowIso, }; await imagesStore.insert(local); uploadFiles[i].status = 'success'; diff --git a/apps/mana/apps/web/src/lib/modules/picture/queries.ts b/apps/mana/apps/web/src/lib/modules/picture/queries.ts index 81b96ef09..ee4cc3c29 100644 --- a/apps/mana/apps/web/src/lib/modules/picture/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/picture/queries.ts @@ -7,6 +7,7 @@ */ import { liveQuery } from 'dexie'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -53,7 +54,7 @@ export function toImage(local: LocalImage): Image { comicPanelIndex: local.comicPanelIndex ?? undefined, comicCharacterId: local.comicCharacterId ?? undefined, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -68,7 +69,7 @@ export function toBoard(local: LocalBoard): Board { backgroundColor: local.backgroundColor, visibility: local.visibility ?? 'private', createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/picture/stores/boards.svelte.ts b/apps/mana/apps/web/src/lib/modules/picture/stores/boards.svelte.ts index 275d05854..6ecc628a6 100644 --- a/apps/mana/apps/web/src/lib/modules/picture/stores/boards.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/picture/stores/boards.svelte.ts @@ -49,7 +49,6 @@ export const boardsStore = { backgroundColor: input.backgroundColor || '#ffffff', visibility: defaultVisibilityFor(getActiveSpace()?.type), createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; // Snapshot plaintext for the return value before encryptRecord @@ -72,7 +71,6 @@ export const boardsStore = { try { const diff: Partial = { ...input, - updatedAt: new Date().toISOString(), }; await encryptRecord('boards', diff); await db.table('boards').update(id, diff); @@ -104,10 +102,10 @@ export const boardsStore = { .equals(id) .toArray(); for (const item of items) { - await db.table('boardItems').update(item.id, { deletedAt: now, updatedAt: now }); + await db.table('boardItems').update(item.id, { deletedAt: now }); } // Soft-delete the board - await db.table('boards').update(id, { deletedAt: now, updatedAt: now }); + await db.table('boards').update(id, { deletedAt: now }); return { success: true }; } catch (e) { error = e instanceof Error ? e.message : 'Failed to delete board'; @@ -145,7 +143,6 @@ export const boardsStore = { backgroundColor: original.backgroundColor, visibility: defaultVisibilityFor(getActiveSpace()?.type), createdAt: now, - updatedAt: now, }; const plaintextSnapshot = toBoard({ ...duplicated }); await encryptRecord('boards', duplicated); @@ -167,7 +164,6 @@ export const boardsStore = { id: crypto.randomUUID(), boardId: newId, createdAt: now, - updatedAt: now, }; await encryptRecord('boardItems', newItem); await db.table('boardItems').add(newItem); @@ -198,7 +194,6 @@ export const boardsStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; if (next === 'unlisted' && !existing.unlistedToken) { patch.unlistedToken = generateUnlistedToken(); diff --git a/apps/mana/apps/web/src/lib/modules/places/queries.ts b/apps/mana/apps/web/src/lib/modules/places/queries.ts index b1e4dd9f6..30cc768df 100644 --- a/apps/mana/apps/web/src/lib/modules/places/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/places/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -28,7 +29,7 @@ export function toPlace(local: LocalPlace): Place { unlistedToken: local.unlistedToken ?? '', unlistedExpiresAt: local.unlistedExpiresAt ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/places/stores/places.svelte.ts b/apps/mana/apps/web/src/lib/modules/places/stores/places.svelte.ts index 499f866e7..d62c59218 100644 --- a/apps/mana/apps/web/src/lib/modules/places/stores/places.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/places/stores/places.svelte.ts @@ -46,7 +46,6 @@ export const placesStore = { visitCount: 0, visibility: defaultVisibilityFor(getActiveSpace()?.type), createdAt: now, - updatedAt: now, }; // Snapshot the plaintext DTO before encryption mutates the record @@ -77,7 +76,6 @@ export const placesStore = { const diff = { ...updateData, - updatedAt: new Date().toISOString(), }; // encryptRecord mutates the diff in place. Fields not in the // places allowlist (lat/lng, isFavorite, isArchived, …) pass @@ -111,7 +109,6 @@ export const placesStore = { await placeTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('PlaceDeleted', 'places', 'places', id, { placeId: id, @@ -125,14 +122,12 @@ export const placesStore = { await placeTable.update(id, { isFavorite: !local.isFavorite, - updatedAt: new Date().toISOString(), }); }, async updateTagIds(id: string, tagIds: string[]) { await placeTable.update(id, { tagIds, - updatedAt: new Date().toISOString(), }); }, @@ -147,7 +142,6 @@ export const placesStore = { await placeTable.update(id, { visitCount: (local.visitCount ?? 0) + 1, lastVisitedAt: now, - updatedAt: now, }); await createBlock({ @@ -183,7 +177,6 @@ export const placesStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; if (next === 'unlisted') { @@ -252,7 +245,6 @@ export const placesStore = { }); await placeTable.update(id, { unlistedToken: token, - updatedAt: new Date().toISOString(), }); return token; } catch (e) { @@ -281,7 +273,6 @@ export const placesStore = { }); await placeTable.update(id, { unlistedExpiresAt: expiresAt ? expiresAt.toISOString() : undefined, - updatedAt: new Date().toISOString(), }); } catch (e) { console.error('[places] setUnlistedExpiry failed', e); diff --git a/apps/mana/apps/web/src/lib/modules/places/stores/tracking.svelte.ts b/apps/mana/apps/web/src/lib/modules/places/stores/tracking.svelte.ts index a9e682831..54c49d673 100644 --- a/apps/mana/apps/web/src/lib/modules/places/stores/tracking.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/places/stores/tracking.svelte.ts @@ -138,7 +138,6 @@ async function logPosition(pos: GeolocationPosition) { timestamp: new Date(pos.timestamp).toISOString(), placeId: nearest?.id, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await encryptRecord('locationLogs', log); @@ -158,7 +157,6 @@ async function logPosition(pos: GeolocationPosition) { const updates: Partial = { visitCount: (local.visitCount ?? 0) + 1, lastVisitedAt: log.timestamp, - updatedAt: new Date().toISOString(), }; // Auto-fill address via reverse geocoding if the place has none @@ -171,7 +169,6 @@ async function logPosition(pos: GeolocationPosition) { await encryptRecord('places', rec); await placeTable.update(nearest.id, { address: rec.address, - updatedAt: new Date().toISOString(), }); } } diff --git a/apps/mana/apps/web/src/lib/modules/plants/mutations.ts b/apps/mana/apps/web/src/lib/modules/plants/mutations.ts index 685c5b792..cb64a108a 100644 --- a/apps/mana/apps/web/src/lib/modules/plants/mutations.ts +++ b/apps/mana/apps/web/src/lib/modules/plants/mutations.ts @@ -41,7 +41,6 @@ export const plantMutations = { healthStatus: null, acquiredAt: dto.acquiredAt ?? null, createdAt: now, - updatedAt: now, }; const plaintextSnapshot = toPlant(newLocal); await encryptRecord('plants', newLocal); @@ -56,9 +55,7 @@ export const plantMutations = { }, async update(id: string, dto: UpdatePlantDto): Promise { - const updateData: Record = { - updatedAt: new Date().toISOString(), - }; + const updateData: Record = {}; if (dto.name !== undefined) updateData.name = dto.name; if (dto.scientificName !== undefined) updateData.scientificName = dto.scientificName ?? null; if (dto.commonName !== undefined) updateData.commonName = dto.commonName ?? null; @@ -81,7 +78,6 @@ export const plantMutations = { async delete(id: string): Promise { await db.table('plants').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('PlantDeleted', 'plants', 'plants', id, { plantId: id }); PlantsEvents.plantDeleted(); @@ -136,7 +132,6 @@ export const wateringMutations = { wateredAt: now, notes: notes ?? null, createdAt: now, - updatedAt: now, }; await db.table('wateringLogs').add(logEntry); @@ -162,7 +157,6 @@ export const wateringMutations = { await db.table('wateringSchedules').update(schedule.id, { lastWateredAt: now, nextWateringAt: nextDate.toISOString(), - updatedAt: now, }); } @@ -182,7 +176,6 @@ export const wateringMutations = { await db.table('wateringSchedules').update(schedule.id, { frequencyDays, nextWateringAt: nextDate.toISOString(), - updatedAt: now, }); } else { const nextDate = new Date(Date.now() + frequencyDays * 86400000); @@ -195,7 +188,6 @@ export const wateringMutations = { reminderEnabled: false, reminderHoursBefore: 0, createdAt: now, - updatedAt: now, }); } }, @@ -228,7 +220,6 @@ export const photoMutations = { isAnalyzed: false, takenAt: now, createdAt: now, - updatedAt: now, }; await db.table('plantPhotos').add(photo); return photo; @@ -240,7 +231,6 @@ export const photoMutations = { const result = await identifyPlant(photo.publicUrl); await db.table('plantPhotos').update(photoId, { isAnalyzed: true, - updatedAt: new Date().toISOString(), }); return result; }, @@ -252,13 +242,13 @@ export const photoMutations = { if (p.plantId !== plantId || p.deletedAt) continue; const shouldBe = p.id === photoId; if (p.isPrimary !== shouldBe) { - await db.table('plantPhotos').update(p.id, { isPrimary: shouldBe, updatedAt: now }); + await db.table('plantPhotos').update(p.id, { isPrimary: shouldBe }); } } }, async remove(photoId: string): Promise { const now = new Date().toISOString(); - await db.table('plantPhotos').update(photoId, { deletedAt: now, updatedAt: now }); + await db.table('plantPhotos').update(photoId, { deletedAt: now }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte index 451a8e78b..3262016c8 100644 --- a/apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte @@ -129,7 +129,6 @@ species: editSpecies.trim() || null, healthStatus: editHealthStatus, acquiredAt: editAcquiredAt ? new Date(editAcquiredAt).toISOString() : null, - updatedAt: new Date().toISOString(), }); } catch (err) { console.error('plant save failed:', err); @@ -142,7 +141,6 @@ await db.table('plants').update(plantId, { healthStatus: editHealthStatus, lightRequirements: editLightRequirements || null, - updatedAt: new Date().toISOString(), }); } catch (err) { console.error('plant select save failed:', err); diff --git a/apps/mana/apps/web/src/lib/modules/playground/queries.ts b/apps/mana/apps/web/src/lib/modules/playground/queries.ts index 69a8b1448..c7a2f50c1 100644 --- a/apps/mana/apps/web/src/lib/modules/playground/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/playground/queries.ts @@ -7,6 +7,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -29,7 +30,7 @@ export function toSnippet(local: LocalPlaygroundSnippet): PlaygroundSnippet { isPinned: local.isPinned ?? false, order: local.order ?? 0, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -65,7 +66,7 @@ export function toConversation(local: LocalPlaygroundConversation): PlaygroundCo isPinned: local.isPinned ?? false, comparisonModels: local.comparisonModels ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/playground/stores/conversations.svelte.ts b/apps/mana/apps/web/src/lib/modules/playground/stores/conversations.svelte.ts index 445976bfa..e89e3bba7 100644 --- a/apps/mana/apps/web/src/lib/modules/playground/stores/conversations.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/playground/stores/conversations.svelte.ts @@ -28,7 +28,6 @@ export const conversationsStore = { isPinned: false, comparisonModels: data.comparisonModels ?? null, createdAt: now, - updatedAt: now, }; const snapshot = toConversation(newLocal); await encryptRecord('playgroundConversations', newLocal); @@ -39,21 +38,18 @@ export const conversationsStore = { async updateTitle(id: string, title: string) { const diff: Partial = { title, - updatedAt: new Date().toISOString(), }; await encryptRecord('playgroundConversations', diff); await playgroundConversationTable.update(id, diff); }, async touch(id: string) { - await playgroundConversationTable.update(id, { - updatedAt: new Date().toISOString(), - }); + await playgroundConversationTable.update(id, {}); }, async remove(id: string) { const now = new Date().toISOString(); - await playgroundConversationTable.update(id, { deletedAt: now, updatedAt: now }); + await playgroundConversationTable.update(id, { deletedAt: now }); }, async addMessage( diff --git a/apps/mana/apps/web/src/lib/modules/playground/stores/snippets.svelte.ts b/apps/mana/apps/web/src/lib/modules/playground/stores/snippets.svelte.ts index 4e6b6cc13..0ba9cdf24 100644 --- a/apps/mana/apps/web/src/lib/modules/playground/stores/snippets.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/playground/stores/snippets.svelte.ts @@ -28,7 +28,6 @@ export const playgroundSnippetsStore = { isPinned: false, order: Date.now(), createdAt: now, - updatedAt: now, }; // Snapshot the plaintext DTO before encryption mutates the record @@ -45,7 +44,6 @@ export const playgroundSnippetsStore = { ): Promise { const diff: Partial & Record = { ...patch, - updatedAt: new Date().toISOString(), }; await encryptRecord('playgroundSnippets', diff); await playgroundSnippetTable.update(id, diff); @@ -56,7 +54,6 @@ export const playgroundSnippetsStore = { if (!local) return; await playgroundSnippetTable.update(id, { isPinned: !local.isPinned, - updatedAt: new Date().toISOString(), }); }, @@ -64,7 +61,6 @@ export const playgroundSnippetsStore = { const now = new Date().toISOString(); await playgroundSnippetTable.update(id, { deletedAt: now, - updatedAt: now, }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/presi/queries.ts b/apps/mana/apps/web/src/lib/modules/presi/queries.ts index a8b79559c..e60afc915 100644 --- a/apps/mana/apps/web/src/lib/modules/presi/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/presi/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecord, decryptRecords } from '$lib/data/crypto'; @@ -21,7 +22,7 @@ export function toDeck(local: LocalDeck): Deck { themeId: local.themeId ?? undefined, visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/presi/stores/decks.svelte.ts b/apps/mana/apps/web/src/lib/modules/presi/stores/decks.svelte.ts index f01cecaa7..c5ced5b4d 100644 --- a/apps/mana/apps/web/src/lib/modules/presi/stores/decks.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/presi/stores/decks.svelte.ts @@ -58,9 +58,7 @@ function createDecksStore() { async function updateDeck(id: string, dto: UpdateDeckDto): Promise { error = null; try { - const localUpdates: Partial & { updatedAt: string } = { - updatedAt: new Date().toISOString(), - }; + const localUpdates: Partial = {}; if (dto.title !== undefined) localUpdates.title = dto.title; if (dto.description !== undefined) localUpdates.description = dto.description; if (dto.themeId !== undefined) localUpdates.themeId = dto.themeId; @@ -109,9 +107,9 @@ function createDecksStore() { await db.transaction('rw', presiDeckTable, slideTable, async () => { const slides = await slideTable.where('deckId').equals(id).toArray(); for (const slide of slides) { - await slideTable.update(slide.id, { deletedAt: now, updatedAt: now }); + await slideTable.update(slide.id, { deletedAt: now }); } - await presiDeckTable.update(id, { deletedAt: now, updatedAt: now }); + await presiDeckTable.update(id, { deletedAt: now }); }); PresiEvents.deckDeleted(); return true; @@ -149,9 +147,7 @@ function createDecksStore() { async function updateSlide(id: string, dto: UpdateSlideDto): Promise { error = null; try { - const localUpdates: Partial & { updatedAt: string } = { - updatedAt: new Date().toISOString(), - }; + const localUpdates: Partial = {}; if (dto.content !== undefined) localUpdates.content = dto.content; if (dto.order !== undefined) localUpdates.order = dto.order; @@ -169,7 +165,7 @@ function createDecksStore() { error = null; try { const now = new Date().toISOString(); - await slideTable.update(id, { deletedAt: now, updatedAt: now }); + await slideTable.update(id, { deletedAt: now }); PresiEvents.slideDeleted(); return true; } catch (e) { @@ -184,7 +180,7 @@ function createDecksStore() { try { const now = new Date().toISOString(); for (const { id, order } of slides) { - await slideTable.update(id, { order, updatedAt: now }); + await slideTable.update(id, { order }); } return true; } catch (e) { @@ -217,7 +213,6 @@ function createDecksStore() { await presiDeckTable.update(deckId, { activeRehearsalBlockId: timeBlockId, - updatedAt: now, }); return timeBlockId; @@ -235,7 +230,6 @@ function createDecksStore() { await presiDeckTable.update(deckId, { activeRehearsalBlockId: null, - updatedAt: now, }); } diff --git a/apps/mana/apps/web/src/lib/modules/profile/migration/repair-silent-twin.ts b/apps/mana/apps/web/src/lib/modules/profile/migration/repair-silent-twin.ts index fe261f2f4..6d3857ea0 100644 --- a/apps/mana/apps/web/src/lib/modules/profile/migration/repair-silent-twin.ts +++ b/apps/mana/apps/web/src/lib/modules/profile/migration/repair-silent-twin.ts @@ -73,7 +73,6 @@ export async function repairSilentTwinAvatarRows(): Promise { const row = victims[i]; await meImagesTable.update(row.id, { primaryFor: i === 0 ? 'face-ref' : null, - updatedAt: nowIso, }); } }); diff --git a/apps/mana/apps/web/src/lib/modules/profile/stores/me-images.svelte.ts b/apps/mana/apps/web/src/lib/modules/profile/stores/me-images.svelte.ts index 2c06506c3..60ec55dda 100644 --- a/apps/mana/apps/web/src/lib/modules/profile/stores/me-images.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/profile/stores/me-images.svelte.ts @@ -120,10 +120,10 @@ async function setPrimaryInTx(id: string | null, slot: MeImagePrimarySlot): Prom .toArray(); for (const row of current) { if (row.id === id) continue; - await meImagesTable.update(row.id, { primaryFor: null, updatedAt: nowIso }); + await meImagesTable.update(row.id, { primaryFor: null }); } if (id !== null) { - await meImagesTable.update(id, { primaryFor: slot, updatedAt: nowIso }); + await meImagesTable.update(id, { primaryFor: slot }); } }); } @@ -167,10 +167,7 @@ export const meImagesStore = { ): Promise { const wrapped = { ...patch } as Record; await encryptRecord('meImages', wrapped); - await meImagesTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await meImagesTable.update(id, wrapped as never); }, /** @@ -187,7 +184,6 @@ export const meImagesStore = { }; await meImagesTable.update(id, { usage: nextUsage, - updatedAt: new Date().toISOString(), }); emitDomainEvent('MeImageAiReferenceToggled', 'profile', 'meImages', id, { meImageId: id, @@ -221,7 +217,7 @@ export const meImagesStore = { const wasAvatarRelevant = existing?.primaryFor === 'face-ref' || existing?.primaryFor === 'avatar'; const nowIso = new Date().toISOString(); - await meImagesTable.update(id, { primaryFor: null, updatedAt: nowIso }); + await meImagesTable.update(id, { primaryFor: null }); emitDomainEvent('MeImagePrimaryChanged', 'profile', 'meImages', id, { meImageId: id, slot: null, @@ -247,7 +243,6 @@ export const meImagesStore = { const nowIso = new Date().toISOString(); await meImagesTable.update(id, { deletedAt: nowIso, - updatedAt: nowIso, // Dropping a primary-holder silently leaves the slot empty; // the UI's primary-picker will prompt the user to pick a new // one next time it renders. diff --git a/apps/mana/apps/web/src/lib/modules/profile/stores/user-context.svelte.ts b/apps/mana/apps/web/src/lib/modules/profile/stores/user-context.svelte.ts index b76b191b2..6f1176331 100644 --- a/apps/mana/apps/web/src/lib/modules/profile/stores/user-context.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/profile/stores/user-context.svelte.ts @@ -54,7 +54,6 @@ export const userContextStore = { const merged = { ...current[section], ...value }; const diff: Partial = { [section]: merged, - updatedAt: new Date().toISOString(), }; await encryptRecord('userContext', diff); await userContextTable.update(USER_CONTEXT_SINGLETON_ID, diff); @@ -89,12 +88,11 @@ export const userContextStore = { // Nested field: e.g. 'about.occupation' const sectionObj = { ...(current[section] as Record) }; sectionObj[field] = finalValue; - diff = { [section]: sectionObj, updatedAt: new Date().toISOString() }; + diff = { [section]: sectionObj }; } else { // Top-level field: e.g. 'interests', 'goals' diff = { [section]: finalValue, - updatedAt: new Date().toISOString(), } as Partial; } @@ -107,7 +105,6 @@ export const userContextStore = { await ensureDoc(); const diff: Partial = { interests, - updatedAt: new Date().toISOString(), }; await encryptRecord('userContext', diff); await userContextTable.update(USER_CONTEXT_SINGLETON_ID, diff); @@ -118,7 +115,6 @@ export const userContextStore = { await ensureDoc(); const diff: Partial = { goals, - updatedAt: new Date().toISOString(), }; await encryptRecord('userContext', diff); await userContextTable.update(USER_CONTEXT_SINGLETON_ID, diff); @@ -129,7 +125,6 @@ export const userContextStore = { await ensureDoc(); const diff: Partial = { freeform: content, - updatedAt: new Date().toISOString(), }; await encryptRecord('userContext', diff); await userContextTable.update(USER_CONTEXT_SINGLETON_ID, diff); @@ -156,7 +151,6 @@ export const userContextStore = { // interview is not encrypted — update directly await userContextTable.update(USER_CONTEXT_SINGLETON_ID, { interview, - updatedAt: new Date().toISOString(), }); }, @@ -171,7 +165,6 @@ export const userContextStore = { interview.lastSessionAt = new Date().toISOString(); await userContextTable.update(USER_CONTEXT_SINGLETON_ID, { interview, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/profile/types.ts b/apps/mana/apps/web/src/lib/modules/profile/types.ts index 26afb0e2f..90824e0f1 100644 --- a/apps/mana/apps/web/src/lib/modules/profile/types.ts +++ b/apps/mana/apps/web/src/lib/modules/profile/types.ts @@ -6,6 +6,7 @@ */ import type { BaseRecord } from '@mana/local-store'; +import { deriveUpdatedAt } from '$lib/data/sync'; export const USER_CONTEXT_SINGLETON_ID = 'singleton' as const; @@ -100,7 +101,7 @@ export function toUserContext(local: LocalUserContext): UserContext { freeform: local.freeform ?? '', interview: local.interview ?? { answeredIds: [], skippedIds: [] }, createdAt: local.createdAt ?? '', - updatedAt: local.updatedAt ?? '', + updatedAt: deriveUpdatedAt(local), }; } @@ -214,6 +215,6 @@ export function toMeImage(local: LocalMeImage): MeImage { primaryFor: local.primaryFor ?? null, spaceId: local.spaceId, createdAt: local.createdAt ?? '', - updatedAt: local.updatedAt ?? '', + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/questions/queries.ts b/apps/mana/apps/web/src/lib/modules/questions/queries.ts index e37d4fa60..c1e1a951e 100644 --- a/apps/mana/apps/web/src/lib/modules/questions/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/questions/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -66,7 +67,7 @@ export function toCollection(local: LocalCollection): Collection { isDefault: local.isDefault, sortOrder: local.sortOrder, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -81,7 +82,7 @@ export function toQuestion(local: LocalQuestion): Question { tags: local.tags ?? [], researchDepth: local.researchDepth, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -95,7 +96,7 @@ export function toAnswer(local: LocalAnswer): Answer { rating: local.rating ?? undefined, isAccepted: local.isAccepted, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/questions/stores/answers.svelte.ts b/apps/mana/apps/web/src/lib/modules/questions/stores/answers.svelte.ts index d08e3946f..428e6509b 100644 --- a/apps/mana/apps/web/src/lib/modules/questions/stores/answers.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/questions/stores/answers.svelte.ts @@ -44,7 +44,6 @@ async function createManual(input: CreateManualAnswerInput): Promise { rating: null, isAccepted: false, createdAt: now, - updatedAt: now, }; await encryptRecord('answers', row); await db.table('answers').add(row); @@ -86,7 +85,6 @@ async function startResearch(opts: StartResearchOptions): Promise = { content: (decrypted.content ?? '') + delta, - updatedAt: new Date().toISOString(), }; await encryptRecord('answers', updated); await db.table('answers').update(answerId, updated); @@ -167,7 +163,6 @@ async function startResearch(opts: StartResearchOptions): Promise = { content, citations, - updatedAt: new Date().toISOString(), }; await encryptRecord('answers', update); await db.table('answers').update(answerId, update); await db.table('questions').update(questionId, { status: 'answered', - updatedAt: new Date().toISOString(), }); } @@ -244,20 +237,17 @@ async function accept(answerId: string, questionId: string): Promise { if (a.isAccepted) { await db.table('answers').update(a.id, { isAccepted: false, - updatedAt: new Date().toISOString(), }); } } await db.table('answers').update(answerId, { isAccepted: true, - updatedAt: new Date().toISOString(), }); } async function softDelete(answerId: string): Promise { await db.table('answers').update(answerId, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte index a35fe4e16..4ebb9f7ac 100644 --- a/apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte @@ -41,7 +41,6 @@ status: editStatus, priority: editPriority, researchDepth: editResearchDepth, - updatedAt: new Date().toISOString(), }; await encryptRecord('questions', diff); await db.table('questions').update(questionId, diff); @@ -52,14 +51,12 @@ status: editStatus, priority: editPriority, researchDepth: editResearchDepth, - updatedAt: new Date().toISOString(), }); } async function deleteQuestion() { await db.table('questions').update(questionId, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/modules/quiz/queries.ts b/apps/mana/apps/web/src/lib/modules/quiz/queries.ts index b8eee02d8..9bf2f38c0 100644 --- a/apps/mana/apps/web/src/lib/modules/quiz/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/quiz/queries.ts @@ -8,6 +8,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -36,7 +37,7 @@ export function toQuiz(local: LocalQuiz): Quiz { isArchived: local.isArchived ?? false, visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -50,7 +51,7 @@ export function toQuestion(local: LocalQuizQuestion): QuizQuestion { options: local.options ?? [], explanation: local.explanation, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -63,7 +64,7 @@ export function toAttempt(local: LocalQuizAttempt): QuizAttempt { score: local.score ?? 0, answers: local.answers ?? [], createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/quiz/stores/attempts.svelte.ts b/apps/mana/apps/web/src/lib/modules/quiz/stores/attempts.svelte.ts index 71574ff9c..23c2c6d0a 100644 --- a/apps/mana/apps/web/src/lib/modules/quiz/stores/attempts.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/quiz/stores/attempts.svelte.ts @@ -39,11 +39,10 @@ export const attemptsStore = { answers, score, finishedAt: now(), - updatedAt: now(), }); }, async deleteAttempt(id: string) { - await quizAttemptTable.update(id, { deletedAt: now(), updatedAt: now() }); + await quizAttemptTable.update(id, { deletedAt: now() }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/quiz/stores/quizzes.svelte.ts b/apps/mana/apps/web/src/lib/modules/quiz/stores/quizzes.svelte.ts index d12e7efbc..ec6fc5304 100644 --- a/apps/mana/apps/web/src/lib/modules/quiz/stores/quizzes.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/quiz/stores/quizzes.svelte.ts @@ -48,23 +48,21 @@ export const quizzesStore = { Pick > ) { - const diff: Partial = { ...data, updatedAt: now() }; + const diff: Partial = { ...data }; await encryptRecord('quizzes', diff); await quizTable.update(id, diff); }, async deleteQuiz(id: string) { - await quizTable.update(id, { deletedAt: now(), updatedAt: now() }); + await quizTable.update(id, { deletedAt: now() }); const questions = await quizQuestionTable.where('quizId').equals(id).toArray(); - await Promise.all( - questions.map((q) => quizQuestionTable.update(q.id, { deletedAt: now(), updatedAt: now() })) - ); + await Promise.all(questions.map((q) => quizQuestionTable.update(q.id, { deletedAt: now() }))); }, async togglePin(id: string) { const quiz = await quizTable.get(id); if (!quiz) return; - await quizTable.update(id, { isPinned: !quiz.isPinned, updatedAt: now() }); + await quizTable.update(id, { isPinned: !quiz.isPinned }); }, /** @@ -126,7 +124,7 @@ export const quizzesStore = { Pick > ) { - const diff: Partial = { ...data, updatedAt: now() }; + const diff: Partial = { ...data }; await encryptRecord('quizQuestions', diff); await quizQuestionTable.update(id, diff); }, @@ -134,7 +132,7 @@ export const quizzesStore = { async deleteQuestion(id: string) { const q = await quizQuestionTable.get(id); if (!q) return; - await quizQuestionTable.update(id, { deletedAt: now(), updatedAt: now() }); + await quizQuestionTable.update(id, { deletedAt: now() }); await this.recountQuestions(q.quizId); }, @@ -142,6 +140,6 @@ export const quizzesStore = { const live = (await quizQuestionTable.where('quizId').equals(quizId).toArray()).filter( (q) => !q.deletedAt ); - await quizTable.update(quizId, { questionCount: live.length, updatedAt: now() }); + await quizTable.update(quizId, { questionCount: live.length }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/quiz/tools.ts b/apps/mana/apps/web/src/lib/modules/quiz/tools.ts index 38b0dc182..d7540d395 100644 --- a/apps/mana/apps/web/src/lib/modules/quiz/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/quiz/tools.ts @@ -17,6 +17,7 @@ */ import type { ModuleTool } from '$lib/data/tools/types'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { quizzesStore } from './stores/quizzes.svelte'; import { toQuiz, toQuestion } from './queries'; import { db } from '$lib/data/database'; @@ -423,7 +424,7 @@ export const quizTools: ModuleTool[] = [ category: q.category, questionCount: q.questionCount, isPinned: q.isPinned, - updatedAt: q.updatedAt, + updatedAt: deriveUpdatedAt(q), })); return { diff --git a/apps/mana/apps/web/src/lib/modules/quotes/ListView.svelte b/apps/mana/apps/web/src/lib/modules/quotes/ListView.svelte index d6e67ceca..a56250edb 100644 --- a/apps/mana/apps/web/src/lib/modules/quotes/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/quotes/ListView.svelte @@ -91,7 +91,6 @@ if (!current.includes(tagData.id)) { await db.table('quotesFavorites').update(fav.id, { tagIds: [...current, tagData.id], - updatedAt: new Date().toISOString(), }); } } diff --git a/apps/mana/apps/web/src/lib/modules/quotes/queries.ts b/apps/mana/apps/web/src/lib/modules/quotes/queries.ts index cb3ddab39..fa6454a07 100644 --- a/apps/mana/apps/web/src/lib/modules/quotes/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/quotes/queries.ts @@ -3,6 +3,7 @@ */ import { liveQuery } from 'dexie'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import type { LocalFavorite, LocalQuoteList, LocalCustomQuote } from './types'; @@ -65,7 +66,7 @@ export function toQuoteList(local: LocalQuoteList): QuoteList { description: local.description ?? undefined, quoteIds: local.quoteIds, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/quotes/stores/custom-quotes.svelte.ts b/apps/mana/apps/web/src/lib/modules/quotes/stores/custom-quotes.svelte.ts index cea44fd96..9254b1e25 100644 --- a/apps/mana/apps/web/src/lib/modules/quotes/stores/custom-quotes.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/quotes/stores/custom-quotes.svelte.ts @@ -26,7 +26,6 @@ export const customQuotesStore = { source: input.source ?? null, year: input.year ?? null, createdAt: now, - updatedAt: now, }); return id; }, @@ -34,14 +33,12 @@ export const customQuotesStore = { async update(id: string, updates: Partial): Promise { await db.table('customQuotes').update(id, { ...updates, - updatedAt: new Date().toISOString(), }); }, async remove(id: string): Promise { await db.table('customQuotes').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/quotes/stores/favorites.svelte.ts b/apps/mana/apps/web/src/lib/modules/quotes/stores/favorites.svelte.ts index 5f9813e2c..5e81ef6a2 100644 --- a/apps/mana/apps/web/src/lib/modules/quotes/stores/favorites.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/quotes/stores/favorites.svelte.ts @@ -15,7 +15,6 @@ export const favoritesStore = { id: crypto.randomUUID(), quoteId, createdAt: now, - updatedAt: now, }); }, @@ -24,7 +23,6 @@ export const favoritesStore = { if (fav) { await db.table('quotesFavorites').update(fav.id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } }, @@ -41,7 +39,6 @@ export const favoritesStore = { async setNotes(favoriteId: string, notes: string) { await db.table('quotesFavorites').update(favoriteId, { notes: notes || null, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/quotes/stores/lists.svelte.ts b/apps/mana/apps/web/src/lib/modules/quotes/stores/lists.svelte.ts index 4faae5a18..4ee51d5cd 100644 --- a/apps/mana/apps/web/src/lib/modules/quotes/stores/lists.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/quotes/stores/lists.svelte.ts @@ -26,7 +26,6 @@ export const listsStore = { description: description ?? null, quoteIds: [], createdAt: now, - updatedAt: now, }; await db.table('quotesLists').add(newLocal); QuotesEvents.listCreated(); @@ -43,7 +42,6 @@ export const listsStore = { try { await db.table('quotesLists').update(id, { ...updates, - updatedAt: new Date().toISOString(), }); const updated = await db.table('quotesLists').get(id); return updated ? toQuoteList(updated) : null; @@ -56,7 +54,6 @@ export const listsStore = { try { await db.table('quotesLists').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); QuotesEvents.listDeleted(); return true; @@ -77,7 +74,6 @@ export const listsStore = { await db.table('quotesLists').update(listId, { quoteIds, - updatedAt: new Date().toISOString(), }); return true; } catch { @@ -94,7 +90,6 @@ export const listsStore = { await db.table('quotesLists').update(listId, { quoteIds, - updatedAt: new Date().toISOString(), }); return true; } catch { diff --git a/apps/mana/apps/web/src/lib/modules/recipes/queries.ts b/apps/mana/apps/web/src/lib/modules/recipes/queries.ts index 2d73a49f7..48a774c66 100644 --- a/apps/mana/apps/web/src/lib/modules/recipes/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/recipes/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -29,7 +30,7 @@ export function toRecipe(local: LocalRecipe): Recipe { photoThumbnailUrl: local.photoThumbnailUrl ?? null, visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/recipes/stores/recipes.svelte.ts b/apps/mana/apps/web/src/lib/modules/recipes/stores/recipes.svelte.ts index bf940359c..572b52758 100644 --- a/apps/mana/apps/web/src/lib/modules/recipes/stores/recipes.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/recipes/stores/recipes.svelte.ts @@ -78,16 +78,12 @@ export const recipesStore = { ) { const wrapped = { ...patch } as Record; await encryptRecord('recipes', wrapped); - await recipeTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await recipeTable.update(id, wrapped as never); }, async deleteRecipe(id: string) { await recipeTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('RecipeDeleted', 'recipes', 'recipes', id, { recipeId: id }); }, @@ -97,7 +93,6 @@ export const recipesStore = { if (!existing) return; await recipeTable.update(id, { isFavorite: !existing.isFavorite, - updatedAt: new Date().toISOString(), }); }, @@ -117,14 +112,13 @@ export const recipesStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; if (next === 'unlisted' && !existing.unlistedToken) { patch.unlistedToken = generateUnlistedToken(); } else if (next !== 'unlisted' && existing.unlistedToken) { patch.unlistedToken = undefined; } - await recipeTable.update(id, patch); + await recipeTable.update(id, patch as never); emitDomainEvent('VisibilityChanged', 'recipes', 'recipes', id, { recordId: id, diff --git a/apps/mana/apps/web/src/lib/modules/skilltree/queries.ts b/apps/mana/apps/web/src/lib/modules/skilltree/queries.ts index e6ec74ae1..d53d5df25 100644 --- a/apps/mana/apps/web/src/lib/modules/skilltree/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/skilltree/queries.ts @@ -7,6 +7,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import type { LocalSkill, LocalActivity, LocalAchievement } from './types'; @@ -28,7 +29,7 @@ export function toSkill(local: LocalSkill): Skill { totalXp: local.totalXp, level: local.level, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/skilltree/stores/achievements.svelte.ts b/apps/mana/apps/web/src/lib/modules/skilltree/stores/achievements.svelte.ts index 21159e78a..ec852e0de 100644 --- a/apps/mana/apps/web/src/lib/modules/skilltree/stores/achievements.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/skilltree/stores/achievements.svelte.ts @@ -103,7 +103,6 @@ async function seedIfEmpty() { icon: def.icon, unlockedAt: '', createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } } @@ -198,7 +197,6 @@ async function checkLocal(context: { const now = new Date().toISOString(); await db.table('achievements').update(a.id, { unlockedAt: now, - updatedAt: now, }); newlyUnlocked.push({ achievement: a, xpReward: a.xpReward }); } diff --git a/apps/mana/apps/web/src/lib/modules/skilltree/stores/skills.svelte.ts b/apps/mana/apps/web/src/lib/modules/skilltree/stores/skills.svelte.ts index 8c633aff2..2fb404d31 100644 --- a/apps/mana/apps/web/src/lib/modules/skilltree/stores/skills.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/skilltree/stores/skills.svelte.ts @@ -32,7 +32,6 @@ async function addSkill(data: Partial): Promise { await db.table('skills').add({ ...localSkill, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('SkillCreated', 'skilltree', 'skills', skill.id, { skillId: skill.id, @@ -43,9 +42,7 @@ async function addSkill(data: Partial): Promise { } async function updateSkill(id: string, updates: Partial): Promise { - const localUpdates: Partial & { updatedAt: string } = { - updatedAt: new Date().toISOString(), - }; + const localUpdates: Partial = {}; if (updates.name !== undefined) localUpdates.name = updates.name; if (updates.description !== undefined) localUpdates.description = updates.description; if (updates.branch !== undefined) localUpdates.branch = updates.branch; @@ -65,9 +62,9 @@ async function deleteSkill(id: string): Promise { .equals(id) .toArray(); for (const a of skillActivities) { - await db.table('activities').update(a.id, { deletedAt: now, updatedAt: now }); + await db.table('activities').update(a.id, { deletedAt: now }); } - await db.table('skills').update(id, { deletedAt: now, updatedAt: now }); + await db.table('skills').update(id, { deletedAt: now }); SkillTreeEvents.skillDeleted(); } @@ -89,7 +86,6 @@ async function addXp( totalXp: newTotalXp, currentXp: newCurrentXp, level: newLevel, - updatedAt: new Date().toISOString(), }); const activity = createActivity(skillId, xp, description, duration); @@ -102,7 +98,6 @@ async function addXp( duration: activity.duration, timestamp: activity.timestamp, createdAt: now, - updatedAt: now, }); // Create a TimeBlock for practice sessions with duration diff --git a/apps/mana/apps/web/src/lib/modules/sleep/queries.ts b/apps/mana/apps/web/src/lib/modules/sleep/queries.ts index ff1199e47..e7f568899 100644 --- a/apps/mana/apps/web/src/lib/modules/sleep/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/sleep/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -39,7 +40,7 @@ export function toSleepEntry(local: LocalSleepEntry): SleepEntry { tags: local.tags ?? [], dreamIds: local.dreamIds ?? [], createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -64,7 +65,7 @@ export function toSleepHygieneCheck(local: LocalSleepHygieneCheck): SleepHygiene isPreset: local.isPreset, order: local.order, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/sleep/stores/sleep.svelte.ts b/apps/mana/apps/web/src/lib/modules/sleep/stores/sleep.svelte.ts index 935a6407c..f71f1aeba 100644 --- a/apps/mana/apps/web/src/lib/modules/sleep/stores/sleep.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/sleep/stores/sleep.svelte.ts @@ -62,7 +62,6 @@ export const sleepStore = { const wrapped = await encryptRecord('sleepEntries', { ...patch }); await sleepEntryTable.update(existing.id, { ...wrapped, - updatedAt: new Date().toISOString(), }); return toSleepEntry({ ...existing, ...patch }); } @@ -123,10 +122,7 @@ export const sleepStore = { } } const wrapped = await encryptRecord('sleepEntries', update); - await sleepEntryTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await sleepEntryTable.update(id, wrapped as never); }, async deleteEntry(id: string) { @@ -154,7 +150,6 @@ export const sleepStore = { await sleepHygieneLogTable.update(existing.id, { completedCheckIds: input.completedCheckIds, score, - updatedAt: new Date().toISOString(), }); return toSleepHygieneLog({ ...existing, completedCheckIds: input.completedCheckIds, score }); } @@ -198,7 +193,6 @@ export const sleepStore = { const wrapped = await encryptRecord('sleepHygieneChecks', { ...patch }); await sleepHygieneCheckTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -207,7 +201,6 @@ export const sleepStore = { if (!check) return; await sleepHygieneCheckTable.update(id, { isActive: !check.isActive, - updatedAt: new Date().toISOString(), }); }, @@ -237,7 +230,6 @@ export const sleepStore = { if (existing) { await sleepSettingsTable.update(existing.id, { ...patch, - updatedAt: new Date().toISOString(), }); return; } diff --git a/apps/mana/apps/web/src/lib/modules/storage/queries.ts b/apps/mana/apps/web/src/lib/modules/storage/queries.ts index 7a94c0da8..0f12a3ae4 100644 --- a/apps/mana/apps/web/src/lib/modules/storage/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/storage/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -68,7 +69,7 @@ export function toFile(local: LocalFile): StorageFile { isDeleted: local.isDeleted, deletedAt: null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -85,7 +86,7 @@ export function toFolder(local: LocalFolder): StorageFolder { isDeleted: local.isDeleted, deletedAt: null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/storage/stores/files.svelte.ts b/apps/mana/apps/web/src/lib/modules/storage/stores/files.svelte.ts index 21bf53d66..6a1de35df 100644 --- a/apps/mana/apps/web/src/lib/modules/storage/stores/files.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/storage/stores/files.svelte.ts @@ -123,7 +123,6 @@ export const filesStore = { async renameFile(id: string, name: string) { const diff: Record = { name, - updatedAt: new Date().toISOString(), }; await encryptRecord('files', diff); await fileTable.update(id, diff); @@ -132,7 +131,6 @@ export const filesStore = { async renameFolder(id: string, name: string) { await storageFolderTable.update(id, { name, - updatedAt: new Date().toISOString(), }); }, @@ -142,7 +140,6 @@ export const filesStore = { const newFav = !file.isFavorite; await fileTable.update(id, { isFavorite: newFav, - updatedAt: new Date().toISOString(), }); StorageEvents.fileFavorited(newFav); return newFav; @@ -156,7 +153,6 @@ export const filesStore = { const newFav = !folder.isFavorite; await storageFolderTable.update(id, { isFavorite: newFav, - updatedAt: new Date().toISOString(), }); StorageEvents.folderFavorited(newFav); return newFav; @@ -167,7 +163,6 @@ export const filesStore = { async deleteFile(id: string) { await fileTable.update(id, { isDeleted: true, - updatedAt: new Date().toISOString(), }); StorageEvents.fileDeleted(); }, @@ -175,7 +170,6 @@ export const filesStore = { async deleteFolder(id: string) { await storageFolderTable.update(id, { isDeleted: true, - updatedAt: new Date().toISOString(), }); StorageEvents.folderDeleted(); }, @@ -183,52 +177,46 @@ export const filesStore = { async restoreFile(id: string) { await fileTable.update(id, { isDeleted: false, - updatedAt: new Date().toISOString(), }); }, async restoreFolder(id: string) { await storageFolderTable.update(id, { isDeleted: false, - updatedAt: new Date().toISOString(), }); }, async permanentDeleteFile(id: string) { await fileTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, async permanentDeleteFolder(id: string) { await storageFolderTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, async moveFile(id: string, targetFolderId: string) { await fileTable.update(id, { parentFolderId: targetFolderId, - updatedAt: new Date().toISOString(), }); }, async moveFolder(id: string, targetFolderId: string) { await storageFolderTable.update(id, { parentFolderId: targetFolderId, - updatedAt: new Date().toISOString(), }); }, async deleteSelected() { const now = new Date().toISOString(); for (const id of selectedFileIds) { - await fileTable.update(id, { isDeleted: true, updatedAt: now }); + await fileTable.update(id, { isDeleted: true }); } for (const id of selectedFolderIds) { - await storageFolderTable.update(id, { isDeleted: true, updatedAt: now }); + await storageFolderTable.update(id, { isDeleted: true }); } const count = selectedFileIds.size + selectedFolderIds.size; selectedFileIds = new Set(); diff --git a/apps/mana/apps/web/src/lib/modules/stretch/queries.ts b/apps/mana/apps/web/src/lib/modules/stretch/queries.ts index a5c072065..05ffe4776 100644 --- a/apps/mana/apps/web/src/lib/modules/stretch/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/stretch/queries.ts @@ -5,6 +5,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { decryptRecords } from '$lib/data/crypto'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -39,7 +40,7 @@ export function toStretchExercise(local: LocalStretchExercise): StretchExercise isArchived: local.isArchived, order: local.order, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -58,7 +59,7 @@ export function toStretchRoutine(local: LocalStretchRoutine): StretchRoutine { isPinned: local.isPinned, order: local.order, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -102,7 +103,7 @@ export function toStretchReminder(local: LocalStretchReminder): StretchReminder isActive: local.isActive, lastTriggeredAt: local.lastTriggeredAt ?? null, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/stretch/stores/stretch.svelte.ts b/apps/mana/apps/web/src/lib/modules/stretch/stores/stretch.svelte.ts index 7ae18727b..458e40538 100644 --- a/apps/mana/apps/web/src/lib/modules/stretch/stores/stretch.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/stretch/stores/stretch.svelte.ts @@ -87,7 +87,6 @@ export const stretchStore = { const wrapped = await encryptRecord('stretchExercises', { ...patch }); await stretchExerciseTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -96,7 +95,6 @@ export const stretchStore = { if (!exercise || exercise.isPreset) return; await stretchExerciseTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -155,7 +153,6 @@ export const stretchStore = { const wrapped = await encryptRecord('stretchRoutines', { ...patch }); await stretchRoutineTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -164,7 +161,6 @@ export const stretchStore = { if (!routine || routine.isPreset) return; await stretchRoutineTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -173,7 +169,6 @@ export const stretchStore = { if (!routine) return; await stretchRoutineTable.update(id, { isPinned: !routine.isPinned, - updatedAt: new Date().toISOString(), }); }, @@ -225,7 +220,6 @@ export const stretchStore = { const wrapped = await encryptRecord('stretchSessions', { ...patch }); await stretchSessionTable.update(id, { ...wrapped, - updatedAt: now, }); }, @@ -297,7 +291,6 @@ export const stretchStore = { const wrapped = await encryptRecord('stretchReminders', { ...patch }); await stretchReminderTable.update(id, { ...wrapped, - updatedAt: new Date().toISOString(), }); }, @@ -306,7 +299,6 @@ export const stretchStore = { if (!reminder) return; await stretchReminderTable.update(id, { isActive: !reminder.isActive, - updatedAt: new Date().toISOString(), }); }, diff --git a/apps/mana/apps/web/src/lib/modules/times/queries.ts b/apps/mana/apps/web/src/lib/modules/times/queries.ts index 935dee394..abf046bb1 100644 --- a/apps/mana/apps/web/src/lib/modules/times/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/times/queries.ts @@ -5,6 +5,7 @@ */ import { liveQuery } from 'dexie'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; @@ -44,7 +45,7 @@ export function toClient(local: LocalClient): Client { notes: local.notes ?? undefined, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -63,7 +64,7 @@ export function toProject(local: LocalProject): Project { guildId: local.guildId ?? undefined, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -89,7 +90,7 @@ export function toTimeEntry(local: LocalTimeEntry, block?: LocalTimeBlock | null guildId: local.guildId ?? undefined, source: local.source ?? undefined, createdAt: local.createdAt ?? now, - updatedAt: local.updatedAt ?? now, + updatedAt: deriveUpdatedAt(local), }; } @@ -105,7 +106,7 @@ export function toTemplate(local: LocalTemplate): EntryTemplate { usageCount: local.usageCount, lastUsedAt: local.lastUsedAt ?? undefined, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -122,7 +123,7 @@ export function toSettings(local: LocalSettings): TimesSettings { timerReminderMinutes: local.timerReminderMinutes, autoStopTimerHours: local.autoStopTimerHours, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -139,7 +140,7 @@ export function toAlarm(local: LocalAlarm): Alarm { sound: local.sound, vibrate: local.vibrate ?? null, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -154,7 +155,7 @@ export function toCountdownTimer(local: LocalCountdownTimer): Timer { pausedAt: local.pausedAt, sound: local.sound, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/times/stores/alarms.svelte.ts b/apps/mana/apps/web/src/lib/modules/times/stores/alarms.svelte.ts index b611e1c85..7fc8e1d1a 100644 --- a/apps/mana/apps/web/src/lib/modules/times/stores/alarms.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/times/stores/alarms.svelte.ts @@ -34,7 +34,6 @@ export const alarmsStore = { sound: input.sound ?? null, vibrate: input.vibrate ?? null, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await db.table('timeAlarms').add(newLocal); @@ -52,9 +51,7 @@ export const alarmsStore = { async updateAlarm(id: string, input: UpdateAlarmInput) { error = null; try { - const updateData: Partial = { - updatedAt: new Date().toISOString(), - }; + const updateData: Partial = {}; if (input.label !== undefined) updateData.label = input.label ?? null; if (input.time !== undefined) updateData.time = input.time; if (input.enabled !== undefined) updateData.enabled = input.enabled; @@ -94,7 +91,6 @@ export const alarmsStore = { try { await db.table('timeAlarms').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); return { success: true }; } catch (e) { diff --git a/apps/mana/apps/web/src/lib/modules/times/stores/countdown-timers.svelte.ts b/apps/mana/apps/web/src/lib/modules/times/stores/countdown-timers.svelte.ts index c634fc155..640a40b88 100644 --- a/apps/mana/apps/web/src/lib/modules/times/stores/countdown-timers.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/times/stores/countdown-timers.svelte.ts @@ -35,7 +35,6 @@ export const countdownTimersStore = { pausedAt: null, sound: input.sound ?? null, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await db.table('timeCountdownTimers').add(newLocal); @@ -53,9 +52,7 @@ export const countdownTimersStore = { async updateTimer(id: string, input: UpdateTimerInput) { error = null; try { - const updateData: Partial = { - updatedAt: new Date().toISOString(), - }; + const updateData: Partial = {}; if (input.label !== undefined) updateData.label = input.label ?? null; if (input.durationSeconds !== undefined) updateData.durationSeconds = input.durationSeconds; if (input.sound !== undefined) updateData.sound = input.sound ?? null; @@ -86,7 +83,6 @@ export const countdownTimersStore = { status: 'running', startedAt: new Date().toISOString(), pausedAt: null, - updatedAt: new Date().toISOString(), }; // If resuming from pause, keep remaining seconds @@ -132,7 +128,6 @@ export const countdownTimersStore = { pausedAt: new Date().toISOString(), remainingSeconds: Math.round(remaining), startedAt: null, - updatedAt: new Date().toISOString(), }; await db.table('timeCountdownTimers').update(id, updateData); @@ -159,7 +154,6 @@ export const countdownTimersStore = { remainingSeconds: null, startedAt: null, pausedAt: null, - updatedAt: new Date().toISOString(), }; await db.table('timeCountdownTimers').update(id, updateData); @@ -183,7 +177,6 @@ export const countdownTimersStore = { try { await db.table('timeCountdownTimers').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); return { success: true }; } catch (e) { diff --git a/apps/mana/apps/web/src/lib/modules/times/stores/session-alarms.svelte.ts b/apps/mana/apps/web/src/lib/modules/times/stores/session-alarms.svelte.ts index 45dbfb976..56d286ed3 100644 --- a/apps/mana/apps/web/src/lib/modules/times/stores/session-alarms.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/times/stores/session-alarms.svelte.ts @@ -70,6 +70,10 @@ export const sessionAlarmsStore = { sound: input.sound || null, vibrate: input.vibrate ?? null, createdAt: now, + // Session-only store: not synced, no Dexie hook to derive + // updatedAt from __fieldMeta. Stamp it directly here so the + // public Alarm shape stays consistent with the synced + // counterpart (LocalAlarm via the type-converter). updatedAt: now, }; @@ -89,7 +93,6 @@ export const sessionAlarmsStore = { const updated: Alarm = { ...alarms[index], ...input, - updatedAt: new Date().toISOString(), }; alarms = alarms.map((a) => (a.id === id ? updated : a)); diff --git a/apps/mana/apps/web/src/lib/modules/times/stores/session-countdown-timers.svelte.ts b/apps/mana/apps/web/src/lib/modules/times/stores/session-countdown-timers.svelte.ts index e467010a7..422f3797e 100644 --- a/apps/mana/apps/web/src/lib/modules/times/stores/session-countdown-timers.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/times/stores/session-countdown-timers.svelte.ts @@ -71,6 +71,7 @@ export const sessionCountdownTimersStore = { pausedAt: null, sound: input.sound || null, createdAt: now, + // Session-only store (no Dexie sync) — stamp updatedAt directly. updatedAt: now, }; @@ -90,7 +91,6 @@ export const sessionCountdownTimersStore = { const updated: Timer = { ...timers[index], ...input, - updatedAt: new Date().toISOString(), }; timers = timers.map((t) => (t.id === id ? updated : t)); @@ -112,7 +112,6 @@ export const sessionCountdownTimersStore = { status: 'running', startedAt: now, pausedAt: null, - updatedAt: now, }; timers = timers.map((t) => (t.id === id ? updated : t)); @@ -133,7 +132,6 @@ export const sessionCountdownTimersStore = { ...timer, status: 'paused', pausedAt: now, - updatedAt: now, }; timers = timers.map((t) => (t.id === id ? updated : t)); @@ -156,7 +154,6 @@ export const sessionCountdownTimersStore = { remainingSeconds: timer.durationSeconds, startedAt: null, pausedAt: null, - updatedAt: now, }; timers = timers.map((t) => (t.id === id ? updated : t)); diff --git a/apps/mana/apps/web/src/lib/modules/times/stores/world-clocks.svelte.ts b/apps/mana/apps/web/src/lib/modules/times/stores/world-clocks.svelte.ts index dd532d163..b4ddfed73 100644 --- a/apps/mana/apps/web/src/lib/modules/times/stores/world-clocks.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/times/stores/world-clocks.svelte.ts @@ -29,7 +29,6 @@ export const worldClocksStore = { cityName: input.cityName, sortOrder: currentCount, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }; await db.table('timeWorldClocks').add(newLocal); @@ -49,7 +48,6 @@ export const worldClocksStore = { try { await db.table('timeWorldClocks').update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); return { success: true }; } catch (e) { @@ -69,7 +67,6 @@ export const worldClocksStore = { for (let i = 0; i < ids.length; i++) { await db.table('timeWorldClocks').update(ids[i], { sortOrder: i, - updatedAt: now, }); } return { success: true }; diff --git a/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts b/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts index c6f62d363..5853993ae 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/composables/useTaskForm.svelte.ts @@ -123,7 +123,6 @@ export function useTaskForm() { if (!r.deletedAt) { await reminderTable.update(r.id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } } diff --git a/apps/mana/apps/web/src/lib/modules/todo/queries.ts b/apps/mana/apps/web/src/lib/modules/todo/queries.ts index 416026226..e7dc87d89 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/queries.ts @@ -3,6 +3,7 @@ */ import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { db } from '$lib/data/database'; import { scopedForModule, applyVisibility } from '$lib/data/scope'; import { decryptRecords } from '$lib/data/crypto'; @@ -38,7 +39,7 @@ export function toTask(local: LocalTask): Task { metadata: local.metadata ?? null, visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/todo/reminder-source.ts b/apps/mana/apps/web/src/lib/modules/todo/reminder-source.ts index 726d12eef..4c6121848 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/reminder-source.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/reminder-source.ts @@ -47,7 +47,6 @@ export const todoReminderSource: ReminderSource = { async markSent(reminderId: string): Promise { await db.table('reminders').update(reminderId, { status: 'sent', - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/todo/stores/board-views.svelte.ts b/apps/mana/apps/web/src/lib/modules/todo/stores/board-views.svelte.ts index 80d61aca1..ef183f640 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/stores/board-views.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/stores/board-views.svelte.ts @@ -25,14 +25,12 @@ export const boardViewsStore = { async updateView(id: string, data: Partial) { await boardViewTable.update(id, { ...data, - updatedAt: new Date().toISOString(), }); }, async deleteView(id: string) { await boardViewTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -40,7 +38,6 @@ export const boardViewsStore = { for (let i = 0; i < viewIds.length; i++) { await boardViewTable.update(viewIds[i], { order: i, - updatedAt: new Date().toISOString(), }); } }, @@ -54,7 +51,6 @@ export const boardViewsStore = { ); await boardViewTable.update(viewId, { columns: updatedColumns, - updatedAt: new Date().toISOString(), }); }, }; diff --git a/apps/mana/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts b/apps/mana/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts index d7a8751c8..863eca245 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/stores/reminders.svelte.ts @@ -28,7 +28,6 @@ export const remindersStore = { async deleteReminder(id: string) { await reminderTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); }, @@ -38,7 +37,6 @@ export const remindersStore = { if (!r.deletedAt) { await reminderTable.update(r.id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } } diff --git a/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts b/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts index a8fd362d3..db5aeb320 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts @@ -370,7 +370,6 @@ export const tasksStore = { const diff: Record = { ...data, - updatedAt: new Date().toISOString(), }; await encryptRecord('tasks', diff); await taskTable.update(id, diff); @@ -386,7 +385,6 @@ export const tasksStore = { await taskTable.update(id, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); emitDomainEvent('TaskDeleted', 'todo', 'tasks', id, { taskId: id, @@ -403,7 +401,6 @@ export const tasksStore = { await taskTable.update(id, { isCompleted: true, completedAt: now, - updatedAt: now, }); emitDomainEvent('TaskCompleted', 'todo', 'tasks', id, { taskId: id, @@ -420,7 +417,6 @@ export const tasksStore = { await taskTable.update(id, { isCompleted: false, completedAt: null, - updatedAt: new Date().toISOString(), }); emitDomainEvent('TaskUncompleted', 'todo', 'tasks', id, { taskId: id, @@ -442,7 +438,6 @@ export const tasksStore = { async updateSubtasks(id: string, subtasks: Subtask[]) { const diff: Record = { subtasks, - updatedAt: new Date().toISOString(), }; await encryptRecord('tasks', diff); await taskTable.update(id, diff); @@ -462,7 +457,6 @@ export const tasksStore = { const existingMeta = (existing?.metadata as Record) ?? {}; const diff: Record = { metadata: { ...existingMeta, labelIds }, - updatedAt: new Date().toISOString(), }; await encryptRecord('tasks', diff); await taskTable.update(id, diff); @@ -472,7 +466,6 @@ export const tasksStore = { for (let i = 0; i < taskIds.length; i++) { await taskTable.update(taskIds[i], { order: i, - updatedAt: new Date().toISOString(), }); } }, @@ -494,7 +487,6 @@ export const tasksStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; if (next === 'unlisted' && !existing.unlistedToken) { patch.unlistedToken = generateUnlistedToken(); diff --git a/apps/mana/apps/web/src/lib/modules/todo/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/todo/views/DetailView.svelte index 61d53e822..e02d1b1c4 100644 --- a/apps/mana/apps/web/src/lib/modules/todo/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/todo/views/DetailView.svelte @@ -140,7 +140,7 @@ await tasksStore.deleteTask(id); goBack(); toastStore.undo('Aufgabe gelöscht', () => { - db.table('tasks').update(id, { deletedAt: undefined, updatedAt: new Date().toISOString() }); + db.table('tasks').update(id, { deletedAt: undefined }); }); } diff --git a/apps/mana/apps/web/src/lib/modules/uload/queries.ts b/apps/mana/apps/web/src/lib/modules/uload/queries.ts index a3501abfb..56a8da301 100644 --- a/apps/mana/apps/web/src/lib/modules/uload/queries.ts +++ b/apps/mana/apps/web/src/lib/modules/uload/queries.ts @@ -6,6 +6,7 @@ */ import { liveQuery } from 'dexie'; +import { deriveUpdatedAt } from '$lib/data/sync'; import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; import { db } from '$lib/data/database'; import { scopedForModule } from '$lib/data/scope'; @@ -93,7 +94,7 @@ export function toLink(local: LocalLink): Link { folderId: local.folderId ?? undefined, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -107,7 +108,7 @@ export function toTag(local: LocalTag): Tag { visibility: local.visibility ?? 'space', usageCount: local.usageCount, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } @@ -118,7 +119,7 @@ export function toFolder(local: LocalFolder): Folder { color: local.color ?? undefined, order: local.order, createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: local.updatedAt ?? new Date().toISOString(), + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/uload/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/uload/views/DetailView.svelte index 0464263f5..962036328 100644 --- a/apps/mana/apps/web/src/lib/modules/uload/views/DetailView.svelte +++ b/apps/mana/apps/web/src/lib/modules/uload/views/DetailView.svelte @@ -44,7 +44,6 @@ description: editDescription.trim() || undefined, isActive: editIsActive, expiresAt: editExpiresAt ? new Date(editExpiresAt).toISOString() : null, - updatedAt: new Date().toISOString(), }; await encryptRecord('links', diff); await db.table('links').update(linkId, diff); @@ -53,14 +52,12 @@ async function handleActiveToggle() { await db.table('links').update(linkId, { isActive: editIsActive, - updatedAt: new Date().toISOString(), }); } async function deleteLink() { await db.table('links').update(linkId, { deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }); } diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/api/try-on.ts b/apps/mana/apps/web/src/lib/modules/wardrobe/api/try-on.ts index b79338a39..8eafa1e07 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/api/try-on.ts +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/api/try-on.ts @@ -219,7 +219,6 @@ export async function runOutfitTryOn(params: RunOutfitTryOnParams): Promise { const wrapped = { ...patch } as Record; await encryptRecord('wardrobeGarments', wrapped); - await wardrobeGarmentsTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await wardrobeGarmentsTable.update(id, wrapped as never); }, /** @@ -100,7 +97,6 @@ export const wardrobeGarmentsStore = { await wardrobeGarmentsTable.update(id, { wearCount: (existing.wearCount ?? 0) + 1, lastWornAt: today, - updatedAt: new Date().toISOString(), }); emitDomainEvent('WardrobeGarmentWorn', 'wardrobe', 'wardrobeGarments', id, { garmentId: id, @@ -111,7 +107,6 @@ export const wardrobeGarmentsStore = { async archiveGarment(id: string, archived: boolean): Promise { await wardrobeGarmentsTable.update(id, { isArchived: archived, - updatedAt: new Date().toISOString(), }); }, @@ -119,7 +114,6 @@ export const wardrobeGarmentsStore = { const nowIso = new Date().toISOString(); await wardrobeGarmentsTable.update(id, { deletedAt: nowIso, - updatedAt: nowIso, }); emitDomainEvent('WardrobeGarmentDeleted', 'wardrobe', 'wardrobeGarments', id, { garmentId: id, diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts b/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts index 5ccb9efe1..b61e1c23a 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/stores/outfits.svelte.ts @@ -73,10 +73,7 @@ export const wardrobeOutfitsStore = { ): Promise { const wrapped = { ...patch } as Record; await encryptRecord('wardrobeOutfits', wrapped); - await wardrobeOutfitsTable.update(id, { - ...wrapped, - updatedAt: new Date().toISOString(), - }); + await wardrobeOutfitsTable.update(id, wrapped as never); }, async toggleFavorite(id: string): Promise { @@ -84,7 +81,6 @@ export const wardrobeOutfitsStore = { if (!existing) return; await wardrobeOutfitsTable.update(id, { isFavorite: !existing.isFavorite, - updatedAt: new Date().toISOString(), }); }, @@ -92,7 +88,6 @@ export const wardrobeOutfitsStore = { const today = new Date().toISOString().slice(0, 10); await wardrobeOutfitsTable.update(id, { lastWornAt: today, - updatedAt: new Date().toISOString(), }); }, @@ -105,7 +100,6 @@ export const wardrobeOutfitsStore = { async setLastTryOn(id: string, tryOn: OutfitTryOn): Promise { await wardrobeOutfitsTable.update(id, { lastTryOn: tryOn, - updatedAt: new Date().toISOString(), }); emitDomainEvent('WardrobeOutfitTryOn', 'wardrobe', 'wardrobeOutfits', id, { outfitId: id, @@ -116,7 +110,6 @@ export const wardrobeOutfitsStore = { async archiveOutfit(id: string, archived: boolean): Promise { await wardrobeOutfitsTable.update(id, { isArchived: archived, - updatedAt: new Date().toISOString(), }); }, @@ -124,7 +117,6 @@ export const wardrobeOutfitsStore = { const nowIso = new Date().toISOString(); await wardrobeOutfitsTable.update(id, { deletedAt: nowIso, - updatedAt: nowIso, }); emitDomainEvent('WardrobeOutfitDeleted', 'wardrobe', 'wardrobeOutfits', id, { outfitId: id, @@ -147,14 +139,13 @@ export const wardrobeOutfitsStore = { visibility: next, visibilityChangedAt: now, visibilityChangedBy: getEffectiveUserId(), - updatedAt: now, }; if (next === 'unlisted' && !existing.unlistedToken) { patch.unlistedToken = generateUnlistedToken(); } else if (next !== 'unlisted' && existing.unlistedToken) { patch.unlistedToken = undefined; } - await wardrobeOutfitsTable.update(id, patch); + await wardrobeOutfitsTable.update(id, patch as never); emitDomainEvent('VisibilityChanged', 'wardrobe', 'wardrobeOutfits', id, { recordId: id, diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts b/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts index feb0d635c..7f983ff9d 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/types.ts @@ -16,6 +16,7 @@ */ import type { BaseRecord } from '@mana/local-store'; +import { deriveUpdatedAt } from '$lib/data/sync'; import type { VisibilityLevel } from '@mana/shared-privacy'; // ─── Garment ────────────────────────────────────────────────────── @@ -115,7 +116,7 @@ export function toGarment(local: LocalWardrobeGarment): Garment { wearCount: local.wearCount ?? undefined, lastWornAt: local.lastWornAt ?? undefined, createdAt: local.createdAt ?? '', - updatedAt: local.updatedAt ?? '', + updatedAt: deriveUpdatedAt(local), }; } @@ -212,6 +213,6 @@ export function toOutfit(local: LocalWardrobeOutfit): Outfit { lastWornAt: local.lastWornAt ?? undefined, visibility: local.visibility ?? 'space', createdAt: local.createdAt ?? '', - updatedAt: local.updatedAt ?? '', + updatedAt: deriveUpdatedAt(local), }; } diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte index 931ff93da..5a8f77076 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte @@ -6,6 +6,7 @@ -->