/** * @fileoverview Tauri IPC abstraction layer. * * Wraps every Rust `#[tauri::command]` behind a thin JS method so the UI * never calls `__TAURI__.core.invoke` directly. Methods are grouped by * domain (projects, tasks, emails, …) and exposed on `GoingsOn.api`. * * Each method maps 1:1 to a Tauri command — the method name documents * which command is invoked, and the arguments mirror the Rust serde input. */ (function() { 'use strict'; // Wait for Tauri to be ready let tauriInvoke = null; // Initialize Tauri invoke function async function initTauri() { if (window.__TAURI__) { tauriInvoke = window.__TAURI__.core.invoke; return true; } return false; } // Try to init immediately initTauri(); /** * Invoke a Tauri command. Lazily initializes the `__TAURI__` reference on * first call — this handles cases where the script loads before the Tauri * runtime is injected (e.g. in dev mode with slow WebView init). */ async function invoke(command, args = {}) { if (!tauriInvoke) { await initTauri(); } if (!tauriInvoke) { throw new Error('Tauri not available'); } try { return await tauriInvoke(command, args); } catch (err) { console.error(`[api] invoke '${command}' failed:`, err, 'args:', JSON.stringify(args)); throw err; } } // ============ API Object ============ const api = { // Projects projects: { list: () => invoke('list_projects'), get: (id) => invoke('get_project', { id }), create: (input) => invoke('create_project', { input }), update: (id, input) => invoke('update_project', { id, input }), delete: (id) => invoke('delete_project', { id }), }, // Tasks — CRUD + lifecycle (start/complete) + snooze/waiting status tasks: { list: () => invoke('list_tasks'), listFiltered: (filters) => invoke('list_tasks_filtered', { filters }), // Server-side filter + paginate listByProject: (projectId) => invoke('list_tasks_for_project', { projectId }), get: (id) => invoke('get_task', { id }), getOverview: (id) => invoke('get_task_overview', { id }), create: (input) => invoke('create_task', { input }), quickAdd: (text) => invoke('quick_add_task', { input: { text } }), // Natural language: "Fix bug +work @tomorrow !high" update: (id, input) => invoke('update_task', { id, input }), delete: (id) => invoke('delete_task', { id }), start: (id) => invoke('start_task', { id }), // Pending → Started (sets started_at) complete: (id) => invoke('complete_task', { id }), // Spawns next instance for recurring tasks listSnoozed: () => invoke('list_snoozed_tasks'), snooze: (id, until) => invoke('snooze_task', { id, input: { until } }), unsnooze: (id) => invoke('unsnooze_task', { id }), listWaiting: () => invoke('list_waiting_tasks'), markWaiting: (id, expectedResponse) => invoke('mark_task_waiting', { id, input: { expectedResponseDate: expectedResponse } }), clearWaiting: (id) => invoke('clear_task_waiting', { id }), }, // Annotations annotations: { list: (taskId) => invoke('list_annotations', { taskId }), add: (taskId, note) => invoke('add_annotation', { taskId, input: { note } }), delete: (taskId, annotationId) => invoke('delete_annotation', { annotationId }), }, // Subtasks subtasks: { list: (taskId) => invoke('list_subtasks', { taskId }), add: (taskId, text) => invoke('add_subtask', { taskId, input: { text } }), addLink: (taskId, linkedTaskId) => invoke('add_subtask_link', { taskId, linkedTaskId }), toggle: (taskId, subtaskId) => invoke('toggle_subtask', { subtaskId }), update: (taskId, subtaskId, text) => invoke('update_subtask', { subtaskId, input: { text } }), delete: (taskId, subtaskId) => invoke('delete_subtask', { subtaskId }), }, // Events events: { list: () => invoke('list_events'), listByProject: (projectId) => invoke('list_events_for_project', { projectId }), listUpcoming: () => invoke('list_upcoming_events'), get: (id) => invoke('get_event', { id }), create: (input) => invoke('create_event', { input }), update: (id, input) => invoke('update_event', { id, input }), delete: (id) => invoke('delete_event', { id }), bulkDelete: (ids) => invoke('bulk_delete_events', { ids }), listBetween: (start, end) => invoke('list_events_between', { start, end }), getStatusIndicator: (leadMinutes) => invoke('get_event_status_indicator', { leadMinutes }), listSnoozed: () => invoke('list_snoozed_events'), snooze: (id, until) => invoke('snooze_event', { id, input: { until } }), unsnooze: (id) => invoke('unsnooze_event', { id }), }, // Emails — CRUD + threading + status (read/archive/snooze/waiting) emails: { list: (includeArchived = false) => invoke('list_emails', { includeArchived }), listThreaded: (params = {}) => invoke('list_emails_threaded', { params }), // Groups by thread_id, paginated listByProject: (projectId) => invoke('list_emails_for_project', { projectId }), listUnlinked: () => invoke('list_unlinked_emails'), // Emails not linked to any project get: (id) => invoke('get_email', { id }), create: (input) => invoke('create_email', { input }), send: (input) => invoke('send_email', { input }), // SMTP send + save copy to local DB delete: (id) => invoke('delete_email', { id }), markRead: (id) => invoke('mark_email_read', { id }), markUnread: (id) => invoke('mark_email_unread', { id }), archive: (id) => invoke('archive_email', { id }), // Also moves on IMAP server if available unarchive: (id) => invoke('unarchive_email', { id }), markAllRead: () => invoke('mark_all_emails_read'), linkToProject: (id, projectId) => invoke('link_email_to_project', { id, input: { projectId } }), getUnreadCount: () => invoke('get_unread_email_count'), listSnoozed: () => invoke('list_snoozed_emails'), snooze: (id, until) => invoke('snooze_email', { id, input: { until } }), unsnooze: (id) => invoke('unsnooze_email', { id }), listWaiting: () => invoke('list_waiting_emails'), markWaiting: (id, expectedResponse) => invoke('mark_email_waiting', { id, input: { expectedResponseDate: expectedResponse } }), clearWaiting: (id) => invoke('clear_email_waiting', { id }), listByThread: (threadId) => invoke('list_emails_by_thread', { threadId }), saveDraft: (input) => invoke('save_email_draft', { input }), listDrafts: () => invoke('list_email_drafts'), sendDraft: (id) => invoke('send_email_draft', { id }), setLabels: (id, labels) => invoke('set_email_labels', { id, labels }), listFolders: () => invoke('list_email_folders'), listLabels: () => invoke('list_email_labels'), moveToFolder: (id, folder) => invoke('move_email_to_folder', { id, folder }), }, // Contacts — CRUD + multi-value fields (emails, phones, social, custom) contacts: { list: () => invoke('list_contacts'), get: (id) => invoke('get_contact', { id }), create: (input) => invoke('create_contact', { input }), update: (id, input) => invoke('update_contact', { id, input }), delete: (id) => invoke('delete_contact', { id }), bulkDelete: (ids) => invoke('bulk_delete_contacts', { ids }), bulkTag: (ids, tag) => invoke('bulk_tag_contacts', { ids, tag }), addEmail: (contactId, input) => invoke('add_contact_email', { contactId, input }), removeEmail: (emailId) => invoke('remove_contact_email', { emailId }), updateEmail: (emailId, input) => invoke('update_contact_email', { emailId, input }), addPhone: (contactId, input) => invoke('add_contact_phone', { contactId, input }), removePhone: (phoneId) => invoke('remove_contact_phone', { phoneId }), updatePhone: (phoneId, input) => invoke('update_contact_phone', { phoneId, input }), addSocialHandle: (contactId, input) => invoke('add_contact_social_handle', { contactId, input }), removeSocialHandle: (handleId) => invoke('remove_contact_social_handle', { handleId }), updateSocialHandle: (handleId, input) => invoke('update_contact_social_handle', { handleId, input }), addCustomField: (contactId, input) => invoke('add_contact_custom_field', { contactId, input }), removeCustomField: (fieldId) => invoke('remove_contact_custom_field', { fieldId }), updateCustomField: (fieldId, input) => invoke('update_contact_custom_field', { fieldId, input }), findByEmail: (email) => invoke('find_contact_by_email', { email }), // Reverse lookup for email sender → contact validateAddresses: (addresses) => invoke('validate_email_addresses', { addresses }), promoteContact: (id) => invoke('promote_contact', { id }), listTasksForContact: (contactId) => invoke('list_tasks_for_contact', { contactId }), listEventsForContact: (contactId) => invoke('list_events_for_contact', { contactId }), listEmailsForContact: (contactId) => invoke('list_emails_for_contact', { contactId }), listFiltered: (search, tag, includeImplicit) => invoke('list_contacts_filtered', { search: search || null, tag: tag || null, includeImplicit: includeImplicit ?? false }), }, // Email Accounts — IMAP/JMAP account configuration and sync triggers emailAccounts: { list: () => invoke('list_email_accounts'), get: (id) => invoke('get_email_account', { id }), create: (input) => invoke('create_email_account', { input }), update: (id, input) => invoke('update_email_account', { id, input }), updateSyncInterval: (id, syncIntervalMinutes) => invoke('update_email_sync_interval', { id, input: { syncIntervalMinutes } }), updateSignature: (id, emailSignature) => invoke('update_email_signature', { id, input: { emailSignature } }), updateNotify: (id, enabled) => invoke('update_email_notify', { id, enabled }), delete: (id) => invoke('delete_email_account', { id }), test: (id) => invoke('test_email_account', { id }), // Verify IMAP/SMTP credentials sync: (id, fullSync = false) => invoke('sync_email_account', { id, fullSync }), // fullSync ignores last_sync_at }, // Stats stats: { getDashboard: () => invoke('get_dashboard_stats'), }, // App metadata app: { getChangelog: () => invoke('get_changelog'), }, // Day Planning dayPlanning: { getDay: (date) => invoke('get_day_planning', { date }), scheduleTask: (id, input) => invoke('schedule_task', { id, input }), unscheduleTask: (id) => invoke('unschedule_task', { id }), }, // Snooze snooze: { getOptions: () => invoke('get_snooze_options'), }, // OAuth — provider-based auth flow (Google, Microsoft, Yahoo, Fastmail) oauth: { listProviders: () => invoke('list_oauth_providers'), start: (providerId) => invoke('start_oauth', { providerId }), // Opens browser for consent complete: (input) => invoke('complete_oauth', { input }), // Exchanges auth code for tokens refreshTokens: (accountId) => invoke('refresh_oauth_tokens', { accountId }), disconnect: (accountId) => invoke('disconnect_oauth', { accountId }), reconnect: (accountId) => invoke('reconnect_oauth', { accountId }), }, // Search — full-text search across all entity types search: { query: (input) => invoke('search', { input }), }, // Export/Backup — data export (JSON/CSV/ICS) and automatic backup management export: { getSummary: () => invoke('get_export_summary'), // Counts per entity type for the export dialog json: (filePath) => invoke('export_json', { filePath }), tasksCSV: (filePath, projectId = null) => invoke('export_tasks_csv', { filePath, projectId }), eventsICS: (filePath, includePast = true) => invoke('export_events_ics', { filePath, includePast }), createBackup: () => invoke('create_backup'), // Full SQLite snapshot listBackups: () => invoke('list_backups'), restoreBackup: (filePath, options) => invoke('restore_backup', { filePath, options }), deleteBackup: (filePath) => invoke('delete_backup', { filePath }), getBackupSettings: () => invoke('get_backup_settings'), saveBackupSettings: (settings) => invoke('save_backup_settings', { input: settings }), }, // Daily Notes — per-day reflection (persisted to SQLite) dailyNotes: { get: (date) => invoke('get_daily_note', { date }), upsert: (input) => invoke('upsert_daily_note', { input }), }, // Weekly Review — GTD-style reflection: focus tasks, vacation, nudges weeklyReview: { get: (weekStart = null) => invoke('get_weekly_review', { input: { weekStart } }), complete: (notes, weekStart = null) => invoke('complete_weekly_review', { input: { notes, weekStart } }), setFocus: (id, isFocus) => invoke('set_task_focus', { id, input: { isFocus } }), clearAllFocus: () => invoke('clear_all_focus'), checkNudge: () => invoke('check_weekly_review_nudge'), setVacationDays: (days, weekStart = null) => invoke('set_vacation_days', { input: { days, weekStart } }), }, // Monthly Review — reflection, goal-setting, heat-map calendar monthlyReview: { get: (month = null) => invoke('get_monthly_review', { input: { month } }), upsertGoal: (month, text, position) => invoke('upsert_monthly_goal', { input: { month, text, position } }), updateGoalStatus: (id, status) => invoke('update_monthly_goal_status', { id, input: { status } }), deleteGoal: (id) => invoke('delete_monthly_goal', { id }), saveReflection: (month, highlight, change) => invoke('save_monthly_reflection', { input: { month, highlight, change } }), }, // Milestones milestones: { list: (projectId) => invoke('list_milestones', { projectId }), create: (input) => invoke('create_milestone', { input }), update: (id, input) => invoke('update_milestone', { id, input }), delete: (id) => invoke('delete_milestone', { id }), reorder: (projectId, input) => invoke('reorder_milestones', { projectId, input }), }, // Sync — cross-device sync with E2E encryption sync: { getTiers: () => invoke('sync_get_tiers'), status: () => invoke('sync_status'), startAuth: () => invoke('sync_start_auth'), completeAuth: (input) => invoke('sync_complete_auth', { input }), disconnect: () => invoke('sync_disconnect'), syncNow: () => invoke('sync_now'), setupEncryptionNew: (password) => invoke('sync_setup_encryption_new', { password }), // First device setupEncryptionExisting: (password) => invoke('sync_setup_encryption_existing', { password }), // Join existing updateSettings: (input) => invoke('sync_update_settings', { input }), subscriptionStatus: () => invoke('sync_subscription_status'), subscribe: (interval) => invoke('sync_subscribe', { interval }), accountInfo: () => invoke('sync_account_info'), }, // Preferences — small JSON file in the app config dir for settings read before the DB is up preferences: { get: () => invoke('get_preferences'), setUpdateCheckOnLaunch: (enabled) => invoke('set_update_check_on_launch', { enabled }), }, // Plugins — Rhai-based import plugin management plugins: { listImport: () => invoke('list_import_plugins'), // All available (enabled + disabled) listEnabled: () => invoke('list_enabled_import_plugins'), getForExtension: (extension) => invoke('get_plugins_for_extension', { extension }), // Match file → compatible plugins preview: (pluginId, filePath, options = {}) => invoke('preview_import', { pluginId, filePath, options }), // Dry-run parse execute: (pluginId, filePath, options = {}) => invoke('execute_import', { pluginId, filePath, options }), // Create entities in DB enable: (pluginId) => invoke('enable_plugin', { pluginId }), // Symlink available/ → enabled/ disable: (pluginId) => invoke('disable_plugin', { pluginId }), reload: (pluginId) => invoke('reload_plugin', { pluginId }), // Re-read + recompile from disk }, // Attachments — file attachments on tasks and projects attachments: { list: (taskId, projectId) => invoke('list_attachments', { taskId: taskId || null, projectId: projectId || null }), add: (taskId, projectId, filePath) => invoke('add_attachment', { taskId: taskId || null, projectId: projectId || null, filePath }), delete: (id) => invoke('delete_attachment', { id }), open: (id) => invoke('open_attachment', { id }), save: (id, destination) => invoke('save_attachment', { id, destination }), convertFromEmail: (emailId, taskId) => invoke('convert_email_attachments', { emailId, taskId }), openEmailBlob: (blobHash, filename) => invoke('open_email_blob', { blobHash, filename }), saveEmailBlob: (blobHash, destination) => invoke('save_email_blob', { blobHash, destination }), fileSize: (filePath) => invoke('get_file_size', { filePath }), }, // External Import — vCard contacts and iCalendar events import: { previewVcf: (filePath) => invoke('preview_vcf', { filePath }), importVcf: (filePath) => invoke('import_vcf', { filePath }), previewIcs: (filePath) => invoke('preview_ics', { filePath }), importIcs: (filePath) => invoke('import_ics', { filePath }), }, // Time Tracking — start/stop timer, sessions, summaries timeTracking: { startTimer: (taskId) => invoke('start_timer', { taskId }), stopTimer: (taskId) => invoke('stop_timer', { taskId }), discardTimer: (taskId) => invoke('discard_timer', { taskId }), getActive: () => invoke('get_active_timer'), listSessions: (taskId) => invoke('list_time_sessions', { taskId }), logManual: (taskId, minutes, date) => invoke('log_manual_time', { input: { taskId, minutes, date } }), getSummary: (start, end) => invoke('get_time_summary', { input: { start, end } }), }, // Window — Tauri window management window: { setTitle: (title) => invoke('set_window_title', { title }), openCompose: (context) => invoke('open_compose_window', { context: context || null }), // Separate compose window, optional reply context openEmailInBrowser: (id) => invoke('open_email_in_browser', { id }), // Render HTML email to temp file → open }, }; // ============ Populate GoingsOn.api Namespace ============ GoingsOn.api = api; })();