mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 04:41:09 +02:00
chore(mana/web): pre-launch module cleanup — schema collapse, dead code, lazy search
Six independent pre-launch tidy-ups bundled because they all touch
the same module-layer surface and the larger commit reads more
clearly than six adjacent two-line PRs.
1. database.ts schema v1–v10 collapsed into a single canonical
db.version(1). The system has no live users yet, so dropping the
versioned migration history is the cheapest moment to do it.
The post-collapse Dexie table set is provably identical to the
pre-collapse state (asserted by module-registry.test.ts).
Removed: EMOJI_TO_ICON map + v2 upgrade, v3 timeBlocks data
migration (~250 LOC of one-shot code), versions 4-10.
Also dropped the @deprecated `setApplyingServerChanges()` shim
(replaced by `beginApplyingTables()` weeks ago, no callers).
2. LocalLabel @deprecated alias renamed to TaskTag in the todo
module and all 11 consumers (board-views, ListView, DetailView,
QuickAddTask, +page.svelte). The alias was annotated @deprecated
but had eleven live consumers — exactly the worst kind of dead
code, the one that grows accidental new consumers via autocomplete
the longer it stays. Renamed to TaskTag rather than `Tag` to
avoid colliding with the `Tag` icon from `@mana/shared-icons`.
3. labelsStore backward-compat alias deleted from todo/stores —
pure dead code with zero consumers.
4. EMOJI_TO_ICON_MAP fallback in habits/queries removed. The
constant only existed as the in-memory equivalent of the v2
schema migration that was just deleted; once no record can have
the old `emoji` field, the fallback can never fire.
5. useAllEvents() in calendar/queries removed. JSDoc itself called
it out as "for backward compatibility with calendar-specific
views" — zero external consumers, only the barrel referenced it.
6. $lib/stores/tags.svelte.ts re-export shim deleted. It was a
20-line pure re-export from @mana/shared-stores with the explicit
header "for backward compatibility with existing imports".
Thirteen importers (todo/calendar/contacts/places/zitare ListView
+ DetailView, plus +layout.svelte and the calendar/contacts/tags
route +page.svelte files) rewritten to import directly.
7. SearchRegistry got `registerLazy(appId, loader)` and the eleven
per-app providers now register via dynamic `import()`. Spotlight
search is opened on demand, so the eleven provider chunks stay
out of the initial JS bundle until the user actually searches.
Sister benefit: a search filtered to a single appId only loads
that one provider.
The structural backbone for all of this — the per-module
`module.config.ts` files plus `module-registry.{ts,test.ts}` — was
committed earlier in 5d4123d2b.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a473897ec
commit
171fbd18be
29 changed files with 162 additions and 451 deletions
|
|
@ -44,10 +44,17 @@ export {
|
|||
|
||||
export const db = new Dexie('mana');
|
||||
|
||||
// Single canonical schema. The pre-launch cleanup collapsed historical
|
||||
// versions 1–10 into this one block — see docs/PRE_LAUNCH_CLEANUP.md for
|
||||
// rationale. After the system goes live, any further schema change MUST
|
||||
// be added as a new `db.version(N)` block; never edit this one.
|
||||
db.version(1).stores({
|
||||
// ─── Sync Infrastructure ───
|
||||
// ─── Sync Infrastructure (local-only, NOT in SYNC_APP_MAP) ───
|
||||
_pendingChanges: '++id, appId, collection, recordId, createdAt',
|
||||
_syncMeta: '[appId+collection]',
|
||||
_eventsTombstones: 'id, token, attempts, createdAt',
|
||||
_activity:
|
||||
'++id, createdAt, appId, collection, recordId, op, [appId+createdAt], [collection+recordId], userId',
|
||||
|
||||
// ─── Core / Mana (appId: 'mana') ───
|
||||
userSettings: 'id, key',
|
||||
|
|
@ -55,16 +62,19 @@ db.version(1).stores({
|
|||
automations: 'id, sourceApp, targetApp, enabled, [sourceApp+sourceCollection]',
|
||||
|
||||
// ─── Todo (appId: 'todo') ───
|
||||
// `scheduledBlockId` is the link to the unified timeBlocks table.
|
||||
tasks:
|
||||
'id, dueDate, isCompleted, priority, order, projectId, [isCompleted+order], [projectId+order]',
|
||||
'id, dueDate, isCompleted, priority, order, projectId, scheduledBlockId, [isCompleted+order], [projectId+order]',
|
||||
todoProjects: 'id, order, isArchived, isDefault',
|
||||
taskLabels: 'id, taskId, labelId', // junction to globalTags (labelId = tagId)
|
||||
reminders: 'id, taskId',
|
||||
boardViews: 'id, order, groupBy',
|
||||
|
||||
// ─── Calendar (appId: 'calendar') ───
|
||||
// Scheduling fields (startDate / endDate / allDay) live on the linked
|
||||
// timeBlocks row, not on `events` itself — see time-blocks/service.ts.
|
||||
calendars: 'id, isDefault, isVisible',
|
||||
events: 'id, calendarId, startDate, endDate, allDay, [calendarId+startDate]',
|
||||
events: 'id, calendarId, timeBlockId',
|
||||
eventTags: 'id, eventId, tagId, [eventId+tagId]',
|
||||
|
||||
// ─── Contacts (appId: 'contacts') ───
|
||||
|
|
@ -72,13 +82,13 @@ db.version(1).stores({
|
|||
contactTags: 'id, contactId, tagId, [contactId+tagId]',
|
||||
|
||||
// ─── Chat (appId: 'chat') ───
|
||||
conversations: 'id, isArchived, isPinned, spaceId, templateId',
|
||||
conversations: 'id, isArchived, isPinned, spaceId, templateId, updatedAt',
|
||||
messages: 'id, conversationId, sender, [conversationId+sender]',
|
||||
chatTemplates: 'id, isDefault',
|
||||
conversationTags: 'id, conversationId, tagId, [conversationId+tagId]',
|
||||
|
||||
// ─── Picture (appId: 'picture') ───
|
||||
images: 'id, isFavorite, isPublic, isArchived, prompt',
|
||||
images: 'id, isFavorite, isPublic, isArchived, prompt, updatedAt',
|
||||
boards: 'id, isPublic',
|
||||
boardItems: 'id, boardId, itemType, zIndex, [boardId+zIndex]',
|
||||
imageTags: 'id, imageId, tagId, [imageId+tagId]', // junction to globalTags
|
||||
|
|
@ -94,8 +104,8 @@ db.version(1).stores({
|
|||
zitareListTags: 'id, listId, tagId, [listId+tagId]',
|
||||
|
||||
// ─── Music (appId: 'music') ───
|
||||
songs: 'id, artist, album, genre, favorite, title',
|
||||
mukkePlaylists: 'id, name',
|
||||
songs: 'id, artist, album, genre, favorite, title, updatedAt',
|
||||
mukkePlaylists: 'id, name, updatedAt',
|
||||
playlistSongs: 'id, playlistId, songId, sortOrder, [playlistId+sortOrder]',
|
||||
mukkeProjects: 'id, title, songId',
|
||||
markers: 'id, beatId, type, sortOrder',
|
||||
|
|
@ -107,7 +117,7 @@ db.version(1).stores({
|
|||
fileTags: 'id, fileId, tagId, [fileId+tagId]', // junction to globalTags
|
||||
|
||||
// ─── Presi (appId: 'presi') ───
|
||||
presiDecks: 'id, isPublic',
|
||||
presiDecks: 'id, isPublic, updatedAt',
|
||||
slides: 'id, deckId, order, [deckId+order]',
|
||||
presiDeckTags: 'id, deckId, tagId, [deckId+tagId]',
|
||||
|
||||
|
|
@ -137,10 +147,11 @@ db.version(1).stores({
|
|||
ccLocationTags: 'id, locationId, tagId, [locationId+tagId]',
|
||||
|
||||
// ─── Times (appId: 'times') ───
|
||||
// Like calendar events, time entries store their scheduling on the
|
||||
// linked timeBlocks row, not on the row itself.
|
||||
timeClients: 'id, order, isArchived, shortCode',
|
||||
timeProjects: 'id, clientId, isArchived, isBillable, guildId, visibility, order',
|
||||
timeEntries:
|
||||
'id, projectId, clientId, date, isRunning, [date+projectId], [date+clientId], guildId, visibility',
|
||||
timeEntries: 'id, projectId, clientId, timeBlockId, guildId, visibility',
|
||||
timeTemplates: 'id, usageCount, lastUsedAt, projectId',
|
||||
timeSettings: 'id',
|
||||
timeAlarms: 'id, enabled, time',
|
||||
|
|
@ -150,7 +161,7 @@ db.version(1).stores({
|
|||
|
||||
// ─── Context (appId: 'context') ───
|
||||
contextSpaces: 'id, pinned, prefix',
|
||||
documents: 'id, spaceId, type, pinned, title, [spaceId+type]',
|
||||
documents: 'id, spaceId, type, pinned, title, [spaceId+type], updatedAt',
|
||||
documentTags: 'id, documentId, tagId, [documentId+tagId]',
|
||||
|
||||
// ─── Questions (appId: 'questions') ───
|
||||
|
|
@ -208,7 +219,27 @@ db.version(1).stores({
|
|||
|
||||
// ─── Habits (appId: 'habits') ───
|
||||
habits: 'id, order, isArchived, color',
|
||||
habitLogs: 'id, habitId, timestamp, [habitId+timestamp]',
|
||||
habitLogs: 'id, habitId, timeBlockId, [habitId+timeBlockId]',
|
||||
|
||||
// ─── Dreams (appId: 'dreams') ───
|
||||
dreams: 'id, dreamDate, mood, isLucid, isPinned, isArchived, updatedAt',
|
||||
dreamSymbols: 'id, name, count, updatedAt',
|
||||
dreamTags: 'id, dreamId, tagId, [dreamId+tagId]',
|
||||
|
||||
// ─── Cycles (appId: 'cycles') ───
|
||||
cycles: 'id, startDate, endDate, isPredicted, isArchived, updatedAt',
|
||||
cycleDayLogs: 'id, logDate, cycleId, flow, [cycleId+logDate]',
|
||||
cycleSymptoms: 'id, name, category, count, updatedAt',
|
||||
|
||||
// ─── Social Events (appId: 'events') ───
|
||||
// `socialEvents` is named distinctly to avoid colliding with calendar.events.
|
||||
socialEvents: 'id, status, timeBlockId, hostContactId, isPublished, [status+createdAt]',
|
||||
eventGuests: 'id, eventId, contactId, rsvpStatus, [eventId+rsvpStatus], [eventId+contactId]',
|
||||
eventInvitations: 'id, eventId, guestId, channel, [eventId+guestId]',
|
||||
// Bring-list ("wer bringt was?") — assignedGuestId points at a local
|
||||
// guest the host picked manually; claimedByName is set by a public
|
||||
// RSVP visitor who reserved the item from the share-link page.
|
||||
eventItems: 'id, eventId, assignedGuestId, done, order, [eventId+order], [eventId+done]',
|
||||
|
||||
// ─── Notes (appId: 'notes') ───
|
||||
notes: 'id, isPinned, isArchived, color, title, updatedAt',
|
||||
|
|
@ -224,6 +255,14 @@ db.version(1).stores({
|
|||
locationLogs: 'id, placeId, timestamp, [placeId+timestamp]',
|
||||
placeTags: 'id, placeId, tagId, [placeId+tagId]',
|
||||
|
||||
// ─── TimeBlocks (appId: 'timeblocks') — unified time model ───
|
||||
// Cross-cutting scheduling table that calendar events, time entries,
|
||||
// habit logs and scheduled tasks all project into. See PROD_READINESS
|
||||
// notes in time-blocks/service.ts for the design rationale.
|
||||
timeBlocks:
|
||||
'id, startDate, kind, type, sourceModule, sourceId, parentBlockId, [sourceModule+sourceId], [type+startDate], [kind+startDate], [parentBlockId+recurrenceDate]',
|
||||
timeBlockTags: 'id, blockId, tagId, [blockId+tagId]',
|
||||
|
||||
// ─── Shared: Global Tags (appId: 'tags') ───
|
||||
globalTags: 'id, name, groupId',
|
||||
tagGroups: 'id',
|
||||
|
|
@ -232,296 +271,6 @@ db.version(1).stores({
|
|||
manaLinks: 'id, sourceAppId, sourceRecordId, targetAppId, targetRecordId',
|
||||
});
|
||||
|
||||
// ─── Schema Migrations ────────────────────────────────────────
|
||||
// Version 2: Habits emoji → icon field migration
|
||||
|
||||
const EMOJI_TO_ICON: Record<string, string> = {
|
||||
'\u2615': 'coffee',
|
||||
'\ud83d\udeb6': 'person-simple-walk',
|
||||
'\ud83c\udfc3': 'person-simple-run',
|
||||
'\ud83e\uddd8': 'person-simple-tai-chi',
|
||||
'\ud83d\udca7': 'drop',
|
||||
'\ud83c\udf4e': 'apple-logo',
|
||||
'\ud83d\udcda': 'book-open',
|
||||
'\ud83d\udcaa': 'barbell',
|
||||
'\ud83d\udecc': 'bed',
|
||||
'\ud83c\udfb5': 'music-note',
|
||||
'\ud83d\udc8a': 'pill',
|
||||
'\ud83c\udf7a': 'beer-stein',
|
||||
'\ud83c\udf55': 'pizza',
|
||||
'\ud83d\udeb4': 'bicycle',
|
||||
'\ud83d\udcdd': 'pencil-simple',
|
||||
'\ud83e\uddfc': 'tooth',
|
||||
'\u2b50': 'star',
|
||||
'\ud83d\ude2e\u200d\ud83d\udca8': 'wind',
|
||||
};
|
||||
|
||||
db.version(2)
|
||||
.stores({})
|
||||
.upgrade((tx) => {
|
||||
return tx
|
||||
.table('habits')
|
||||
.toCollection()
|
||||
.modify((habit: Record<string, unknown>) => {
|
||||
if (habit.emoji !== undefined && habit.icon === undefined) {
|
||||
habit.icon = EMOJI_TO_ICON[habit.emoji as string] ?? 'star';
|
||||
delete habit.emoji;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Version 3: Unified Time Model (timeBlocks) ─────────────
|
||||
// Adds timeBlocks table, updates indexes on events/timeEntries/tasks/habitLogs,
|
||||
// and migrates existing time data into timeBlocks.
|
||||
|
||||
db.version(3)
|
||||
.stores({
|
||||
// New tables
|
||||
timeBlocks:
|
||||
'id, startDate, kind, type, sourceModule, sourceId, [sourceModule+sourceId], [type+startDate], [kind+startDate]',
|
||||
timeBlockTags: 'id, blockId, tagId, [blockId+tagId]',
|
||||
|
||||
// Updated indexes (timeBlockId / scheduledBlockId added)
|
||||
events: 'id, calendarId, timeBlockId',
|
||||
timeEntries: 'id, projectId, clientId, timeBlockId, guildId, visibility',
|
||||
tasks:
|
||||
'id, dueDate, isCompleted, priority, order, projectId, scheduledBlockId, [isCompleted+order], [projectId+order]',
|
||||
habitLogs: 'id, habitId, timeBlockId, [habitId+timeBlockId]',
|
||||
})
|
||||
.upgrade(async (tx) => {
|
||||
const timeBlocksTable = tx.table('timeBlocks');
|
||||
|
||||
// 1. Migrate calendar events → timeBlocks
|
||||
const events = await tx.table('events').toArray();
|
||||
for (const event of events) {
|
||||
if (!event.startDate) continue;
|
||||
const blockId = crypto.randomUUID();
|
||||
await timeBlocksTable.add({
|
||||
id: blockId,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate ?? null,
|
||||
allDay: event.allDay ?? false,
|
||||
isLive: false,
|
||||
timezone: null,
|
||||
recurrenceRule: event.recurrenceRule ?? null,
|
||||
kind: 'scheduled',
|
||||
type: 'event',
|
||||
sourceModule: 'calendar',
|
||||
sourceId: event.id,
|
||||
linkedBlockId: null,
|
||||
title: event.title ?? '',
|
||||
description: event.description ?? null,
|
||||
color: event.color ?? null,
|
||||
icon: null,
|
||||
projectId: null,
|
||||
createdAt: event.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: event.updatedAt ?? new Date().toISOString(),
|
||||
deletedAt: event.deletedAt ?? null,
|
||||
});
|
||||
await tx.table('events').update(event.id, { timeBlockId: blockId });
|
||||
}
|
||||
|
||||
// 2. Migrate time entries → timeBlocks
|
||||
const entries = await tx.table('timeEntries').toArray();
|
||||
for (const entry of entries) {
|
||||
if (!entry.date && !entry.startTime) continue; // skip entries with no date at all
|
||||
const blockId = crypto.randomUUID();
|
||||
const startDate = entry.startTime ?? `${entry.date}T00:00:00.000Z`;
|
||||
await timeBlocksTable.add({
|
||||
id: blockId,
|
||||
startDate,
|
||||
endDate: entry.endTime ?? null,
|
||||
allDay: false,
|
||||
isLive: entry.isRunning ?? false,
|
||||
timezone: null,
|
||||
recurrenceRule: null,
|
||||
kind: 'logged',
|
||||
type: 'timeEntry',
|
||||
sourceModule: 'times',
|
||||
sourceId: entry.id,
|
||||
linkedBlockId: null,
|
||||
title: entry.description || 'Time Entry',
|
||||
description: null,
|
||||
color: null,
|
||||
icon: null,
|
||||
projectId: entry.projectId ?? null,
|
||||
createdAt: entry.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: entry.updatedAt ?? new Date().toISOString(),
|
||||
deletedAt: entry.deletedAt ?? null,
|
||||
});
|
||||
await tx.table('timeEntries').update(entry.id, { timeBlockId: blockId });
|
||||
}
|
||||
|
||||
// 3. Migrate habit logs → timeBlocks
|
||||
const logs = await tx.table('habitLogs').toArray();
|
||||
const habitsById = new Map<string, Record<string, unknown>>();
|
||||
const allHabits = await tx.table('habits').toArray();
|
||||
for (const h of allHabits) habitsById.set(h.id as string, h);
|
||||
|
||||
for (const log of logs) {
|
||||
if (!log.timestamp) continue;
|
||||
const blockId = crypto.randomUUID();
|
||||
const habit = habitsById.get(log.habitId as string);
|
||||
await timeBlocksTable.add({
|
||||
id: blockId,
|
||||
startDate: log.timestamp,
|
||||
endDate: null,
|
||||
allDay: false,
|
||||
isLive: false,
|
||||
timezone: null,
|
||||
recurrenceRule: null,
|
||||
kind: 'logged',
|
||||
type: 'habit',
|
||||
sourceModule: 'habits',
|
||||
sourceId: log.id,
|
||||
linkedBlockId: null,
|
||||
title: (habit?.title as string) ?? 'Habit',
|
||||
description: null,
|
||||
color: (habit?.color as string) ?? null,
|
||||
icon: (habit?.icon as string) ?? null,
|
||||
projectId: null,
|
||||
createdAt: log.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: log.updatedAt ?? log.createdAt ?? new Date().toISOString(),
|
||||
deletedAt: log.deletedAt ?? null,
|
||||
});
|
||||
await tx.table('habitLogs').update(log.id, { timeBlockId: blockId });
|
||||
}
|
||||
|
||||
// 4. Migrate scheduled tasks → timeBlocks
|
||||
const tasks = await tx.table('tasks').toArray();
|
||||
for (const task of tasks) {
|
||||
if (!task.scheduledDate) continue;
|
||||
const blockId = crypto.randomUUID();
|
||||
const startISO = task.scheduledStartTime
|
||||
? `${task.scheduledDate}T${task.scheduledStartTime}:00`
|
||||
: `${task.scheduledDate}T09:00:00`;
|
||||
const durationMs = task.estimatedDuration ? task.estimatedDuration * 1000 : 3600000; // default 1h
|
||||
const endISO = new Date(new Date(startISO).getTime() + durationMs).toISOString();
|
||||
|
||||
await timeBlocksTable.add({
|
||||
id: blockId,
|
||||
startDate: startISO,
|
||||
endDate: endISO,
|
||||
allDay: !task.scheduledStartTime,
|
||||
isLive: false,
|
||||
timezone: null,
|
||||
recurrenceRule: null,
|
||||
kind: 'scheduled',
|
||||
type: 'task',
|
||||
sourceModule: 'todo',
|
||||
sourceId: task.id,
|
||||
linkedBlockId: null,
|
||||
title: task.title ?? '',
|
||||
description: null,
|
||||
color: null,
|
||||
icon: null,
|
||||
projectId: task.projectId ?? null,
|
||||
createdAt: task.createdAt ?? new Date().toISOString(),
|
||||
updatedAt: task.updatedAt ?? new Date().toISOString(),
|
||||
deletedAt: task.deletedAt ?? null,
|
||||
});
|
||||
await tx.table('tasks').update(task.id, { scheduledBlockId: blockId });
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Version 4: Recurrence instance fields on timeBlocks ──────
|
||||
// Adds parentBlockId, recurrenceDate, isRecurrenceException indexes.
|
||||
|
||||
db.version(4).stores({
|
||||
timeBlocks:
|
||||
'id, startDate, kind, type, sourceModule, sourceId, parentBlockId, [sourceModule+sourceId], [type+startDate], [kind+startDate], [parentBlockId+recurrenceDate]',
|
||||
});
|
||||
|
||||
// ─── Version 5: Dreams (Traumtagebuch) ────────────────────────
|
||||
// Adds dreams, dreamSymbols, dreamTags tables.
|
||||
|
||||
db.version(5).stores({
|
||||
dreams: 'id, dreamDate, mood, isLucid, isPinned, isArchived, updatedAt',
|
||||
dreamSymbols: 'id, name, count, updatedAt',
|
||||
dreamTags: 'id, dreamId, tagId, [dreamId+tagId]',
|
||||
});
|
||||
|
||||
// ─── Version 6: Events (Social gatherings) ────────────────────
|
||||
// Distinct from calendar's `events` table — these are gatherings with guests/RSVPs.
|
||||
// Main table is `socialEvents` to avoid collision with calendar.events.
|
||||
|
||||
db.version(6).stores({
|
||||
socialEvents: 'id, status, timeBlockId, hostContactId, isPublished, [status+createdAt]',
|
||||
eventGuests: 'id, eventId, contactId, rsvpStatus, [eventId+rsvpStatus], [eventId+contactId]',
|
||||
eventInvitations: 'id, eventId, guestId, channel, [eventId+guestId]',
|
||||
});
|
||||
|
||||
// ─── Version 7: Cycles (Menstruationszyklus-Tracking) ────────
|
||||
|
||||
db.version(7).stores({
|
||||
cycles: 'id, startDate, endDate, isPredicted, isArchived, updatedAt',
|
||||
cycleDayLogs: 'id, logDate, cycleId, flow, [cycleId+logDate]',
|
||||
cycleSymptoms: 'id, name, category, count, updatedAt',
|
||||
});
|
||||
|
||||
// ─── Version 8: Events tombstones (orphaned snapshot cleanup) ─
|
||||
// Local-only retry queue. When the events store fails to DELETE a
|
||||
// server snapshot during unpublish/delete, the (eventId, token) is
|
||||
// pushed here so a later drain attempt can clean it up. NOT synced.
|
||||
|
||||
db.version(8).stores({
|
||||
_eventsTombstones: 'id, token, attempts, createdAt',
|
||||
});
|
||||
|
||||
// ─── Version 9: Add updatedAt indexes for "recent X" dashboard widgets ─
|
||||
//
|
||||
// Several cross-app queries (`useRecentConversations`, `useRecentImages`,
|
||||
// `useRecentDecks`, `useRecentDocuments`) used to load entire tables and
|
||||
// JS-sort by `updatedAt`. With these indexes Dexie can walk the BTree in
|
||||
// reverse and stop after N matches.
|
||||
//
|
||||
// `++` is NOT used — we are only adding secondary indexes to existing
|
||||
// stores. The full `stores()` line is repeated because Dexie's upgrade
|
||||
// API requires the complete schema for the version, even when most
|
||||
// fields are unchanged.
|
||||
//
|
||||
// No data migration needed: indexes are built lazily by Dexie at upgrade
|
||||
// time without touching record contents.
|
||||
|
||||
db.version(9).stores({
|
||||
conversations: 'id, isArchived, isPinned, spaceId, templateId, updatedAt',
|
||||
images: 'id, isFavorite, isPublic, isArchived, prompt, updatedAt',
|
||||
presiDecks: 'id, isPublic, updatedAt',
|
||||
documents: 'id, spaceId, type, pinned, title, [spaceId+type], updatedAt',
|
||||
songs: 'id, artist, album, genre, favorite, title, updatedAt',
|
||||
mukkePlaylists: 'id, name, updatedAt',
|
||||
});
|
||||
|
||||
// ─── Version 10: Local activity log ───────────────────────────
|
||||
//
|
||||
// Capped, append-only feed of every local write across sync-tracked
|
||||
// tables. Powers a future "what changed recently?" UI without leaking
|
||||
// PII to the server (this table is intentionally NOT in SYNC_APP_MAP).
|
||||
//
|
||||
// Indexes:
|
||||
// - createdAt: timeline view
|
||||
// - [appId+createdAt]: per-app filter
|
||||
// - [collection+recordId]: history of a single record
|
||||
// - userId: multi-account isolation when that lands
|
||||
//
|
||||
// Schema is deliberately small (no field diffs, no payload) to keep
|
||||
// the table cheap to write and bound the disk footprint.
|
||||
|
||||
db.version(10).stores({
|
||||
_activity:
|
||||
'++id, createdAt, appId, collection, recordId, op, [appId+createdAt], [collection+recordId], userId',
|
||||
});
|
||||
|
||||
// ─── Version 11: Events bring-list (eventItems) ───────────────
|
||||
// Adds the "wer bringt was?" table attached to social events.
|
||||
// `assignedGuestId` points at a local guest the host picked manually;
|
||||
// `claimedByName` is set by a public RSVP visitor who reserved the
|
||||
// item from the share-link page.
|
||||
|
||||
db.version(11).stores({
|
||||
eventItems: 'id, eventId, assignedGuestId, done, order, [eventId+order], [eventId+done]',
|
||||
});
|
||||
|
||||
// ─── 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
|
||||
|
|
@ -566,21 +315,6 @@ export function isApplyingTable(tableName: string): boolean {
|
|||
return _applyingTables.has(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Legacy single-flag API kept temporarily for any external
|
||||
* caller. Prefer `beginApplyingTables` so per-table races stay impossible.
|
||||
* When `v === true` it marks every sync-tracked table; `false` clears them.
|
||||
*/
|
||||
export function setApplyingServerChanges(v: boolean): void {
|
||||
if (v) {
|
||||
for (const tables of Object.values(SYNC_APP_MAP)) {
|
||||
for (const t of tables) _applyingTables.add(t);
|
||||
}
|
||||
} else {
|
||||
_applyingTables.clear();
|
||||
}
|
||||
}
|
||||
|
||||
const pendingChangesTable = db.table('_pendingChanges');
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui';
|
||||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ export { calendarViewStore } from './stores/view.svelte';
|
|||
export {
|
||||
useAllCalendars,
|
||||
useAllCalendarItems,
|
||||
useAllEvents,
|
||||
toCalendar,
|
||||
getVisibleCalendars,
|
||||
getDefaultCalendar,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui';
|
||||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
} from '@mana/shared-icons';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalContact } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
import { toastStore } from '@mana/shared-ui/toast';
|
||||
|
||||
|
|
|
|||
|
|
@ -26,5 +26,5 @@ export {
|
|||
export { habitTable, habitLogTable, HABITS_GUEST_SEED } from './collections';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────
|
||||
export { HABIT_COLORS, HABIT_ICONS, EMOJI_TO_ICON_MAP } from './types';
|
||||
export { HABIT_COLORS, HABIT_ICONS } from './types';
|
||||
export type { LocalHabit, LocalHabitLog, Habit, HabitLog } from './types';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
|
|||
import { db } from '$lib/data/database';
|
||||
import type { LocalHabit, LocalHabitLog, Habit, HabitLog } from './types';
|
||||
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
|
||||
import { EMOJI_TO_ICON_MAP } from './types';
|
||||
|
||||
// ─── Type Converters ───────────────────────────────────────
|
||||
|
||||
|
|
@ -16,10 +15,7 @@ export function toHabit(local: LocalHabit): Habit {
|
|||
return {
|
||||
id: local.id,
|
||||
title: local.title,
|
||||
icon:
|
||||
local.icon ??
|
||||
EMOJI_TO_ICON_MAP[(local as unknown as { emoji?: string }).emoji ?? ''] ??
|
||||
'star',
|
||||
icon: local.icon ?? 'star',
|
||||
color: local.color,
|
||||
targetPerDay: local.targetPerDay,
|
||||
defaultDuration: local.defaultDuration ?? null,
|
||||
|
|
|
|||
|
|
@ -101,25 +101,3 @@ export const HABIT_ICONS: string[] = [
|
|||
'target',
|
||||
'fire',
|
||||
];
|
||||
|
||||
/** Maps legacy emoji values to icon names for data migration. */
|
||||
export const EMOJI_TO_ICON_MAP: Record<string, string> = {
|
||||
'\u2615': 'coffee',
|
||||
'\ud83d\udeb6': 'person-simple-walk',
|
||||
'\ud83c\udfc3': 'person-simple-run',
|
||||
'\ud83e\uddd8': 'person-simple-tai-chi',
|
||||
'\ud83d\udca7': 'drop',
|
||||
'\ud83c\udf4e': 'apple-logo',
|
||||
'\ud83d\udcda': 'book-open',
|
||||
'\ud83d\udcaa': 'barbell',
|
||||
'\ud83d\udecc': 'bed',
|
||||
'\ud83c\udfb5': 'music-note',
|
||||
'\ud83d\udc8a': 'pill',
|
||||
'\ud83c\udf7a': 'beer-stein',
|
||||
'\ud83c\udf55': 'pizza',
|
||||
'\ud83d\udeb4': 'bicycle',
|
||||
'\ud83d\udcdd': 'pencil-simple',
|
||||
'\ud83e\uddfc': 'tooth',
|
||||
'\u2b50': 'star',
|
||||
'\ud83d\ude2e\u200d\ud83d\udca8': 'wind',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui';
|
||||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import { Trash, Star, MapPin, X } from '@mana/shared-icons';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalPlace, PlaceCategory, LocalLocationLog } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui';
|
||||
import { dropTarget, dragSource } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@
|
|||
formatDuration,
|
||||
} from '../utils/task-parser';
|
||||
import type { ParsedTask } from '../utils/task-parser';
|
||||
import type { LocalLabel } from '../types';
|
||||
import type { TaskTag } from '../types';
|
||||
import { getPriorityColor } from '../queries';
|
||||
import { Plus, CalendarBlank, Flag, ArrowsClockwise, Timer, Tag, Info } from '@mana/shared-icons';
|
||||
|
||||
interface Props {
|
||||
labels?: LocalLabel[];
|
||||
labels?: TaskTag[];
|
||||
locale?: string;
|
||||
onShowSyntaxHelp?: () => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Task, LocalLabel, LocalBoardView } from '../../types';
|
||||
import type { Task, TaskTag, LocalBoardView } from '../../types';
|
||||
import KanbanLayout from './KanbanLayout.svelte';
|
||||
import GridLayout from './GridLayout.svelte';
|
||||
import FokusLayout from './FokusLayout.svelte';
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
interface Props {
|
||||
view: LocalBoardView;
|
||||
tasks: Task[];
|
||||
labels: LocalLabel[];
|
||||
labels: TaskTag[];
|
||||
wipLimit?: number | null;
|
||||
cardSize?: 'compact' | 'normal' | 'large';
|
||||
onToggleComplete: (taskId: string) => void;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Task, LocalLabel, LocalBoardView } from '../../types';
|
||||
import type { Task, TaskTag, LocalBoardView } from '../../types';
|
||||
import { groupTasksByView } from '../../view-grouping';
|
||||
import { Check, Circle, CaretRight } from '@mana/shared-icons';
|
||||
import { getPriorityColor } from '../../queries';
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
interface Props {
|
||||
view: LocalBoardView;
|
||||
tasks: Task[];
|
||||
labels: LocalLabel[];
|
||||
labels: TaskTag[];
|
||||
onToggleComplete: (taskId: string) => void;
|
||||
onOpenTask: (task: Task) => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import type { Task, LocalLabel, LocalBoardView } from '../../types';
|
||||
import type { Task, TaskTag, LocalBoardView } from '../../types';
|
||||
import { groupTasksByView } from '../../view-grouping';
|
||||
import KanbanTaskCard from '../kanban/KanbanTaskCard.svelte';
|
||||
|
||||
interface Props {
|
||||
view: LocalBoardView;
|
||||
tasks: Task[];
|
||||
labels: LocalLabel[];
|
||||
labels: TaskTag[];
|
||||
onToggleComplete: (taskId: string) => void;
|
||||
onSaveTask: (taskId: string, data: Partial<Task>) => void;
|
||||
onDeleteTask: (taskId: string) => void;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Task, LocalLabel, LocalBoardView } from '../../types';
|
||||
import type { Task, TaskTag, LocalBoardView } from '../../types';
|
||||
import { groupTasksByView, getDropActionUpdate } from '../../view-grouping';
|
||||
import { tasksStore } from '../../stores/tasks.svelte';
|
||||
import ViewColumn from './ViewColumn.svelte';
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
interface Props {
|
||||
view: LocalBoardView;
|
||||
tasks: Task[];
|
||||
labels: LocalLabel[];
|
||||
labels: TaskTag[];
|
||||
wipLimit?: number | null;
|
||||
cardSize?: 'compact' | 'normal' | 'large';
|
||||
onToggleComplete: (taskId: string) => void;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Task, LocalLabel } from '../../types';
|
||||
import type { Task, TaskTag } from '../../types';
|
||||
import type { GroupedColumn } from '../../view-grouping';
|
||||
import ViewColumnHeader from './ViewColumnHeader.svelte';
|
||||
import KanbanTaskCard from '../kanban/KanbanTaskCard.svelte';
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
interface Props {
|
||||
column: GroupedColumn;
|
||||
labels: LocalLabel[];
|
||||
labels: TaskTag[];
|
||||
wipLimit?: number | null;
|
||||
cardSize?: 'compact' | 'normal' | 'large';
|
||||
onToggleComplete: (taskId: string) => void;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
// Stores
|
||||
export { tasksStore } from './stores/tasks.svelte';
|
||||
export { boardViewsStore } from './stores/board-views.svelte';
|
||||
export { labelsStore } from './stores/labels.svelte';
|
||||
export { remindersStore } from './stores/reminders.svelte';
|
||||
export { todoSettings } from './stores/settings.svelte';
|
||||
export { contactsStore } from './stores/contacts.svelte';
|
||||
|
|
@ -62,7 +61,7 @@ export { useTaskForm } from './composables/useTaskForm.svelte';
|
|||
// Types
|
||||
export type {
|
||||
LocalTask,
|
||||
LocalLabel,
|
||||
TaskTag,
|
||||
LocalTaskTag,
|
||||
LocalReminder,
|
||||
LocalBoardView,
|
||||
|
|
|
|||
|
|
@ -14,22 +14,6 @@ export {
|
|||
} from '@mana/shared-stores';
|
||||
|
||||
export const taskTagOps = createTagLinkOps({
|
||||
table: () => db.table('taskLabels'), // DB table still 'taskLabels' until schema migration
|
||||
table: () => db.table('taskLabels'),
|
||||
entityIdField: 'taskId',
|
||||
});
|
||||
|
||||
// Backward-compat alias
|
||||
export const labelsStore = {
|
||||
createLabel: async (data: { name: string; color: string }) => {
|
||||
const { tagMutations } = await import('@mana/shared-stores');
|
||||
return tagMutations.createTag({ name: data.name, color: data.color });
|
||||
},
|
||||
updateLabel: async (id: string, data: { name?: string; color?: string }) => {
|
||||
const { tagMutations } = await import('@mana/shared-stores');
|
||||
return tagMutations.updateTag(id, data);
|
||||
},
|
||||
deleteLabel: async (id: string) => {
|
||||
const { tagMutations } = await import('@mana/shared-stores');
|
||||
return tagMutations.deleteTag(id);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,8 +5,12 @@
|
|||
import type { BaseRecord } from '@mana/local-store';
|
||||
import type { Tag } from '@mana/shared-tags';
|
||||
|
||||
/** @deprecated Use Tag from @mana/shared-tags. Kept for backward compatibility. */
|
||||
export type LocalLabel = Tag;
|
||||
/**
|
||||
* A tag attached to a task. Structurally identical to the shared `Tag`
|
||||
* type — the alias exists so todo code can read `TaskTag` without colliding
|
||||
* with the `Tag` icon from `@mana/shared-icons` that several views import.
|
||||
*/
|
||||
export type TaskTag = Tag;
|
||||
|
||||
// ─── Local Types (IndexedDB) ──────────────────────────────
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import { Heart } from '@mana/shared-icons';
|
||||
import { dropTarget } from '@mana/shared-ui/dnd';
|
||||
import type { TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags, getTagsByIds } from '@mana/shared-stores';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalFavorite } from './types';
|
||||
import type { Quote } from '@zitare/content';
|
||||
|
|
|
|||
|
|
@ -1,40 +1,29 @@
|
|||
/**
|
||||
* All cross-app search providers, registered lazily.
|
||||
*
|
||||
* Each provider lives in its own file and pulls in its module's tables /
|
||||
* helpers. Registering them eagerly would balloon the initial JS bundle for
|
||||
* a feature (spotlight search) that the user only opens on demand. The
|
||||
* dynamic `import()` calls below let Vite split each provider into its own
|
||||
* chunk that the registry awaits the first time `search()` runs.
|
||||
*/
|
||||
|
||||
import type { SearchRegistry } from '../registry';
|
||||
import { todoSearchProvider } from './todo';
|
||||
import { calendarSearchProvider } from './calendar';
|
||||
import { contactsSearchProvider } from './contacts';
|
||||
import { chatSearchProvider } from './chat';
|
||||
import { storageSearchProvider } from './storage';
|
||||
import { cardsSearchProvider } from './cards';
|
||||
import { pictureSearchProvider } from './picture';
|
||||
import { presiSearchProvider } from './presi';
|
||||
import { musicSearchProvider } from './music';
|
||||
import { zitareSearchProvider } from './zitare';
|
||||
import { clockSearchProvider } from './clock';
|
||||
|
||||
export function registerAllProviders(registry: SearchRegistry): void {
|
||||
registry.register(todoSearchProvider);
|
||||
registry.register(calendarSearchProvider);
|
||||
registry.register(contactsSearchProvider);
|
||||
registry.register(chatSearchProvider);
|
||||
registry.register(storageSearchProvider);
|
||||
registry.register(cardsSearchProvider);
|
||||
registry.register(pictureSearchProvider);
|
||||
registry.register(presiSearchProvider);
|
||||
registry.register(musicSearchProvider);
|
||||
registry.register(zitareSearchProvider);
|
||||
registry.register(clockSearchProvider);
|
||||
registry.registerLazy('todo', () => import('./todo').then((m) => m.todoSearchProvider));
|
||||
registry.registerLazy('calendar', () =>
|
||||
import('./calendar').then((m) => m.calendarSearchProvider)
|
||||
);
|
||||
registry.registerLazy('contacts', () =>
|
||||
import('./contacts').then((m) => m.contactsSearchProvider)
|
||||
);
|
||||
registry.registerLazy('chat', () => import('./chat').then((m) => m.chatSearchProvider));
|
||||
registry.registerLazy('storage', () => import('./storage').then((m) => m.storageSearchProvider));
|
||||
registry.registerLazy('cards', () => import('./cards').then((m) => m.cardsSearchProvider));
|
||||
registry.registerLazy('picture', () => import('./picture').then((m) => m.pictureSearchProvider));
|
||||
registry.registerLazy('presi', () => import('./presi').then((m) => m.presiSearchProvider));
|
||||
registry.registerLazy('music', () => import('./music').then((m) => m.musicSearchProvider));
|
||||
registry.registerLazy('zitare', () => import('./zitare').then((m) => m.zitareSearchProvider));
|
||||
registry.registerLazy('clock', () => import('./clock').then((m) => m.clockSearchProvider));
|
||||
}
|
||||
|
||||
export {
|
||||
todoSearchProvider,
|
||||
calendarSearchProvider,
|
||||
contactsSearchProvider,
|
||||
chatSearchProvider,
|
||||
storageSearchProvider,
|
||||
cardsSearchProvider,
|
||||
pictureSearchProvider,
|
||||
presiSearchProvider,
|
||||
musicSearchProvider,
|
||||
zitareSearchProvider,
|
||||
clockSearchProvider,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,12 +3,20 @@
|
|||
*
|
||||
* Central registry that fans out search queries to all registered providers
|
||||
* in parallel and merges results sorted by relevance.
|
||||
*
|
||||
* Providers can be registered eagerly (the synchronous `register()` API) or
|
||||
* lazily (`registerLazy()`). Lazy providers are loaded the first time
|
||||
* `search()` runs — this lets the unified web app keep all per-module search
|
||||
* code out of the initial JS bundle, since search is opened on demand.
|
||||
*/
|
||||
|
||||
import type { SearchProvider, SearchResult, SearchOptions, GroupedSearchResults } from './types';
|
||||
import type { SearchProvider, SearchOptions, GroupedSearchResults } from './types';
|
||||
|
||||
type LazyLoader = () => Promise<SearchProvider>;
|
||||
|
||||
export class SearchRegistry {
|
||||
private providers: SearchProvider[] = [];
|
||||
private lazyLoaders = new Map<string, LazyLoader>();
|
||||
|
||||
register(provider: SearchProvider): void {
|
||||
// Avoid duplicate registration
|
||||
|
|
@ -17,8 +25,45 @@ export class SearchRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
getProviders(): SearchProvider[] {
|
||||
return this.providers;
|
||||
/**
|
||||
* Register a provider that will be loaded on first search. The `appId` is
|
||||
* required up front so the registry can resolve filter constraints
|
||||
* (`options.appIds`) without ever loading providers the user filtered out.
|
||||
*/
|
||||
registerLazy(appId: string, loader: LazyLoader): void {
|
||||
// If something already registered eagerly, prefer it.
|
||||
if (this.providers.some((p) => p.appId === appId)) return;
|
||||
this.lazyLoaders.set(appId, loader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the lazy loaders relevant to a search call (all of them, or just
|
||||
* the ones matching the appIds filter) and registers them. Each loader runs
|
||||
* at most once across the lifetime of the registry.
|
||||
*/
|
||||
private async hydrate(appIdFilter?: string[]): Promise<void> {
|
||||
if (this.lazyLoaders.size === 0) return;
|
||||
const targets = appIdFilter
|
||||
? appIdFilter.filter((id) => this.lazyLoaders.has(id))
|
||||
: Array.from(this.lazyLoaders.keys());
|
||||
if (targets.length === 0) return;
|
||||
|
||||
const loaded = await Promise.all(
|
||||
targets.map(async (appId) => {
|
||||
const loader = this.lazyLoaders.get(appId)!;
|
||||
try {
|
||||
return await loader();
|
||||
} catch (err) {
|
||||
console.error(`[search] failed to load provider "${appId}":`, err);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
for (let i = 0; i < targets.length; i++) {
|
||||
const provider = loaded[i];
|
||||
this.lazyLoaders.delete(targets[i]);
|
||||
if (provider) this.register(provider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -29,6 +74,8 @@ export class SearchRegistry {
|
|||
const q = query.trim();
|
||||
if (!q) return [];
|
||||
|
||||
await this.hydrate(options?.appIds);
|
||||
|
||||
const limit = options?.limit ?? 5;
|
||||
const targetProviders = options?.appIds
|
||||
? this.providers.filter((p) => options.appIds!.includes(p.appId))
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* Tag Store - Re-exports shared local-first tag store
|
||||
*
|
||||
* Tags use the shared IndexedDB ('mana-tags') across all apps.
|
||||
* This module re-exports for backward compatibility with existing imports.
|
||||
*/
|
||||
|
||||
export {
|
||||
tagLocalStore,
|
||||
tagMutations,
|
||||
tagCollection,
|
||||
tagGroupCollection,
|
||||
useAllTags,
|
||||
useAllTagGroups,
|
||||
getTagById,
|
||||
getTagsByIds,
|
||||
getTagColor,
|
||||
getTagsByGroup,
|
||||
} from '@mana/shared-stores';
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
import { AuthGate, GuestWelcomeModal } from '@mana/shared-auth-ui';
|
||||
import { createGuestMode, type GuestMode } from '$lib/stores/guest-mode.svelte';
|
||||
import { NotificationBar } from '@mana/shared-ui';
|
||||
import { tagLocalStore, tagMutations, useAllTags } from '$lib/stores/tags.svelte';
|
||||
import { tagLocalStore, tagMutations, useAllTags } from '@mana/shared-stores';
|
||||
import { linkLocalStore, linkMutations } from '@mana/shared-links';
|
||||
import { manaStore } from '$lib/data/local-store';
|
||||
import { createUnifiedSync } from '$lib/data/sync';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { getContext, onMount } from 'svelte';
|
||||
import { dropTarget } from '@mana/shared-ui/dnd';
|
||||
import type { DragPayload, TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags } from '@mana/shared-stores';
|
||||
import { calendarViewStore } from '$lib/modules/calendar/stores/view.svelte';
|
||||
import { eventsStore } from '$lib/modules/calendar/stores/events.svelte';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { getContext, onMount } from 'svelte';
|
||||
import type { Observable } from 'dexie';
|
||||
import type { DragPayload, TagDragData } from '@mana/shared-ui/dnd';
|
||||
import { useAllTags } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags } from '@mana/shared-stores';
|
||||
import {
|
||||
type Contact,
|
||||
contactsStore,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { useAllTags } from '$lib/stores/tags.svelte';
|
||||
import { useAllTags } from '@mana/shared-stores';
|
||||
|
||||
const tags = useAllTags();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { getContext, onMount } from 'svelte';
|
||||
import type { Observable } from 'dexie';
|
||||
import type { DragPayload } from '@mana/shared-ui/dnd';
|
||||
import { type Task, type LocalLabel, tasksStore, taskTable } from '$lib/modules/todo';
|
||||
import { type Task, type TaskTag, tasksStore, taskTable } from '$lib/modules/todo';
|
||||
import { Gear } from '@mana/shared-icons';
|
||||
import { ShareModal } from '@mana/shared-uload';
|
||||
|
||||
|
|
@ -22,10 +22,10 @@
|
|||
|
||||
// Get data from layout context
|
||||
const allTasks$: Observable<Task[]> = getContext('tasks');
|
||||
const allLabels$: Observable<LocalLabel[]> = getContext('labels');
|
||||
const allLabels$: Observable<TaskTag[]> = getContext('labels');
|
||||
|
||||
let allTasks = $state<Task[]>([]);
|
||||
let allLabels = $state<LocalLabel[]>([]);
|
||||
let allLabels = $state<TaskTag[]>([]);
|
||||
let isLoaded = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue