experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

tauri backend more closer

+8532 -1
+4
.gitignore
··· 9 9 app/dist/ 10 10 dist/ 11 11 12 + # Tauri build output 13 + backend/tauri/src-tauri/target/ 14 + backend/tauri/src-tauri/gen/ 15 + 12 16 # Test output 13 17 test-results/ 14 18 playwright-report/
+170
backend/README.md
··· 1 + # Peek Backend Architecture 2 + 3 + Peek supports multiple backend implementations that share the same renderer code (`app/`). This document describes the portability architecture. 4 + 5 + ## Design Principles 6 + 7 + 1. **Backend Abstraction**: The `app/` directory contains all renderer code and must work unchanged with any backend 8 + 2. **Shared API Contract**: All backends expose the same `window.app` API via preload script injection 9 + 3. **Shared Data**: Backends use the same SQLite schema and can share database files 10 + 4. **Profile Isolation**: Data is separated by profile (dev, default, etc.) 11 + 12 + ## Directory Structure 13 + 14 + ``` 15 + peek/ 16 + ├── app/ # Renderer code (backend-agnostic) 17 + │ ├── background.html # Main entry point 18 + │ ├── index.js # Core app logic 19 + │ ├── windows.js # Window management 20 + │ └── ... 21 + ├── backend/ 22 + │ ├── electron/ # Electron backend 23 + │ │ ├── index.js # Main process 24 + │ │ ├── preload.js # API bridge (contextBridge) 25 + │ │ ├── protocol.ts # peek:// handler 26 + │ │ └── datastore.ts # SQLite operations 27 + │ └── tauri/ # Tauri backend 28 + │ ├── src-tauri/ # Rust backend 29 + │ │ ├── src/ 30 + │ │ │ ├── lib.rs # App setup 31 + │ │ │ ├── protocol.rs # peek:// handler 32 + │ │ │ ├── datastore.rs# SQLite operations 33 + │ │ │ └── commands/ # IPC handlers 34 + │ │ └── Cargo.toml 35 + │ └── preload.js # API adapter (injected) 36 + └── extensions/ # Extension code (uses window.app API) 37 + ``` 38 + 39 + ## Backend Responsibilities 40 + 41 + Each backend must implement: 42 + 43 + ### 1. Custom Protocol (`peek://`) 44 + - `peek://app/...` → Serve files from `app/` directory 45 + - `peek://ext/{id}/...` → Serve extension files 46 + - MIME type detection 47 + - Path traversal protection 48 + 49 + ### 2. Window Management 50 + - `window_open(source, url, options)` → Create new window 51 + - `window_close(id)` → Close window 52 + - `window_hide(id)` / `window_show(id)` → Toggle visibility 53 + - `window_focus(id)` → Bring to front 54 + - `window_list()` → List all windows 55 + 56 + ### 3. SQLite Datastore 57 + All backends use the same schema (see `app/datastore/schema.js`): 58 + - `addresses` - URIs and metadata 59 + - `visits` - Navigation history 60 + - `tags` - Tag definitions 61 + - `address_tags` - Tag associations 62 + - `extensions` - Extension registry 63 + - `blobs` - Binary data storage 64 + 65 + Database location: `{app_data}/{profile}/datastore.sqlite` 66 + 67 + ### 4. Preload Script Injection 68 + Inject the `window.app` API before any page scripts run. 69 + 70 + ## API Contract 71 + 72 + All backends must expose `window.app` with these methods: 73 + 74 + ```javascript 75 + window.app = { 76 + // Window management 77 + window: { 78 + open(url, options) → Promise<{success, id}> 79 + close(id?) → Promise<{success}> 80 + hide(id?) → Promise<{success}> 81 + show(id?) → Promise<{success}> 82 + focus(id?) → Promise<{success}> 83 + list() → Promise<{success, data: WindowInfo[]}> 84 + }, 85 + 86 + // Datastore 87 + datastore: { 88 + addAddress(uri, options) → Promise<{success, data}> 89 + getAddress(id) → Promise<{success, data}> 90 + updateAddress(id, updates) → Promise<{success}> 91 + queryAddresses(filter) → Promise<{success, data}> 92 + addVisit(addressId, options) → Promise<{success, data}> 93 + queryVisits(filter) → Promise<{success, data}> 94 + getOrCreateTag(name) → Promise<{success, data}> 95 + tagAddress(addressId, tagId) → Promise<{success}> 96 + untagAddress(addressId, tagId) → Promise<{success}> 97 + getAddressTags(addressId) → Promise<{success, data}> 98 + getTable(tableName) → Promise<{success, data}> 99 + setRow(tableName, rowId, rowData) → Promise<{success}> 100 + getStats() → Promise<{success, data}> 101 + }, 102 + 103 + // PubSub messaging 104 + publish(topic, msg, scope), 105 + subscribe(topic, callback, scope), 106 + 107 + // Shortcuts 108 + shortcuts: { 109 + register(shortcut, callback, options), 110 + unregister(shortcut, options) 111 + }, 112 + 113 + // Commands (for cmd palette) 114 + commands: { 115 + register(command), 116 + unregister(name), 117 + getAll() → Promise<Command[]> 118 + }, 119 + 120 + // Logging 121 + log(...args), 122 + 123 + // Constants 124 + debug: boolean, 125 + scopes: { SYSTEM, SELF, GLOBAL } 126 + } 127 + ``` 128 + 129 + ## Running Backends 130 + 131 + ### Electron 132 + ```bash 133 + yarn start # Normal mode 134 + yarn debug # With DevTools 135 + PROFILE=test yarn start # Custom profile 136 + ``` 137 + 138 + ### Tauri 139 + ```bash 140 + cd backend/tauri/src-tauri 141 + cargo run # Normal mode 142 + HEADLESS=1 cargo run # No visible windows (testing) 143 + PROFILE=test cargo run # Custom profile 144 + PEEK_DEVTOOLS=1 cargo run # Auto-open DevTools 145 + ``` 146 + 147 + Or use the helper script: 148 + ```bash 149 + ./scripts/tauri-run.sh 10 # Run headless for 10 seconds 150 + ./scripts/tauri-run.sh 10 --visible # Run with visible windows 151 + ./scripts/tauri-run.sh 0 # Run interactively 152 + ``` 153 + 154 + ## Adding a New Backend 155 + 156 + 1. Create `backend/{name}/` directory 157 + 2. Implement custom protocol handler for `peek://` 158 + 3. Implement SQLite datastore with shared schema 159 + 4. Implement window management commands 160 + 5. Create preload script that exposes `window.app` API 161 + 6. Inject preload before page scripts run 162 + 163 + The renderer code (`app/`) should work without modification. 164 + 165 + ## Data Sync Strategy 166 + 167 + Currently, backends share the same SQLite database file: 168 + - Only one backend should run at a time 169 + - SQLite WAL mode enables better concurrent read access 170 + - Future: real-time sync via file watching or sync service
+375
backend/tauri/preload.js
··· 1 + /** 2 + * Tauri Preload Adapter 3 + * 4 + * Provides the same `window.app` API as Electron's preload.js. 5 + * This script is injected by Tauri's initialization_script mechanism 6 + * so it runs before any page scripts. 7 + */ 8 + (function() { 9 + 'use strict'; 10 + 11 + // Skip if already initialized or not in Tauri 12 + if (window.app || !window.__TAURI__) { 13 + return; 14 + } 15 + 16 + const DEBUG = false; 17 + const DEBUG_LEVELS = { BASIC: 1, FIRST_RUN: 2 }; 18 + const DEBUG_LEVEL = DEBUG_LEVELS.BASIC; 19 + 20 + const sourceAddress = window.location.toString(); 21 + const rndm = () => Math.random().toString(16).slice(2); 22 + 23 + // Context detection 24 + const isCore = sourceAddress.startsWith('peek://app/'); 25 + const isExtension = sourceAddress.startsWith('peek://ext/'); 26 + 27 + const getExtensionId = () => { 28 + if (!isExtension) return null; 29 + const match = sourceAddress.match(/peek:\/\/ext\/([^/]+)/); 30 + return match ? match[1] : null; 31 + }; 32 + 33 + // Tauri APIs 34 + const { invoke } = window.__TAURI__.core; 35 + const { emit, listen } = window.__TAURI__.event; 36 + const { getCurrentWebviewWindow } = window.__TAURI__.webviewWindow; 37 + 38 + // Local shortcut handlers (for non-global shortcuts) 39 + const localShortcutHandlers = new Map(); 40 + 41 + // PubSub subscriptions tracking 42 + const subscriptions = new Map(); 43 + 44 + const api = {}; 45 + 46 + // Log to main process 47 + api.log = (...args) => { 48 + invoke('log_message', { source: sourceAddress, args: args.map(a => String(a)) }).catch(() => {}); 49 + }; 50 + 51 + api.debug = DEBUG; 52 + api.debugLevels = DEBUG_LEVELS; 53 + api.debugLevel = DEBUG_LEVEL; 54 + 55 + api.scopes = { 56 + SYSTEM: 1, 57 + SELF: 2, 58 + GLOBAL: 3 59 + }; 60 + 61 + // ==================== Shortcuts ==================== 62 + 63 + api.shortcuts = { 64 + register: (shortcut, cb, options = {}) => { 65 + const isGlobal = options.global === true; 66 + console.log(`[tauri] registering ${isGlobal ? 'global' : 'local'} shortcut ${shortcut}`); 67 + 68 + if (isGlobal) { 69 + // Global shortcuts not yet implemented in Tauri MVP 70 + console.warn('[tauri] Global shortcuts not yet implemented'); 71 + } else { 72 + localShortcutHandlers.set(shortcut, cb); 73 + } 74 + }, 75 + 76 + unregister: (shortcut, options = {}) => { 77 + const isGlobal = options.global === true; 78 + console.log(`[tauri] unregistering ${isGlobal ? 'global' : 'local'} shortcut ${shortcut}`); 79 + 80 + if (!isGlobal) { 81 + localShortcutHandlers.delete(shortcut); 82 + } 83 + } 84 + }; 85 + 86 + // ==================== Window Management ==================== 87 + // Must match Electron's API exactly 88 + 89 + api.window = { 90 + open: async (url, options = {}) => { 91 + console.log('[tauri] window.open', url); 92 + try { 93 + const result = await invoke('window_open', { source: sourceAddress, url, options }); 94 + // Tauri returns { success, data: { id } } - transform to match Electron's { success, id } 95 + if (result.success && result.data) { 96 + return { success: true, id: result.data.id }; 97 + } 98 + return result; 99 + } catch (e) { 100 + console.error('[tauri] window.open error:', e); 101 + return { success: false, error: String(e) }; 102 + } 103 + }, 104 + 105 + close: async (idOrOptions = null) => { 106 + // Electron API: close(id) or close({ id }) 107 + let id = null; 108 + if (idOrOptions === null || idOrOptions === undefined) { 109 + // Close current window 110 + const currentWindow = getCurrentWebviewWindow(); 111 + await currentWindow.close(); 112 + return { success: true }; 113 + } else if (typeof idOrOptions === 'object') { 114 + id = idOrOptions.id; 115 + } else { 116 + id = idOrOptions; 117 + } 118 + return invoke('window_close', { id }); 119 + }, 120 + 121 + hide: async (idOrOptions) => { 122 + const id = typeof idOrOptions === 'object' ? idOrOptions.id : idOrOptions; 123 + return invoke('window_hide', { id }); 124 + }, 125 + 126 + show: async (idOrOptions) => { 127 + const id = typeof idOrOptions === 'object' ? idOrOptions.id : idOrOptions; 128 + return invoke('window_show', { id }); 129 + }, 130 + 131 + focus: async (idOrOptions) => { 132 + const id = typeof idOrOptions === 'object' ? idOrOptions.id : idOrOptions; 133 + return invoke('window_focus', { id }); 134 + }, 135 + 136 + blur: async (idOrOptions) => { 137 + // Not implemented yet - return success 138 + return { success: true }; 139 + }, 140 + 141 + exists: async (idOrOptions) => { 142 + const id = typeof idOrOptions === 'object' ? idOrOptions.id : idOrOptions; 143 + const result = await invoke('window_list', {}); 144 + if (result.success && result.data) { 145 + const exists = result.data.some(w => w.id === id || w.label === id); 146 + return { success: true, data: exists }; 147 + } 148 + return { success: false, data: false }; 149 + }, 150 + 151 + move: async (idOrOptions, x, y) => { 152 + // Not implemented yet 153 + return { success: true }; 154 + }, 155 + 156 + list: async (options = {}) => { 157 + return invoke('window_list', { options }); 158 + } 159 + }; 160 + 161 + api.closeWindow = (id, callback) => { 162 + api.window.close(id).then(result => { 163 + if (callback) callback(result); 164 + }); 165 + }; 166 + 167 + api.modifyWindow = (winName, params) => { 168 + // Not implemented yet 169 + console.log('[tauri] modifyWindow not implemented'); 170 + }; 171 + 172 + // ==================== PubSub ==================== 173 + 174 + api.publish = (topic, msg, scope = api.scopes.SELF) => { 175 + console.log('[tauri] publish', topic); 176 + emit(`pubsub:${topic}`, { source: sourceAddress, scope, data: msg }).catch(e => { 177 + console.error('[tauri] publish error:', e); 178 + }); 179 + }; 180 + 181 + api.subscribe = (topic, callback, scope = api.scopes.SELF) => { 182 + console.log('[tauri] subscribe', topic); 183 + 184 + const key = `${sourceAddress}:${topic}`; 185 + 186 + listen(`pubsub:${topic}`, (event) => { 187 + const msg = event.payload || {}; 188 + msg.source = sourceAddress; 189 + try { 190 + callback(msg); 191 + } catch (ex) { 192 + console.error('[tauri] subscriber callback error for topic', topic, ex); 193 + } 194 + }).then(unlisten => { 195 + subscriptions.set(key, unlisten); 196 + }).catch(e => { 197 + console.error('[tauri] subscribe error:', e); 198 + }); 199 + }; 200 + 201 + // ==================== Datastore ==================== 202 + 203 + api.datastore = { 204 + addAddress: (uri, options) => invoke('datastore_add_address', { uri, options }), 205 + getAddress: (id) => invoke('datastore_get_address', { id }), 206 + updateAddress: (id, updates) => invoke('datastore_update_address', { id, updates }), 207 + queryAddresses: (filter) => invoke('datastore_query_addresses', { filter }), 208 + addVisit: (addressId, options) => invoke('datastore_add_visit', { addressId, options }), 209 + queryVisits: (filter) => invoke('datastore_query_visits', { filter }), 210 + addContent: (options) => invoke('datastore_add_content', { options }), 211 + queryContent: (filter) => invoke('datastore_query_content', { filter }), 212 + getTable: (tableName) => invoke('datastore_get_table', { tableName }), 213 + setRow: (tableName, rowId, rowData) => invoke('datastore_set_row', { tableName, rowId, rowData }), 214 + getStats: () => invoke('datastore_get_stats', {}), 215 + getOrCreateTag: (name) => invoke('datastore_get_or_create_tag', { name }), 216 + tagAddress: (addressId, tagId) => invoke('datastore_tag_address', { addressId, tagId }), 217 + untagAddress: (addressId, tagId) => invoke('datastore_untag_address', { addressId, tagId }), 218 + getTagsByFrecency: (domain) => ({ success: true, data: [] }), // Not implemented 219 + getAddressTags: (addressId) => invoke('datastore_get_address_tags', { addressId }), 220 + getAddressesByTag: (tagId) => ({ success: true, data: [] }), // Not implemented 221 + getUntaggedAddresses: () => ({ success: true, data: [] }) // Not implemented 222 + }; 223 + 224 + // ==================== Commands ==================== 225 + 226 + api.commands = { 227 + register: (command) => { 228 + if (!command.name || !command.execute) { 229 + console.error('commands.register: name and execute are required'); 230 + return; 231 + } 232 + window._cmdHandlers = window._cmdHandlers || {}; 233 + window._cmdHandlers[command.name] = command.execute; 234 + 235 + // Publish command registration 236 + api.publish('cmd:register', { 237 + name: command.name, 238 + description: command.description || '', 239 + source: sourceAddress 240 + }, api.scopes.GLOBAL); 241 + 242 + console.log('[tauri] commands.register:', command.name); 243 + }, 244 + 245 + unregister: (name) => { 246 + if (window._cmdHandlers) { 247 + delete window._cmdHandlers[name]; 248 + } 249 + api.publish('cmd:unregister', { name }, api.scopes.GLOBAL); 250 + console.log('[tauri] commands.unregister:', name); 251 + }, 252 + 253 + getAll: async () => { 254 + // Return empty array for now - cmd registry not implemented 255 + return []; 256 + } 257 + }; 258 + 259 + // ==================== Extensions ==================== 260 + 261 + api.extensions = { 262 + _hasPermission: () => sourceAddress.startsWith('peek://app/'), 263 + 264 + list: async () => ({ success: true, data: [] }), 265 + load: async (id) => ({ success: false, error: 'Not implemented in Tauri MVP' }), 266 + unload: async (id) => ({ success: false, error: 'Not implemented in Tauri MVP' }), 267 + reload: async (id) => ({ success: false, error: 'Not implemented in Tauri MVP' }), 268 + getManifest: async (id) => ({ success: false, error: 'Not implemented' }), 269 + pickFolder: async () => ({ success: false, error: 'Not implemented' }), 270 + validateFolder: async (path) => ({ success: false, error: 'Not implemented' }), 271 + add: async (path, manifest, enabled) => ({ success: false, error: 'Not implemented' }), 272 + remove: async (id) => ({ success: false, error: 'Not implemented' }), 273 + update: async (id, updates) => ({ success: false, error: 'Not implemented' }), 274 + getAll: async () => ({ success: true, data: [] }), 275 + get: async (id) => ({ success: false, error: 'Not found' }), 276 + getSettingsSchema: async (extId) => ({ success: false, error: 'Not implemented' }) 277 + }; 278 + 279 + // ==================== Settings ==================== 280 + 281 + api.settings = { 282 + get: async () => { 283 + const extId = getExtensionId(); 284 + if (!extId) return { success: false, error: 'Not an extension context' }; 285 + return { success: true, data: {} }; 286 + }, 287 + set: async (settings) => { 288 + const extId = getExtensionId(); 289 + if (!extId) return { success: false, error: 'Not an extension context' }; 290 + return { success: true }; 291 + }, 292 + getKey: async (key) => { 293 + const extId = getExtensionId(); 294 + if (!extId) return { success: false, error: 'Not an extension context' }; 295 + return { success: true, data: null }; 296 + }, 297 + setKey: async (key, value) => { 298 + const extId = getExtensionId(); 299 + if (!extId) return { success: false, error: 'Not an extension context' }; 300 + return { success: true }; 301 + } 302 + }; 303 + 304 + // ==================== Escape ==================== 305 + 306 + api.escape = { 307 + onEscape: (callback) => { 308 + // Will be called when ESC is pressed 309 + window._escapeCallback = callback; 310 + } 311 + }; 312 + 313 + // ==================== App Control ==================== 314 + 315 + api.quit = () => { 316 + console.log('[tauri] quit requested'); 317 + // Not implemented yet 318 + }; 319 + 320 + // ==================== Keyboard Handling ==================== 321 + 322 + // Local shortcut handler 323 + document.addEventListener('keydown', (e) => { 324 + const parts = []; 325 + if (e.altKey) parts.push('Alt'); 326 + if (e.ctrlKey) parts.push('Control'); 327 + if (e.metaKey) parts.push('Command'); 328 + if (e.shiftKey) parts.push('Shift'); 329 + 330 + if (e.key.length === 1) { 331 + parts.push(e.key.toUpperCase()); 332 + } else { 333 + parts.push(e.key); 334 + } 335 + 336 + const shortcut = parts.join('+'); 337 + const variations = [ 338 + shortcut, 339 + shortcut.replace('Alt', 'Option'), 340 + shortcut.replace('Control', 'CommandOrControl'), 341 + shortcut.replace('Command', 'CommandOrControl'), 342 + ]; 343 + 344 + for (const variant of variations) { 345 + const handler = localShortcutHandlers.get(variant); 346 + if (handler) { 347 + e.preventDefault(); 348 + handler(); 349 + break; 350 + } 351 + } 352 + }); 353 + 354 + // ESC key handling 355 + document.addEventListener('keyup', (e) => { 356 + if (e.key === 'Escape') { 357 + if (window._escapeCallback) { 358 + Promise.resolve(window._escapeCallback()).then(result => { 359 + if (!result || !result.handled) { 360 + getCurrentWebviewWindow().close(); 361 + } 362 + }).catch(() => { 363 + getCurrentWebviewWindow().close(); 364 + }); 365 + } else { 366 + getCurrentWebviewWindow().close(); 367 + } 368 + } 369 + }); 370 + 371 + // Expose API globally 372 + window.app = api; 373 + 374 + console.log('[tauri:preload] API initialized for:', sourceAddress); 375 + })();
+4908
backend/tauri/src-tauri/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "adler2" 7 + version = "2.0.1" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 + 11 + [[package]] 12 + name = "ahash" 13 + version = "0.8.12" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 16 + dependencies = [ 17 + "cfg-if", 18 + "once_cell", 19 + "version_check", 20 + "zerocopy", 21 + ] 22 + 23 + [[package]] 24 + name = "aho-corasick" 25 + version = "1.1.4" 26 + source = "registry+https://github.com/rust-lang/crates.io-index" 27 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 28 + dependencies = [ 29 + "memchr", 30 + ] 31 + 32 + [[package]] 33 + name = "alloc-no-stdlib" 34 + version = "2.0.4" 35 + source = "registry+https://github.com/rust-lang/crates.io-index" 36 + checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 37 + 38 + [[package]] 39 + name = "alloc-stdlib" 40 + version = "0.2.2" 41 + source = "registry+https://github.com/rust-lang/crates.io-index" 42 + checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 43 + dependencies = [ 44 + "alloc-no-stdlib", 45 + ] 46 + 47 + [[package]] 48 + name = "android_system_properties" 49 + version = "0.1.5" 50 + source = "registry+https://github.com/rust-lang/crates.io-index" 51 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 52 + dependencies = [ 53 + "libc", 54 + ] 55 + 56 + [[package]] 57 + name = "anyhow" 58 + version = "1.0.100" 59 + source = "registry+https://github.com/rust-lang/crates.io-index" 60 + checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 61 + 62 + [[package]] 63 + name = "atk" 64 + version = "0.18.2" 65 + source = "registry+https://github.com/rust-lang/crates.io-index" 66 + checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" 67 + dependencies = [ 68 + "atk-sys", 69 + "glib", 70 + "libc", 71 + ] 72 + 73 + [[package]] 74 + name = "atk-sys" 75 + version = "0.18.2" 76 + source = "registry+https://github.com/rust-lang/crates.io-index" 77 + checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" 78 + dependencies = [ 79 + "glib-sys", 80 + "gobject-sys", 81 + "libc", 82 + "system-deps", 83 + ] 84 + 85 + [[package]] 86 + name = "atomic-waker" 87 + version = "1.1.2" 88 + source = "registry+https://github.com/rust-lang/crates.io-index" 89 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 90 + 91 + [[package]] 92 + name = "autocfg" 93 + version = "1.5.0" 94 + source = "registry+https://github.com/rust-lang/crates.io-index" 95 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 96 + 97 + [[package]] 98 + name = "base64" 99 + version = "0.21.7" 100 + source = "registry+https://github.com/rust-lang/crates.io-index" 101 + checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 102 + 103 + [[package]] 104 + name = "base64" 105 + version = "0.22.1" 106 + source = "registry+https://github.com/rust-lang/crates.io-index" 107 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 108 + 109 + [[package]] 110 + name = "bitflags" 111 + version = "1.3.2" 112 + source = "registry+https://github.com/rust-lang/crates.io-index" 113 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 114 + 115 + [[package]] 116 + name = "bitflags" 117 + version = "2.10.0" 118 + source = "registry+https://github.com/rust-lang/crates.io-index" 119 + checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 120 + dependencies = [ 121 + "serde_core", 122 + ] 123 + 124 + [[package]] 125 + name = "block-buffer" 126 + version = "0.10.4" 127 + source = "registry+https://github.com/rust-lang/crates.io-index" 128 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 129 + dependencies = [ 130 + "generic-array", 131 + ] 132 + 133 + [[package]] 134 + name = "block2" 135 + version = "0.6.2" 136 + source = "registry+https://github.com/rust-lang/crates.io-index" 137 + checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" 138 + dependencies = [ 139 + "objc2", 140 + ] 141 + 142 + [[package]] 143 + name = "brotli" 144 + version = "8.0.2" 145 + source = "registry+https://github.com/rust-lang/crates.io-index" 146 + checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" 147 + dependencies = [ 148 + "alloc-no-stdlib", 149 + "alloc-stdlib", 150 + "brotli-decompressor", 151 + ] 152 + 153 + [[package]] 154 + name = "brotli-decompressor" 155 + version = "5.0.0" 156 + source = "registry+https://github.com/rust-lang/crates.io-index" 157 + checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" 158 + dependencies = [ 159 + "alloc-no-stdlib", 160 + "alloc-stdlib", 161 + ] 162 + 163 + [[package]] 164 + name = "bumpalo" 165 + version = "3.19.1" 166 + source = "registry+https://github.com/rust-lang/crates.io-index" 167 + checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" 168 + 169 + [[package]] 170 + name = "bytemuck" 171 + version = "1.24.0" 172 + source = "registry+https://github.com/rust-lang/crates.io-index" 173 + checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" 174 + 175 + [[package]] 176 + name = "byteorder" 177 + version = "1.5.0" 178 + source = "registry+https://github.com/rust-lang/crates.io-index" 179 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 180 + 181 + [[package]] 182 + name = "bytes" 183 + version = "1.11.0" 184 + source = "registry+https://github.com/rust-lang/crates.io-index" 185 + checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 186 + dependencies = [ 187 + "serde", 188 + ] 189 + 190 + [[package]] 191 + name = "cairo-rs" 192 + version = "0.18.5" 193 + source = "registry+https://github.com/rust-lang/crates.io-index" 194 + checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" 195 + dependencies = [ 196 + "bitflags 2.10.0", 197 + "cairo-sys-rs", 198 + "glib", 199 + "libc", 200 + "once_cell", 201 + "thiserror 1.0.69", 202 + ] 203 + 204 + [[package]] 205 + name = "cairo-sys-rs" 206 + version = "0.18.2" 207 + source = "registry+https://github.com/rust-lang/crates.io-index" 208 + checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" 209 + dependencies = [ 210 + "glib-sys", 211 + "libc", 212 + "system-deps", 213 + ] 214 + 215 + [[package]] 216 + name = "camino" 217 + version = "1.2.2" 218 + source = "registry+https://github.com/rust-lang/crates.io-index" 219 + checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" 220 + dependencies = [ 221 + "serde_core", 222 + ] 223 + 224 + [[package]] 225 + name = "cargo-platform" 226 + version = "0.1.9" 227 + source = "registry+https://github.com/rust-lang/crates.io-index" 228 + checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" 229 + dependencies = [ 230 + "serde", 231 + ] 232 + 233 + [[package]] 234 + name = "cargo_metadata" 235 + version = "0.19.2" 236 + source = "registry+https://github.com/rust-lang/crates.io-index" 237 + checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" 238 + dependencies = [ 239 + "camino", 240 + "cargo-platform", 241 + "semver", 242 + "serde", 243 + "serde_json", 244 + "thiserror 2.0.17", 245 + ] 246 + 247 + [[package]] 248 + name = "cargo_toml" 249 + version = "0.22.3" 250 + source = "registry+https://github.com/rust-lang/crates.io-index" 251 + checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" 252 + dependencies = [ 253 + "serde", 254 + "toml 0.9.11+spec-1.1.0", 255 + ] 256 + 257 + [[package]] 258 + name = "cc" 259 + version = "1.2.52" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" 262 + dependencies = [ 263 + "find-msvc-tools", 264 + "shlex", 265 + ] 266 + 267 + [[package]] 268 + name = "cesu8" 269 + version = "1.1.0" 270 + source = "registry+https://github.com/rust-lang/crates.io-index" 271 + checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 272 + 273 + [[package]] 274 + name = "cfb" 275 + version = "0.7.3" 276 + source = "registry+https://github.com/rust-lang/crates.io-index" 277 + checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" 278 + dependencies = [ 279 + "byteorder", 280 + "fnv", 281 + "uuid", 282 + ] 283 + 284 + [[package]] 285 + name = "cfg-expr" 286 + version = "0.15.8" 287 + source = "registry+https://github.com/rust-lang/crates.io-index" 288 + checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" 289 + dependencies = [ 290 + "smallvec", 291 + "target-lexicon", 292 + ] 293 + 294 + [[package]] 295 + name = "cfg-if" 296 + version = "1.0.4" 297 + source = "registry+https://github.com/rust-lang/crates.io-index" 298 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 299 + 300 + [[package]] 301 + name = "chrono" 302 + version = "0.4.42" 303 + source = "registry+https://github.com/rust-lang/crates.io-index" 304 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 305 + dependencies = [ 306 + "iana-time-zone", 307 + "js-sys", 308 + "num-traits", 309 + "serde", 310 + "wasm-bindgen", 311 + "windows-link 0.2.1", 312 + ] 313 + 314 + [[package]] 315 + name = "combine" 316 + version = "4.6.7" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 319 + dependencies = [ 320 + "bytes", 321 + "memchr", 322 + ] 323 + 324 + [[package]] 325 + name = "convert_case" 326 + version = "0.4.0" 327 + source = "registry+https://github.com/rust-lang/crates.io-index" 328 + checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 329 + 330 + [[package]] 331 + name = "cookie" 332 + version = "0.18.1" 333 + source = "registry+https://github.com/rust-lang/crates.io-index" 334 + checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 335 + dependencies = [ 336 + "time", 337 + "version_check", 338 + ] 339 + 340 + [[package]] 341 + name = "core-foundation" 342 + version = "0.10.1" 343 + source = "registry+https://github.com/rust-lang/crates.io-index" 344 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 345 + dependencies = [ 346 + "core-foundation-sys", 347 + "libc", 348 + ] 349 + 350 + [[package]] 351 + name = "core-foundation-sys" 352 + version = "0.8.7" 353 + source = "registry+https://github.com/rust-lang/crates.io-index" 354 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 355 + 356 + [[package]] 357 + name = "core-graphics" 358 + version = "0.24.0" 359 + source = "registry+https://github.com/rust-lang/crates.io-index" 360 + checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" 361 + dependencies = [ 362 + "bitflags 2.10.0", 363 + "core-foundation", 364 + "core-graphics-types", 365 + "foreign-types", 366 + "libc", 367 + ] 368 + 369 + [[package]] 370 + name = "core-graphics-types" 371 + version = "0.2.0" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" 374 + dependencies = [ 375 + "bitflags 2.10.0", 376 + "core-foundation", 377 + "libc", 378 + ] 379 + 380 + [[package]] 381 + name = "cpufeatures" 382 + version = "0.2.17" 383 + source = "registry+https://github.com/rust-lang/crates.io-index" 384 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 385 + dependencies = [ 386 + "libc", 387 + ] 388 + 389 + [[package]] 390 + name = "crc32fast" 391 + version = "1.5.0" 392 + source = "registry+https://github.com/rust-lang/crates.io-index" 393 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 394 + dependencies = [ 395 + "cfg-if", 396 + ] 397 + 398 + [[package]] 399 + name = "crossbeam-channel" 400 + version = "0.5.15" 401 + source = "registry+https://github.com/rust-lang/crates.io-index" 402 + checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 403 + dependencies = [ 404 + "crossbeam-utils", 405 + ] 406 + 407 + [[package]] 408 + name = "crossbeam-utils" 409 + version = "0.8.21" 410 + source = "registry+https://github.com/rust-lang/crates.io-index" 411 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 412 + 413 + [[package]] 414 + name = "crypto-common" 415 + version = "0.1.7" 416 + source = "registry+https://github.com/rust-lang/crates.io-index" 417 + checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 418 + dependencies = [ 419 + "generic-array", 420 + "typenum", 421 + ] 422 + 423 + [[package]] 424 + name = "cssparser" 425 + version = "0.29.6" 426 + source = "registry+https://github.com/rust-lang/crates.io-index" 427 + checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" 428 + dependencies = [ 429 + "cssparser-macros", 430 + "dtoa-short", 431 + "itoa", 432 + "matches", 433 + "phf 0.10.1", 434 + "proc-macro2", 435 + "quote", 436 + "smallvec", 437 + "syn 1.0.109", 438 + ] 439 + 440 + [[package]] 441 + name = "cssparser-macros" 442 + version = "0.6.1" 443 + source = "registry+https://github.com/rust-lang/crates.io-index" 444 + checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" 445 + dependencies = [ 446 + "quote", 447 + "syn 2.0.114", 448 + ] 449 + 450 + [[package]] 451 + name = "ctor" 452 + version = "0.2.9" 453 + source = "registry+https://github.com/rust-lang/crates.io-index" 454 + checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" 455 + dependencies = [ 456 + "quote", 457 + "syn 2.0.114", 458 + ] 459 + 460 + [[package]] 461 + name = "darling" 462 + version = "0.21.3" 463 + source = "registry+https://github.com/rust-lang/crates.io-index" 464 + checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" 465 + dependencies = [ 466 + "darling_core", 467 + "darling_macro", 468 + ] 469 + 470 + [[package]] 471 + name = "darling_core" 472 + version = "0.21.3" 473 + source = "registry+https://github.com/rust-lang/crates.io-index" 474 + checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" 475 + dependencies = [ 476 + "fnv", 477 + "ident_case", 478 + "proc-macro2", 479 + "quote", 480 + "strsim", 481 + "syn 2.0.114", 482 + ] 483 + 484 + [[package]] 485 + name = "darling_macro" 486 + version = "0.21.3" 487 + source = "registry+https://github.com/rust-lang/crates.io-index" 488 + checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" 489 + dependencies = [ 490 + "darling_core", 491 + "quote", 492 + "syn 2.0.114", 493 + ] 494 + 495 + [[package]] 496 + name = "deranged" 497 + version = "0.5.5" 498 + source = "registry+https://github.com/rust-lang/crates.io-index" 499 + checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 500 + dependencies = [ 501 + "powerfmt", 502 + "serde_core", 503 + ] 504 + 505 + [[package]] 506 + name = "derive_more" 507 + version = "0.99.20" 508 + source = "registry+https://github.com/rust-lang/crates.io-index" 509 + checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" 510 + dependencies = [ 511 + "convert_case", 512 + "proc-macro2", 513 + "quote", 514 + "rustc_version", 515 + "syn 2.0.114", 516 + ] 517 + 518 + [[package]] 519 + name = "digest" 520 + version = "0.10.7" 521 + source = "registry+https://github.com/rust-lang/crates.io-index" 522 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 523 + dependencies = [ 524 + "block-buffer", 525 + "crypto-common", 526 + ] 527 + 528 + [[package]] 529 + name = "dirs" 530 + version = "6.0.0" 531 + source = "registry+https://github.com/rust-lang/crates.io-index" 532 + checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 533 + dependencies = [ 534 + "dirs-sys", 535 + ] 536 + 537 + [[package]] 538 + name = "dirs-sys" 539 + version = "0.5.0" 540 + source = "registry+https://github.com/rust-lang/crates.io-index" 541 + checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 542 + dependencies = [ 543 + "libc", 544 + "option-ext", 545 + "redox_users", 546 + "windows-sys 0.61.2", 547 + ] 548 + 549 + [[package]] 550 + name = "dispatch" 551 + version = "0.2.0" 552 + source = "registry+https://github.com/rust-lang/crates.io-index" 553 + checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 554 + 555 + [[package]] 556 + name = "dispatch2" 557 + version = "0.3.0" 558 + source = "registry+https://github.com/rust-lang/crates.io-index" 559 + checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" 560 + dependencies = [ 561 + "bitflags 2.10.0", 562 + "objc2", 563 + ] 564 + 565 + [[package]] 566 + name = "displaydoc" 567 + version = "0.2.5" 568 + source = "registry+https://github.com/rust-lang/crates.io-index" 569 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 570 + dependencies = [ 571 + "proc-macro2", 572 + "quote", 573 + "syn 2.0.114", 574 + ] 575 + 576 + [[package]] 577 + name = "dlopen2" 578 + version = "0.8.2" 579 + source = "registry+https://github.com/rust-lang/crates.io-index" 580 + checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" 581 + dependencies = [ 582 + "dlopen2_derive", 583 + "libc", 584 + "once_cell", 585 + "winapi", 586 + ] 587 + 588 + [[package]] 589 + name = "dlopen2_derive" 590 + version = "0.4.3" 591 + source = "registry+https://github.com/rust-lang/crates.io-index" 592 + checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" 593 + dependencies = [ 594 + "proc-macro2", 595 + "quote", 596 + "syn 2.0.114", 597 + ] 598 + 599 + [[package]] 600 + name = "dpi" 601 + version = "0.1.2" 602 + source = "registry+https://github.com/rust-lang/crates.io-index" 603 + checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" 604 + dependencies = [ 605 + "serde", 606 + ] 607 + 608 + [[package]] 609 + name = "dtoa" 610 + version = "1.0.11" 611 + source = "registry+https://github.com/rust-lang/crates.io-index" 612 + checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" 613 + 614 + [[package]] 615 + name = "dtoa-short" 616 + version = "0.3.5" 617 + source = "registry+https://github.com/rust-lang/crates.io-index" 618 + checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" 619 + dependencies = [ 620 + "dtoa", 621 + ] 622 + 623 + [[package]] 624 + name = "dunce" 625 + version = "1.0.5" 626 + source = "registry+https://github.com/rust-lang/crates.io-index" 627 + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 628 + 629 + [[package]] 630 + name = "dyn-clone" 631 + version = "1.0.20" 632 + source = "registry+https://github.com/rust-lang/crates.io-index" 633 + checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 634 + 635 + [[package]] 636 + name = "embed-resource" 637 + version = "3.0.6" 638 + source = "registry+https://github.com/rust-lang/crates.io-index" 639 + checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" 640 + dependencies = [ 641 + "cc", 642 + "memchr", 643 + "rustc_version", 644 + "toml 0.9.11+spec-1.1.0", 645 + "vswhom", 646 + "winreg", 647 + ] 648 + 649 + [[package]] 650 + name = "embed_plist" 651 + version = "1.2.2" 652 + source = "registry+https://github.com/rust-lang/crates.io-index" 653 + checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" 654 + 655 + [[package]] 656 + name = "encoding_rs" 657 + version = "0.8.35" 658 + source = "registry+https://github.com/rust-lang/crates.io-index" 659 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 660 + dependencies = [ 661 + "cfg-if", 662 + ] 663 + 664 + [[package]] 665 + name = "equivalent" 666 + version = "1.0.2" 667 + source = "registry+https://github.com/rust-lang/crates.io-index" 668 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 669 + 670 + [[package]] 671 + name = "erased-serde" 672 + version = "0.4.9" 673 + source = "registry+https://github.com/rust-lang/crates.io-index" 674 + checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" 675 + dependencies = [ 676 + "serde", 677 + "serde_core", 678 + "typeid", 679 + ] 680 + 681 + [[package]] 682 + name = "errno" 683 + version = "0.3.14" 684 + source = "registry+https://github.com/rust-lang/crates.io-index" 685 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 686 + dependencies = [ 687 + "libc", 688 + "windows-sys 0.61.2", 689 + ] 690 + 691 + [[package]] 692 + name = "fallible-iterator" 693 + version = "0.3.0" 694 + source = "registry+https://github.com/rust-lang/crates.io-index" 695 + checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 696 + 697 + [[package]] 698 + name = "fallible-streaming-iterator" 699 + version = "0.1.9" 700 + source = "registry+https://github.com/rust-lang/crates.io-index" 701 + checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 702 + 703 + [[package]] 704 + name = "fastrand" 705 + version = "2.3.0" 706 + source = "registry+https://github.com/rust-lang/crates.io-index" 707 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 708 + 709 + [[package]] 710 + name = "fdeflate" 711 + version = "0.3.7" 712 + source = "registry+https://github.com/rust-lang/crates.io-index" 713 + checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 714 + dependencies = [ 715 + "simd-adler32", 716 + ] 717 + 718 + [[package]] 719 + name = "field-offset" 720 + version = "0.3.6" 721 + source = "registry+https://github.com/rust-lang/crates.io-index" 722 + checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" 723 + dependencies = [ 724 + "memoffset", 725 + "rustc_version", 726 + ] 727 + 728 + [[package]] 729 + name = "find-msvc-tools" 730 + version = "0.1.7" 731 + source = "registry+https://github.com/rust-lang/crates.io-index" 732 + checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" 733 + 734 + [[package]] 735 + name = "flate2" 736 + version = "1.1.5" 737 + source = "registry+https://github.com/rust-lang/crates.io-index" 738 + checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" 739 + dependencies = [ 740 + "crc32fast", 741 + "miniz_oxide", 742 + ] 743 + 744 + [[package]] 745 + name = "fnv" 746 + version = "1.0.7" 747 + source = "registry+https://github.com/rust-lang/crates.io-index" 748 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 749 + 750 + [[package]] 751 + name = "foreign-types" 752 + version = "0.5.0" 753 + source = "registry+https://github.com/rust-lang/crates.io-index" 754 + checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 755 + dependencies = [ 756 + "foreign-types-macros", 757 + "foreign-types-shared", 758 + ] 759 + 760 + [[package]] 761 + name = "foreign-types-macros" 762 + version = "0.2.3" 763 + source = "registry+https://github.com/rust-lang/crates.io-index" 764 + checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 765 + dependencies = [ 766 + "proc-macro2", 767 + "quote", 768 + "syn 2.0.114", 769 + ] 770 + 771 + [[package]] 772 + name = "foreign-types-shared" 773 + version = "0.3.1" 774 + source = "registry+https://github.com/rust-lang/crates.io-index" 775 + checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 776 + 777 + [[package]] 778 + name = "form_urlencoded" 779 + version = "1.2.2" 780 + source = "registry+https://github.com/rust-lang/crates.io-index" 781 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 782 + dependencies = [ 783 + "percent-encoding", 784 + ] 785 + 786 + [[package]] 787 + name = "futf" 788 + version = "0.1.5" 789 + source = "registry+https://github.com/rust-lang/crates.io-index" 790 + checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 791 + dependencies = [ 792 + "mac", 793 + "new_debug_unreachable", 794 + ] 795 + 796 + [[package]] 797 + name = "futures-channel" 798 + version = "0.3.31" 799 + source = "registry+https://github.com/rust-lang/crates.io-index" 800 + checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 801 + dependencies = [ 802 + "futures-core", 803 + ] 804 + 805 + [[package]] 806 + name = "futures-core" 807 + version = "0.3.31" 808 + source = "registry+https://github.com/rust-lang/crates.io-index" 809 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 810 + 811 + [[package]] 812 + name = "futures-executor" 813 + version = "0.3.31" 814 + source = "registry+https://github.com/rust-lang/crates.io-index" 815 + checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 816 + dependencies = [ 817 + "futures-core", 818 + "futures-task", 819 + "futures-util", 820 + ] 821 + 822 + [[package]] 823 + name = "futures-io" 824 + version = "0.3.31" 825 + source = "registry+https://github.com/rust-lang/crates.io-index" 826 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 827 + 828 + [[package]] 829 + name = "futures-macro" 830 + version = "0.3.31" 831 + source = "registry+https://github.com/rust-lang/crates.io-index" 832 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 833 + dependencies = [ 834 + "proc-macro2", 835 + "quote", 836 + "syn 2.0.114", 837 + ] 838 + 839 + [[package]] 840 + name = "futures-sink" 841 + version = "0.3.31" 842 + source = "registry+https://github.com/rust-lang/crates.io-index" 843 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 844 + 845 + [[package]] 846 + name = "futures-task" 847 + version = "0.3.31" 848 + source = "registry+https://github.com/rust-lang/crates.io-index" 849 + checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 850 + 851 + [[package]] 852 + name = "futures-util" 853 + version = "0.3.31" 854 + source = "registry+https://github.com/rust-lang/crates.io-index" 855 + checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 856 + dependencies = [ 857 + "futures-core", 858 + "futures-io", 859 + "futures-macro", 860 + "futures-sink", 861 + "futures-task", 862 + "memchr", 863 + "pin-project-lite", 864 + "pin-utils", 865 + "slab", 866 + ] 867 + 868 + [[package]] 869 + name = "fxhash" 870 + version = "0.2.1" 871 + source = "registry+https://github.com/rust-lang/crates.io-index" 872 + checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 873 + dependencies = [ 874 + "byteorder", 875 + ] 876 + 877 + [[package]] 878 + name = "gdk" 879 + version = "0.18.2" 880 + source = "registry+https://github.com/rust-lang/crates.io-index" 881 + checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" 882 + dependencies = [ 883 + "cairo-rs", 884 + "gdk-pixbuf", 885 + "gdk-sys", 886 + "gio", 887 + "glib", 888 + "libc", 889 + "pango", 890 + ] 891 + 892 + [[package]] 893 + name = "gdk-pixbuf" 894 + version = "0.18.5" 895 + source = "registry+https://github.com/rust-lang/crates.io-index" 896 + checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" 897 + dependencies = [ 898 + "gdk-pixbuf-sys", 899 + "gio", 900 + "glib", 901 + "libc", 902 + "once_cell", 903 + ] 904 + 905 + [[package]] 906 + name = "gdk-pixbuf-sys" 907 + version = "0.18.0" 908 + source = "registry+https://github.com/rust-lang/crates.io-index" 909 + checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" 910 + dependencies = [ 911 + "gio-sys", 912 + "glib-sys", 913 + "gobject-sys", 914 + "libc", 915 + "system-deps", 916 + ] 917 + 918 + [[package]] 919 + name = "gdk-sys" 920 + version = "0.18.2" 921 + source = "registry+https://github.com/rust-lang/crates.io-index" 922 + checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" 923 + dependencies = [ 924 + "cairo-sys-rs", 925 + "gdk-pixbuf-sys", 926 + "gio-sys", 927 + "glib-sys", 928 + "gobject-sys", 929 + "libc", 930 + "pango-sys", 931 + "pkg-config", 932 + "system-deps", 933 + ] 934 + 935 + [[package]] 936 + name = "gdkwayland-sys" 937 + version = "0.18.2" 938 + source = "registry+https://github.com/rust-lang/crates.io-index" 939 + checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" 940 + dependencies = [ 941 + "gdk-sys", 942 + "glib-sys", 943 + "gobject-sys", 944 + "libc", 945 + "pkg-config", 946 + "system-deps", 947 + ] 948 + 949 + [[package]] 950 + name = "gdkx11" 951 + version = "0.18.2" 952 + source = "registry+https://github.com/rust-lang/crates.io-index" 953 + checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" 954 + dependencies = [ 955 + "gdk", 956 + "gdkx11-sys", 957 + "gio", 958 + "glib", 959 + "libc", 960 + "x11", 961 + ] 962 + 963 + [[package]] 964 + name = "gdkx11-sys" 965 + version = "0.18.2" 966 + source = "registry+https://github.com/rust-lang/crates.io-index" 967 + checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" 968 + dependencies = [ 969 + "gdk-sys", 970 + "glib-sys", 971 + "libc", 972 + "system-deps", 973 + "x11", 974 + ] 975 + 976 + [[package]] 977 + name = "generic-array" 978 + version = "0.14.7" 979 + source = "registry+https://github.com/rust-lang/crates.io-index" 980 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 981 + dependencies = [ 982 + "typenum", 983 + "version_check", 984 + ] 985 + 986 + [[package]] 987 + name = "getrandom" 988 + version = "0.1.16" 989 + source = "registry+https://github.com/rust-lang/crates.io-index" 990 + checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 991 + dependencies = [ 992 + "cfg-if", 993 + "libc", 994 + "wasi 0.9.0+wasi-snapshot-preview1", 995 + ] 996 + 997 + [[package]] 998 + name = "getrandom" 999 + version = "0.2.16" 1000 + source = "registry+https://github.com/rust-lang/crates.io-index" 1001 + checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 1002 + dependencies = [ 1003 + "cfg-if", 1004 + "libc", 1005 + "wasi 0.11.1+wasi-snapshot-preview1", 1006 + ] 1007 + 1008 + [[package]] 1009 + name = "getrandom" 1010 + version = "0.3.4" 1011 + source = "registry+https://github.com/rust-lang/crates.io-index" 1012 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 1013 + dependencies = [ 1014 + "cfg-if", 1015 + "libc", 1016 + "r-efi", 1017 + "wasip2", 1018 + ] 1019 + 1020 + [[package]] 1021 + name = "gio" 1022 + version = "0.18.4" 1023 + source = "registry+https://github.com/rust-lang/crates.io-index" 1024 + checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" 1025 + dependencies = [ 1026 + "futures-channel", 1027 + "futures-core", 1028 + "futures-io", 1029 + "futures-util", 1030 + "gio-sys", 1031 + "glib", 1032 + "libc", 1033 + "once_cell", 1034 + "pin-project-lite", 1035 + "smallvec", 1036 + "thiserror 1.0.69", 1037 + ] 1038 + 1039 + [[package]] 1040 + name = "gio-sys" 1041 + version = "0.18.1" 1042 + source = "registry+https://github.com/rust-lang/crates.io-index" 1043 + checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" 1044 + dependencies = [ 1045 + "glib-sys", 1046 + "gobject-sys", 1047 + "libc", 1048 + "system-deps", 1049 + "winapi", 1050 + ] 1051 + 1052 + [[package]] 1053 + name = "glib" 1054 + version = "0.18.5" 1055 + source = "registry+https://github.com/rust-lang/crates.io-index" 1056 + checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" 1057 + dependencies = [ 1058 + "bitflags 2.10.0", 1059 + "futures-channel", 1060 + "futures-core", 1061 + "futures-executor", 1062 + "futures-task", 1063 + "futures-util", 1064 + "gio-sys", 1065 + "glib-macros", 1066 + "glib-sys", 1067 + "gobject-sys", 1068 + "libc", 1069 + "memchr", 1070 + "once_cell", 1071 + "smallvec", 1072 + "thiserror 1.0.69", 1073 + ] 1074 + 1075 + [[package]] 1076 + name = "glib-macros" 1077 + version = "0.18.5" 1078 + source = "registry+https://github.com/rust-lang/crates.io-index" 1079 + checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" 1080 + dependencies = [ 1081 + "heck 0.4.1", 1082 + "proc-macro-crate 2.0.2", 1083 + "proc-macro-error", 1084 + "proc-macro2", 1085 + "quote", 1086 + "syn 2.0.114", 1087 + ] 1088 + 1089 + [[package]] 1090 + name = "glib-sys" 1091 + version = "0.18.1" 1092 + source = "registry+https://github.com/rust-lang/crates.io-index" 1093 + checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" 1094 + dependencies = [ 1095 + "libc", 1096 + "system-deps", 1097 + ] 1098 + 1099 + [[package]] 1100 + name = "glob" 1101 + version = "0.3.3" 1102 + source = "registry+https://github.com/rust-lang/crates.io-index" 1103 + checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1104 + 1105 + [[package]] 1106 + name = "gobject-sys" 1107 + version = "0.18.0" 1108 + source = "registry+https://github.com/rust-lang/crates.io-index" 1109 + checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" 1110 + dependencies = [ 1111 + "glib-sys", 1112 + "libc", 1113 + "system-deps", 1114 + ] 1115 + 1116 + [[package]] 1117 + name = "gtk" 1118 + version = "0.18.2" 1119 + source = "registry+https://github.com/rust-lang/crates.io-index" 1120 + checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" 1121 + dependencies = [ 1122 + "atk", 1123 + "cairo-rs", 1124 + "field-offset", 1125 + "futures-channel", 1126 + "gdk", 1127 + "gdk-pixbuf", 1128 + "gio", 1129 + "glib", 1130 + "gtk-sys", 1131 + "gtk3-macros", 1132 + "libc", 1133 + "pango", 1134 + "pkg-config", 1135 + ] 1136 + 1137 + [[package]] 1138 + name = "gtk-sys" 1139 + version = "0.18.2" 1140 + source = "registry+https://github.com/rust-lang/crates.io-index" 1141 + checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" 1142 + dependencies = [ 1143 + "atk-sys", 1144 + "cairo-sys-rs", 1145 + "gdk-pixbuf-sys", 1146 + "gdk-sys", 1147 + "gio-sys", 1148 + "glib-sys", 1149 + "gobject-sys", 1150 + "libc", 1151 + "pango-sys", 1152 + "system-deps", 1153 + ] 1154 + 1155 + [[package]] 1156 + name = "gtk3-macros" 1157 + version = "0.18.2" 1158 + source = "registry+https://github.com/rust-lang/crates.io-index" 1159 + checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" 1160 + dependencies = [ 1161 + "proc-macro-crate 1.3.1", 1162 + "proc-macro-error", 1163 + "proc-macro2", 1164 + "quote", 1165 + "syn 2.0.114", 1166 + ] 1167 + 1168 + [[package]] 1169 + name = "hashbrown" 1170 + version = "0.12.3" 1171 + source = "registry+https://github.com/rust-lang/crates.io-index" 1172 + checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 1173 + 1174 + [[package]] 1175 + name = "hashbrown" 1176 + version = "0.14.5" 1177 + source = "registry+https://github.com/rust-lang/crates.io-index" 1178 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1179 + dependencies = [ 1180 + "ahash", 1181 + ] 1182 + 1183 + [[package]] 1184 + name = "hashbrown" 1185 + version = "0.16.1" 1186 + source = "registry+https://github.com/rust-lang/crates.io-index" 1187 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 1188 + 1189 + [[package]] 1190 + name = "hashlink" 1191 + version = "0.9.1" 1192 + source = "registry+https://github.com/rust-lang/crates.io-index" 1193 + checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" 1194 + dependencies = [ 1195 + "hashbrown 0.14.5", 1196 + ] 1197 + 1198 + [[package]] 1199 + name = "heck" 1200 + version = "0.4.1" 1201 + source = "registry+https://github.com/rust-lang/crates.io-index" 1202 + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1203 + 1204 + [[package]] 1205 + name = "heck" 1206 + version = "0.5.0" 1207 + source = "registry+https://github.com/rust-lang/crates.io-index" 1208 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1209 + 1210 + [[package]] 1211 + name = "hex" 1212 + version = "0.4.3" 1213 + source = "registry+https://github.com/rust-lang/crates.io-index" 1214 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1215 + 1216 + [[package]] 1217 + name = "html5ever" 1218 + version = "0.29.1" 1219 + source = "registry+https://github.com/rust-lang/crates.io-index" 1220 + checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" 1221 + dependencies = [ 1222 + "log", 1223 + "mac", 1224 + "markup5ever", 1225 + "match_token", 1226 + ] 1227 + 1228 + [[package]] 1229 + name = "http" 1230 + version = "1.4.0" 1231 + source = "registry+https://github.com/rust-lang/crates.io-index" 1232 + checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 1233 + dependencies = [ 1234 + "bytes", 1235 + "itoa", 1236 + ] 1237 + 1238 + [[package]] 1239 + name = "http-body" 1240 + version = "1.0.1" 1241 + source = "registry+https://github.com/rust-lang/crates.io-index" 1242 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1243 + dependencies = [ 1244 + "bytes", 1245 + "http", 1246 + ] 1247 + 1248 + [[package]] 1249 + name = "http-body-util" 1250 + version = "0.1.3" 1251 + source = "registry+https://github.com/rust-lang/crates.io-index" 1252 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 1253 + dependencies = [ 1254 + "bytes", 1255 + "futures-core", 1256 + "http", 1257 + "http-body", 1258 + "pin-project-lite", 1259 + ] 1260 + 1261 + [[package]] 1262 + name = "httparse" 1263 + version = "1.10.1" 1264 + source = "registry+https://github.com/rust-lang/crates.io-index" 1265 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1266 + 1267 + [[package]] 1268 + name = "hyper" 1269 + version = "1.8.1" 1270 + source = "registry+https://github.com/rust-lang/crates.io-index" 1271 + checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 1272 + dependencies = [ 1273 + "atomic-waker", 1274 + "bytes", 1275 + "futures-channel", 1276 + "futures-core", 1277 + "http", 1278 + "http-body", 1279 + "httparse", 1280 + "itoa", 1281 + "pin-project-lite", 1282 + "pin-utils", 1283 + "smallvec", 1284 + "tokio", 1285 + "want", 1286 + ] 1287 + 1288 + [[package]] 1289 + name = "hyper-util" 1290 + version = "0.1.19" 1291 + source = "registry+https://github.com/rust-lang/crates.io-index" 1292 + checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" 1293 + dependencies = [ 1294 + "base64 0.22.1", 1295 + "bytes", 1296 + "futures-channel", 1297 + "futures-core", 1298 + "futures-util", 1299 + "http", 1300 + "http-body", 1301 + "hyper", 1302 + "ipnet", 1303 + "libc", 1304 + "percent-encoding", 1305 + "pin-project-lite", 1306 + "socket2", 1307 + "tokio", 1308 + "tower-service", 1309 + "tracing", 1310 + ] 1311 + 1312 + [[package]] 1313 + name = "iana-time-zone" 1314 + version = "0.1.64" 1315 + source = "registry+https://github.com/rust-lang/crates.io-index" 1316 + checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 1317 + dependencies = [ 1318 + "android_system_properties", 1319 + "core-foundation-sys", 1320 + "iana-time-zone-haiku", 1321 + "js-sys", 1322 + "log", 1323 + "wasm-bindgen", 1324 + "windows-core 0.62.2", 1325 + ] 1326 + 1327 + [[package]] 1328 + name = "iana-time-zone-haiku" 1329 + version = "0.1.2" 1330 + source = "registry+https://github.com/rust-lang/crates.io-index" 1331 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1332 + dependencies = [ 1333 + "cc", 1334 + ] 1335 + 1336 + [[package]] 1337 + name = "ico" 1338 + version = "0.4.0" 1339 + source = "registry+https://github.com/rust-lang/crates.io-index" 1340 + checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" 1341 + dependencies = [ 1342 + "byteorder", 1343 + "png", 1344 + ] 1345 + 1346 + [[package]] 1347 + name = "icu_collections" 1348 + version = "2.1.1" 1349 + source = "registry+https://github.com/rust-lang/crates.io-index" 1350 + checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 1351 + dependencies = [ 1352 + "displaydoc", 1353 + "potential_utf", 1354 + "yoke", 1355 + "zerofrom", 1356 + "zerovec", 1357 + ] 1358 + 1359 + [[package]] 1360 + name = "icu_locale_core" 1361 + version = "2.1.1" 1362 + source = "registry+https://github.com/rust-lang/crates.io-index" 1363 + checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 1364 + dependencies = [ 1365 + "displaydoc", 1366 + "litemap", 1367 + "tinystr", 1368 + "writeable", 1369 + "zerovec", 1370 + ] 1371 + 1372 + [[package]] 1373 + name = "icu_normalizer" 1374 + version = "2.1.1" 1375 + source = "registry+https://github.com/rust-lang/crates.io-index" 1376 + checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 1377 + dependencies = [ 1378 + "icu_collections", 1379 + "icu_normalizer_data", 1380 + "icu_properties", 1381 + "icu_provider", 1382 + "smallvec", 1383 + "zerovec", 1384 + ] 1385 + 1386 + [[package]] 1387 + name = "icu_normalizer_data" 1388 + version = "2.1.1" 1389 + source = "registry+https://github.com/rust-lang/crates.io-index" 1390 + checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 1391 + 1392 + [[package]] 1393 + name = "icu_properties" 1394 + version = "2.1.2" 1395 + source = "registry+https://github.com/rust-lang/crates.io-index" 1396 + checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" 1397 + dependencies = [ 1398 + "icu_collections", 1399 + "icu_locale_core", 1400 + "icu_properties_data", 1401 + "icu_provider", 1402 + "zerotrie", 1403 + "zerovec", 1404 + ] 1405 + 1406 + [[package]] 1407 + name = "icu_properties_data" 1408 + version = "2.1.2" 1409 + source = "registry+https://github.com/rust-lang/crates.io-index" 1410 + checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" 1411 + 1412 + [[package]] 1413 + name = "icu_provider" 1414 + version = "2.1.1" 1415 + source = "registry+https://github.com/rust-lang/crates.io-index" 1416 + checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 1417 + dependencies = [ 1418 + "displaydoc", 1419 + "icu_locale_core", 1420 + "writeable", 1421 + "yoke", 1422 + "zerofrom", 1423 + "zerotrie", 1424 + "zerovec", 1425 + ] 1426 + 1427 + [[package]] 1428 + name = "ident_case" 1429 + version = "1.0.1" 1430 + source = "registry+https://github.com/rust-lang/crates.io-index" 1431 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1432 + 1433 + [[package]] 1434 + name = "idna" 1435 + version = "1.1.0" 1436 + source = "registry+https://github.com/rust-lang/crates.io-index" 1437 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 1438 + dependencies = [ 1439 + "idna_adapter", 1440 + "smallvec", 1441 + "utf8_iter", 1442 + ] 1443 + 1444 + [[package]] 1445 + name = "idna_adapter" 1446 + version = "1.2.1" 1447 + source = "registry+https://github.com/rust-lang/crates.io-index" 1448 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1449 + dependencies = [ 1450 + "icu_normalizer", 1451 + "icu_properties", 1452 + ] 1453 + 1454 + [[package]] 1455 + name = "indexmap" 1456 + version = "1.9.3" 1457 + source = "registry+https://github.com/rust-lang/crates.io-index" 1458 + checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 1459 + dependencies = [ 1460 + "autocfg", 1461 + "hashbrown 0.12.3", 1462 + "serde", 1463 + ] 1464 + 1465 + [[package]] 1466 + name = "indexmap" 1467 + version = "2.13.0" 1468 + source = "registry+https://github.com/rust-lang/crates.io-index" 1469 + checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" 1470 + dependencies = [ 1471 + "equivalent", 1472 + "hashbrown 0.16.1", 1473 + "serde", 1474 + "serde_core", 1475 + ] 1476 + 1477 + [[package]] 1478 + name = "infer" 1479 + version = "0.19.0" 1480 + source = "registry+https://github.com/rust-lang/crates.io-index" 1481 + checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" 1482 + dependencies = [ 1483 + "cfb", 1484 + ] 1485 + 1486 + [[package]] 1487 + name = "ipnet" 1488 + version = "2.11.0" 1489 + source = "registry+https://github.com/rust-lang/crates.io-index" 1490 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1491 + 1492 + [[package]] 1493 + name = "iri-string" 1494 + version = "0.7.10" 1495 + source = "registry+https://github.com/rust-lang/crates.io-index" 1496 + checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" 1497 + dependencies = [ 1498 + "memchr", 1499 + "serde", 1500 + ] 1501 + 1502 + [[package]] 1503 + name = "is-docker" 1504 + version = "0.2.0" 1505 + source = "registry+https://github.com/rust-lang/crates.io-index" 1506 + checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" 1507 + dependencies = [ 1508 + "once_cell", 1509 + ] 1510 + 1511 + [[package]] 1512 + name = "is-wsl" 1513 + version = "0.4.0" 1514 + source = "registry+https://github.com/rust-lang/crates.io-index" 1515 + checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" 1516 + dependencies = [ 1517 + "is-docker", 1518 + "once_cell", 1519 + ] 1520 + 1521 + [[package]] 1522 + name = "itoa" 1523 + version = "1.0.17" 1524 + source = "registry+https://github.com/rust-lang/crates.io-index" 1525 + checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 1526 + 1527 + [[package]] 1528 + name = "javascriptcore-rs" 1529 + version = "1.1.2" 1530 + source = "registry+https://github.com/rust-lang/crates.io-index" 1531 + checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" 1532 + dependencies = [ 1533 + "bitflags 1.3.2", 1534 + "glib", 1535 + "javascriptcore-rs-sys", 1536 + ] 1537 + 1538 + [[package]] 1539 + name = "javascriptcore-rs-sys" 1540 + version = "1.1.1" 1541 + source = "registry+https://github.com/rust-lang/crates.io-index" 1542 + checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" 1543 + dependencies = [ 1544 + "glib-sys", 1545 + "gobject-sys", 1546 + "libc", 1547 + "system-deps", 1548 + ] 1549 + 1550 + [[package]] 1551 + name = "jni" 1552 + version = "0.21.1" 1553 + source = "registry+https://github.com/rust-lang/crates.io-index" 1554 + checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 1555 + dependencies = [ 1556 + "cesu8", 1557 + "cfg-if", 1558 + "combine", 1559 + "jni-sys", 1560 + "log", 1561 + "thiserror 1.0.69", 1562 + "walkdir", 1563 + "windows-sys 0.45.0", 1564 + ] 1565 + 1566 + [[package]] 1567 + name = "jni-sys" 1568 + version = "0.3.0" 1569 + source = "registry+https://github.com/rust-lang/crates.io-index" 1570 + checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 1571 + 1572 + [[package]] 1573 + name = "js-sys" 1574 + version = "0.3.83" 1575 + source = "registry+https://github.com/rust-lang/crates.io-index" 1576 + checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" 1577 + dependencies = [ 1578 + "once_cell", 1579 + "wasm-bindgen", 1580 + ] 1581 + 1582 + [[package]] 1583 + name = "json-patch" 1584 + version = "3.0.1" 1585 + source = "registry+https://github.com/rust-lang/crates.io-index" 1586 + checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" 1587 + dependencies = [ 1588 + "jsonptr", 1589 + "serde", 1590 + "serde_json", 1591 + "thiserror 1.0.69", 1592 + ] 1593 + 1594 + [[package]] 1595 + name = "jsonptr" 1596 + version = "0.6.3" 1597 + source = "registry+https://github.com/rust-lang/crates.io-index" 1598 + checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" 1599 + dependencies = [ 1600 + "serde", 1601 + "serde_json", 1602 + ] 1603 + 1604 + [[package]] 1605 + name = "keyboard-types" 1606 + version = "0.7.0" 1607 + source = "registry+https://github.com/rust-lang/crates.io-index" 1608 + checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" 1609 + dependencies = [ 1610 + "bitflags 2.10.0", 1611 + "serde", 1612 + "unicode-segmentation", 1613 + ] 1614 + 1615 + [[package]] 1616 + name = "kuchikiki" 1617 + version = "0.8.8-speedreader" 1618 + source = "registry+https://github.com/rust-lang/crates.io-index" 1619 + checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" 1620 + dependencies = [ 1621 + "cssparser", 1622 + "html5ever", 1623 + "indexmap 2.13.0", 1624 + "selectors", 1625 + ] 1626 + 1627 + [[package]] 1628 + name = "lazy_static" 1629 + version = "1.5.0" 1630 + source = "registry+https://github.com/rust-lang/crates.io-index" 1631 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1632 + 1633 + [[package]] 1634 + name = "libappindicator" 1635 + version = "0.9.0" 1636 + source = "registry+https://github.com/rust-lang/crates.io-index" 1637 + checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" 1638 + dependencies = [ 1639 + "glib", 1640 + "gtk", 1641 + "gtk-sys", 1642 + "libappindicator-sys", 1643 + "log", 1644 + ] 1645 + 1646 + [[package]] 1647 + name = "libappindicator-sys" 1648 + version = "0.9.0" 1649 + source = "registry+https://github.com/rust-lang/crates.io-index" 1650 + checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" 1651 + dependencies = [ 1652 + "gtk-sys", 1653 + "libloading", 1654 + "once_cell", 1655 + ] 1656 + 1657 + [[package]] 1658 + name = "libc" 1659 + version = "0.2.180" 1660 + source = "registry+https://github.com/rust-lang/crates.io-index" 1661 + checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" 1662 + 1663 + [[package]] 1664 + name = "libloading" 1665 + version = "0.7.4" 1666 + source = "registry+https://github.com/rust-lang/crates.io-index" 1667 + checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 1668 + dependencies = [ 1669 + "cfg-if", 1670 + "winapi", 1671 + ] 1672 + 1673 + [[package]] 1674 + name = "libredox" 1675 + version = "0.1.12" 1676 + source = "registry+https://github.com/rust-lang/crates.io-index" 1677 + checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" 1678 + dependencies = [ 1679 + "bitflags 2.10.0", 1680 + "libc", 1681 + ] 1682 + 1683 + [[package]] 1684 + name = "libsqlite3-sys" 1685 + version = "0.30.1" 1686 + source = "registry+https://github.com/rust-lang/crates.io-index" 1687 + checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" 1688 + dependencies = [ 1689 + "cc", 1690 + "pkg-config", 1691 + "vcpkg", 1692 + ] 1693 + 1694 + [[package]] 1695 + name = "linux-raw-sys" 1696 + version = "0.11.0" 1697 + source = "registry+https://github.com/rust-lang/crates.io-index" 1698 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1699 + 1700 + [[package]] 1701 + name = "litemap" 1702 + version = "0.8.1" 1703 + source = "registry+https://github.com/rust-lang/crates.io-index" 1704 + checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 1705 + 1706 + [[package]] 1707 + name = "lock_api" 1708 + version = "0.4.14" 1709 + source = "registry+https://github.com/rust-lang/crates.io-index" 1710 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1711 + dependencies = [ 1712 + "scopeguard", 1713 + ] 1714 + 1715 + [[package]] 1716 + name = "log" 1717 + version = "0.4.29" 1718 + source = "registry+https://github.com/rust-lang/crates.io-index" 1719 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 1720 + 1721 + [[package]] 1722 + name = "mac" 1723 + version = "0.1.1" 1724 + source = "registry+https://github.com/rust-lang/crates.io-index" 1725 + checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 1726 + 1727 + [[package]] 1728 + name = "markup5ever" 1729 + version = "0.14.1" 1730 + source = "registry+https://github.com/rust-lang/crates.io-index" 1731 + checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" 1732 + dependencies = [ 1733 + "log", 1734 + "phf 0.11.3", 1735 + "phf_codegen 0.11.3", 1736 + "string_cache", 1737 + "string_cache_codegen", 1738 + "tendril", 1739 + ] 1740 + 1741 + [[package]] 1742 + name = "match_token" 1743 + version = "0.1.0" 1744 + source = "registry+https://github.com/rust-lang/crates.io-index" 1745 + checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" 1746 + dependencies = [ 1747 + "proc-macro2", 1748 + "quote", 1749 + "syn 2.0.114", 1750 + ] 1751 + 1752 + [[package]] 1753 + name = "matches" 1754 + version = "0.1.10" 1755 + source = "registry+https://github.com/rust-lang/crates.io-index" 1756 + checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" 1757 + 1758 + [[package]] 1759 + name = "memchr" 1760 + version = "2.7.6" 1761 + source = "registry+https://github.com/rust-lang/crates.io-index" 1762 + checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1763 + 1764 + [[package]] 1765 + name = "memoffset" 1766 + version = "0.9.1" 1767 + source = "registry+https://github.com/rust-lang/crates.io-index" 1768 + checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 1769 + dependencies = [ 1770 + "autocfg", 1771 + ] 1772 + 1773 + [[package]] 1774 + name = "mime" 1775 + version = "0.3.17" 1776 + source = "registry+https://github.com/rust-lang/crates.io-index" 1777 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1778 + 1779 + [[package]] 1780 + name = "mime_guess" 1781 + version = "2.0.5" 1782 + source = "registry+https://github.com/rust-lang/crates.io-index" 1783 + checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 1784 + dependencies = [ 1785 + "mime", 1786 + "unicase", 1787 + ] 1788 + 1789 + [[package]] 1790 + name = "miniz_oxide" 1791 + version = "0.8.9" 1792 + source = "registry+https://github.com/rust-lang/crates.io-index" 1793 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1794 + dependencies = [ 1795 + "adler2", 1796 + "simd-adler32", 1797 + ] 1798 + 1799 + [[package]] 1800 + name = "mio" 1801 + version = "1.1.1" 1802 + source = "registry+https://github.com/rust-lang/crates.io-index" 1803 + checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" 1804 + dependencies = [ 1805 + "libc", 1806 + "wasi 0.11.1+wasi-snapshot-preview1", 1807 + "windows-sys 0.61.2", 1808 + ] 1809 + 1810 + [[package]] 1811 + name = "muda" 1812 + version = "0.17.1" 1813 + source = "registry+https://github.com/rust-lang/crates.io-index" 1814 + checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" 1815 + dependencies = [ 1816 + "crossbeam-channel", 1817 + "dpi", 1818 + "gtk", 1819 + "keyboard-types", 1820 + "objc2", 1821 + "objc2-app-kit", 1822 + "objc2-core-foundation", 1823 + "objc2-foundation", 1824 + "once_cell", 1825 + "png", 1826 + "serde", 1827 + "thiserror 2.0.17", 1828 + "windows-sys 0.60.2", 1829 + ] 1830 + 1831 + [[package]] 1832 + name = "ndk" 1833 + version = "0.9.0" 1834 + source = "registry+https://github.com/rust-lang/crates.io-index" 1835 + checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" 1836 + dependencies = [ 1837 + "bitflags 2.10.0", 1838 + "jni-sys", 1839 + "log", 1840 + "ndk-sys", 1841 + "num_enum", 1842 + "raw-window-handle", 1843 + "thiserror 1.0.69", 1844 + ] 1845 + 1846 + [[package]] 1847 + name = "ndk-context" 1848 + version = "0.1.1" 1849 + source = "registry+https://github.com/rust-lang/crates.io-index" 1850 + checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 1851 + 1852 + [[package]] 1853 + name = "ndk-sys" 1854 + version = "0.6.0+11769913" 1855 + source = "registry+https://github.com/rust-lang/crates.io-index" 1856 + checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" 1857 + dependencies = [ 1858 + "jni-sys", 1859 + ] 1860 + 1861 + [[package]] 1862 + name = "new_debug_unreachable" 1863 + version = "1.0.6" 1864 + source = "registry+https://github.com/rust-lang/crates.io-index" 1865 + checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 1866 + 1867 + [[package]] 1868 + name = "nodrop" 1869 + version = "0.1.14" 1870 + source = "registry+https://github.com/rust-lang/crates.io-index" 1871 + checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 1872 + 1873 + [[package]] 1874 + name = "num-conv" 1875 + version = "0.1.0" 1876 + source = "registry+https://github.com/rust-lang/crates.io-index" 1877 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1878 + 1879 + [[package]] 1880 + name = "num-traits" 1881 + version = "0.2.19" 1882 + source = "registry+https://github.com/rust-lang/crates.io-index" 1883 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1884 + dependencies = [ 1885 + "autocfg", 1886 + ] 1887 + 1888 + [[package]] 1889 + name = "num_enum" 1890 + version = "0.7.5" 1891 + source = "registry+https://github.com/rust-lang/crates.io-index" 1892 + checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" 1893 + dependencies = [ 1894 + "num_enum_derive", 1895 + "rustversion", 1896 + ] 1897 + 1898 + [[package]] 1899 + name = "num_enum_derive" 1900 + version = "0.7.5" 1901 + source = "registry+https://github.com/rust-lang/crates.io-index" 1902 + checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" 1903 + dependencies = [ 1904 + "proc-macro-crate 3.4.0", 1905 + "proc-macro2", 1906 + "quote", 1907 + "syn 2.0.114", 1908 + ] 1909 + 1910 + [[package]] 1911 + name = "objc2" 1912 + version = "0.6.3" 1913 + source = "registry+https://github.com/rust-lang/crates.io-index" 1914 + checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" 1915 + dependencies = [ 1916 + "objc2-encode", 1917 + "objc2-exception-helper", 1918 + ] 1919 + 1920 + [[package]] 1921 + name = "objc2-app-kit" 1922 + version = "0.3.2" 1923 + source = "registry+https://github.com/rust-lang/crates.io-index" 1924 + checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" 1925 + dependencies = [ 1926 + "bitflags 2.10.0", 1927 + "block2", 1928 + "libc", 1929 + "objc2", 1930 + "objc2-cloud-kit", 1931 + "objc2-core-data", 1932 + "objc2-core-foundation", 1933 + "objc2-core-graphics", 1934 + "objc2-core-image", 1935 + "objc2-core-text", 1936 + "objc2-core-video", 1937 + "objc2-foundation", 1938 + "objc2-quartz-core", 1939 + ] 1940 + 1941 + [[package]] 1942 + name = "objc2-cloud-kit" 1943 + version = "0.3.2" 1944 + source = "registry+https://github.com/rust-lang/crates.io-index" 1945 + checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" 1946 + dependencies = [ 1947 + "bitflags 2.10.0", 1948 + "objc2", 1949 + "objc2-foundation", 1950 + ] 1951 + 1952 + [[package]] 1953 + name = "objc2-core-data" 1954 + version = "0.3.2" 1955 + source = "registry+https://github.com/rust-lang/crates.io-index" 1956 + checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" 1957 + dependencies = [ 1958 + "bitflags 2.10.0", 1959 + "objc2", 1960 + "objc2-foundation", 1961 + ] 1962 + 1963 + [[package]] 1964 + name = "objc2-core-foundation" 1965 + version = "0.3.2" 1966 + source = "registry+https://github.com/rust-lang/crates.io-index" 1967 + checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" 1968 + dependencies = [ 1969 + "bitflags 2.10.0", 1970 + "dispatch2", 1971 + "objc2", 1972 + ] 1973 + 1974 + [[package]] 1975 + name = "objc2-core-graphics" 1976 + version = "0.3.2" 1977 + source = "registry+https://github.com/rust-lang/crates.io-index" 1978 + checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" 1979 + dependencies = [ 1980 + "bitflags 2.10.0", 1981 + "dispatch2", 1982 + "objc2", 1983 + "objc2-core-foundation", 1984 + "objc2-io-surface", 1985 + ] 1986 + 1987 + [[package]] 1988 + name = "objc2-core-image" 1989 + version = "0.3.2" 1990 + source = "registry+https://github.com/rust-lang/crates.io-index" 1991 + checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" 1992 + dependencies = [ 1993 + "objc2", 1994 + "objc2-foundation", 1995 + ] 1996 + 1997 + [[package]] 1998 + name = "objc2-core-text" 1999 + version = "0.3.2" 2000 + source = "registry+https://github.com/rust-lang/crates.io-index" 2001 + checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" 2002 + dependencies = [ 2003 + "bitflags 2.10.0", 2004 + "objc2", 2005 + "objc2-core-foundation", 2006 + "objc2-core-graphics", 2007 + ] 2008 + 2009 + [[package]] 2010 + name = "objc2-core-video" 2011 + version = "0.3.2" 2012 + source = "registry+https://github.com/rust-lang/crates.io-index" 2013 + checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" 2014 + dependencies = [ 2015 + "bitflags 2.10.0", 2016 + "objc2", 2017 + "objc2-core-foundation", 2018 + "objc2-core-graphics", 2019 + "objc2-io-surface", 2020 + ] 2021 + 2022 + [[package]] 2023 + name = "objc2-encode" 2024 + version = "4.1.0" 2025 + source = "registry+https://github.com/rust-lang/crates.io-index" 2026 + checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 2027 + 2028 + [[package]] 2029 + name = "objc2-exception-helper" 2030 + version = "0.1.1" 2031 + source = "registry+https://github.com/rust-lang/crates.io-index" 2032 + checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" 2033 + dependencies = [ 2034 + "cc", 2035 + ] 2036 + 2037 + [[package]] 2038 + name = "objc2-foundation" 2039 + version = "0.3.2" 2040 + source = "registry+https://github.com/rust-lang/crates.io-index" 2041 + checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" 2042 + dependencies = [ 2043 + "bitflags 2.10.0", 2044 + "block2", 2045 + "libc", 2046 + "objc2", 2047 + "objc2-core-foundation", 2048 + ] 2049 + 2050 + [[package]] 2051 + name = "objc2-io-surface" 2052 + version = "0.3.2" 2053 + source = "registry+https://github.com/rust-lang/crates.io-index" 2054 + checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" 2055 + dependencies = [ 2056 + "bitflags 2.10.0", 2057 + "objc2", 2058 + "objc2-core-foundation", 2059 + ] 2060 + 2061 + [[package]] 2062 + name = "objc2-javascript-core" 2063 + version = "0.3.2" 2064 + source = "registry+https://github.com/rust-lang/crates.io-index" 2065 + checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" 2066 + dependencies = [ 2067 + "objc2", 2068 + "objc2-core-foundation", 2069 + ] 2070 + 2071 + [[package]] 2072 + name = "objc2-quartz-core" 2073 + version = "0.3.2" 2074 + source = "registry+https://github.com/rust-lang/crates.io-index" 2075 + checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" 2076 + dependencies = [ 2077 + "bitflags 2.10.0", 2078 + "objc2", 2079 + "objc2-core-foundation", 2080 + "objc2-foundation", 2081 + ] 2082 + 2083 + [[package]] 2084 + name = "objc2-security" 2085 + version = "0.3.2" 2086 + source = "registry+https://github.com/rust-lang/crates.io-index" 2087 + checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" 2088 + dependencies = [ 2089 + "bitflags 2.10.0", 2090 + "objc2", 2091 + "objc2-core-foundation", 2092 + ] 2093 + 2094 + [[package]] 2095 + name = "objc2-ui-kit" 2096 + version = "0.3.2" 2097 + source = "registry+https://github.com/rust-lang/crates.io-index" 2098 + checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" 2099 + dependencies = [ 2100 + "bitflags 2.10.0", 2101 + "objc2", 2102 + "objc2-core-foundation", 2103 + "objc2-foundation", 2104 + ] 2105 + 2106 + [[package]] 2107 + name = "objc2-web-kit" 2108 + version = "0.3.2" 2109 + source = "registry+https://github.com/rust-lang/crates.io-index" 2110 + checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" 2111 + dependencies = [ 2112 + "bitflags 2.10.0", 2113 + "block2", 2114 + "objc2", 2115 + "objc2-app-kit", 2116 + "objc2-core-foundation", 2117 + "objc2-foundation", 2118 + "objc2-javascript-core", 2119 + "objc2-security", 2120 + ] 2121 + 2122 + [[package]] 2123 + name = "once_cell" 2124 + version = "1.21.3" 2125 + source = "registry+https://github.com/rust-lang/crates.io-index" 2126 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 2127 + 2128 + [[package]] 2129 + name = "open" 2130 + version = "5.3.3" 2131 + source = "registry+https://github.com/rust-lang/crates.io-index" 2132 + checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" 2133 + dependencies = [ 2134 + "dunce", 2135 + "is-wsl", 2136 + "libc", 2137 + "pathdiff", 2138 + ] 2139 + 2140 + [[package]] 2141 + name = "option-ext" 2142 + version = "0.2.0" 2143 + source = "registry+https://github.com/rust-lang/crates.io-index" 2144 + checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 2145 + 2146 + [[package]] 2147 + name = "os_pipe" 2148 + version = "1.2.3" 2149 + source = "registry+https://github.com/rust-lang/crates.io-index" 2150 + checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" 2151 + dependencies = [ 2152 + "libc", 2153 + "windows-sys 0.61.2", 2154 + ] 2155 + 2156 + [[package]] 2157 + name = "pango" 2158 + version = "0.18.3" 2159 + source = "registry+https://github.com/rust-lang/crates.io-index" 2160 + checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" 2161 + dependencies = [ 2162 + "gio", 2163 + "glib", 2164 + "libc", 2165 + "once_cell", 2166 + "pango-sys", 2167 + ] 2168 + 2169 + [[package]] 2170 + name = "pango-sys" 2171 + version = "0.18.0" 2172 + source = "registry+https://github.com/rust-lang/crates.io-index" 2173 + checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" 2174 + dependencies = [ 2175 + "glib-sys", 2176 + "gobject-sys", 2177 + "libc", 2178 + "system-deps", 2179 + ] 2180 + 2181 + [[package]] 2182 + name = "parking_lot" 2183 + version = "0.12.5" 2184 + source = "registry+https://github.com/rust-lang/crates.io-index" 2185 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 2186 + dependencies = [ 2187 + "lock_api", 2188 + "parking_lot_core", 2189 + ] 2190 + 2191 + [[package]] 2192 + name = "parking_lot_core" 2193 + version = "0.9.12" 2194 + source = "registry+https://github.com/rust-lang/crates.io-index" 2195 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 2196 + dependencies = [ 2197 + "cfg-if", 2198 + "libc", 2199 + "redox_syscall", 2200 + "smallvec", 2201 + "windows-link 0.2.1", 2202 + ] 2203 + 2204 + [[package]] 2205 + name = "pathdiff" 2206 + version = "0.2.3" 2207 + source = "registry+https://github.com/rust-lang/crates.io-index" 2208 + checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" 2209 + 2210 + [[package]] 2211 + name = "peek-tauri" 2212 + version = "0.1.0" 2213 + dependencies = [ 2214 + "chrono", 2215 + "mime_guess", 2216 + "rusqlite", 2217 + "serde", 2218 + "serde_json", 2219 + "tauri", 2220 + "tauri-build", 2221 + "tauri-plugin-shell", 2222 + "tempfile", 2223 + "tokio", 2224 + "url", 2225 + "uuid", 2226 + ] 2227 + 2228 + [[package]] 2229 + name = "percent-encoding" 2230 + version = "2.3.2" 2231 + source = "registry+https://github.com/rust-lang/crates.io-index" 2232 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 2233 + 2234 + [[package]] 2235 + name = "phf" 2236 + version = "0.8.0" 2237 + source = "registry+https://github.com/rust-lang/crates.io-index" 2238 + checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 2239 + dependencies = [ 2240 + "phf_shared 0.8.0", 2241 + ] 2242 + 2243 + [[package]] 2244 + name = "phf" 2245 + version = "0.10.1" 2246 + source = "registry+https://github.com/rust-lang/crates.io-index" 2247 + checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 2248 + dependencies = [ 2249 + "phf_macros 0.10.0", 2250 + "phf_shared 0.10.0", 2251 + "proc-macro-hack", 2252 + ] 2253 + 2254 + [[package]] 2255 + name = "phf" 2256 + version = "0.11.3" 2257 + source = "registry+https://github.com/rust-lang/crates.io-index" 2258 + checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" 2259 + dependencies = [ 2260 + "phf_macros 0.11.3", 2261 + "phf_shared 0.11.3", 2262 + ] 2263 + 2264 + [[package]] 2265 + name = "phf_codegen" 2266 + version = "0.8.0" 2267 + source = "registry+https://github.com/rust-lang/crates.io-index" 2268 + checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 2269 + dependencies = [ 2270 + "phf_generator 0.8.0", 2271 + "phf_shared 0.8.0", 2272 + ] 2273 + 2274 + [[package]] 2275 + name = "phf_codegen" 2276 + version = "0.11.3" 2277 + source = "registry+https://github.com/rust-lang/crates.io-index" 2278 + checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" 2279 + dependencies = [ 2280 + "phf_generator 0.11.3", 2281 + "phf_shared 0.11.3", 2282 + ] 2283 + 2284 + [[package]] 2285 + name = "phf_generator" 2286 + version = "0.8.0" 2287 + source = "registry+https://github.com/rust-lang/crates.io-index" 2288 + checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 2289 + dependencies = [ 2290 + "phf_shared 0.8.0", 2291 + "rand 0.7.3", 2292 + ] 2293 + 2294 + [[package]] 2295 + name = "phf_generator" 2296 + version = "0.10.0" 2297 + source = "registry+https://github.com/rust-lang/crates.io-index" 2298 + checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 2299 + dependencies = [ 2300 + "phf_shared 0.10.0", 2301 + "rand 0.8.5", 2302 + ] 2303 + 2304 + [[package]] 2305 + name = "phf_generator" 2306 + version = "0.11.3" 2307 + source = "registry+https://github.com/rust-lang/crates.io-index" 2308 + checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 2309 + dependencies = [ 2310 + "phf_shared 0.11.3", 2311 + "rand 0.8.5", 2312 + ] 2313 + 2314 + [[package]] 2315 + name = "phf_macros" 2316 + version = "0.10.0" 2317 + source = "registry+https://github.com/rust-lang/crates.io-index" 2318 + checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" 2319 + dependencies = [ 2320 + "phf_generator 0.10.0", 2321 + "phf_shared 0.10.0", 2322 + "proc-macro-hack", 2323 + "proc-macro2", 2324 + "quote", 2325 + "syn 1.0.109", 2326 + ] 2327 + 2328 + [[package]] 2329 + name = "phf_macros" 2330 + version = "0.11.3" 2331 + source = "registry+https://github.com/rust-lang/crates.io-index" 2332 + checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" 2333 + dependencies = [ 2334 + "phf_generator 0.11.3", 2335 + "phf_shared 0.11.3", 2336 + "proc-macro2", 2337 + "quote", 2338 + "syn 2.0.114", 2339 + ] 2340 + 2341 + [[package]] 2342 + name = "phf_shared" 2343 + version = "0.8.0" 2344 + source = "registry+https://github.com/rust-lang/crates.io-index" 2345 + checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 2346 + dependencies = [ 2347 + "siphasher 0.3.11", 2348 + ] 2349 + 2350 + [[package]] 2351 + name = "phf_shared" 2352 + version = "0.10.0" 2353 + source = "registry+https://github.com/rust-lang/crates.io-index" 2354 + checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 2355 + dependencies = [ 2356 + "siphasher 0.3.11", 2357 + ] 2358 + 2359 + [[package]] 2360 + name = "phf_shared" 2361 + version = "0.11.3" 2362 + source = "registry+https://github.com/rust-lang/crates.io-index" 2363 + checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 2364 + dependencies = [ 2365 + "siphasher 1.0.1", 2366 + ] 2367 + 2368 + [[package]] 2369 + name = "pin-project-lite" 2370 + version = "0.2.16" 2371 + source = "registry+https://github.com/rust-lang/crates.io-index" 2372 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 2373 + 2374 + [[package]] 2375 + name = "pin-utils" 2376 + version = "0.1.0" 2377 + source = "registry+https://github.com/rust-lang/crates.io-index" 2378 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 2379 + 2380 + [[package]] 2381 + name = "pkg-config" 2382 + version = "0.3.32" 2383 + source = "registry+https://github.com/rust-lang/crates.io-index" 2384 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 2385 + 2386 + [[package]] 2387 + name = "plist" 2388 + version = "1.8.0" 2389 + source = "registry+https://github.com/rust-lang/crates.io-index" 2390 + checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" 2391 + dependencies = [ 2392 + "base64 0.22.1", 2393 + "indexmap 2.13.0", 2394 + "quick-xml", 2395 + "serde", 2396 + "time", 2397 + ] 2398 + 2399 + [[package]] 2400 + name = "png" 2401 + version = "0.17.16" 2402 + source = "registry+https://github.com/rust-lang/crates.io-index" 2403 + checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" 2404 + dependencies = [ 2405 + "bitflags 1.3.2", 2406 + "crc32fast", 2407 + "fdeflate", 2408 + "flate2", 2409 + "miniz_oxide", 2410 + ] 2411 + 2412 + [[package]] 2413 + name = "potential_utf" 2414 + version = "0.1.4" 2415 + source = "registry+https://github.com/rust-lang/crates.io-index" 2416 + checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 2417 + dependencies = [ 2418 + "zerovec", 2419 + ] 2420 + 2421 + [[package]] 2422 + name = "powerfmt" 2423 + version = "0.2.0" 2424 + source = "registry+https://github.com/rust-lang/crates.io-index" 2425 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2426 + 2427 + [[package]] 2428 + name = "ppv-lite86" 2429 + version = "0.2.21" 2430 + source = "registry+https://github.com/rust-lang/crates.io-index" 2431 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 2432 + dependencies = [ 2433 + "zerocopy", 2434 + ] 2435 + 2436 + [[package]] 2437 + name = "precomputed-hash" 2438 + version = "0.1.1" 2439 + source = "registry+https://github.com/rust-lang/crates.io-index" 2440 + checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 2441 + 2442 + [[package]] 2443 + name = "proc-macro-crate" 2444 + version = "1.3.1" 2445 + source = "registry+https://github.com/rust-lang/crates.io-index" 2446 + checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 2447 + dependencies = [ 2448 + "once_cell", 2449 + "toml_edit 0.19.15", 2450 + ] 2451 + 2452 + [[package]] 2453 + name = "proc-macro-crate" 2454 + version = "2.0.2" 2455 + source = "registry+https://github.com/rust-lang/crates.io-index" 2456 + checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" 2457 + dependencies = [ 2458 + "toml_datetime 0.6.3", 2459 + "toml_edit 0.20.2", 2460 + ] 2461 + 2462 + [[package]] 2463 + name = "proc-macro-crate" 2464 + version = "3.4.0" 2465 + source = "registry+https://github.com/rust-lang/crates.io-index" 2466 + checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" 2467 + dependencies = [ 2468 + "toml_edit 0.23.10+spec-1.0.0", 2469 + ] 2470 + 2471 + [[package]] 2472 + name = "proc-macro-error" 2473 + version = "1.0.4" 2474 + source = "registry+https://github.com/rust-lang/crates.io-index" 2475 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 2476 + dependencies = [ 2477 + "proc-macro-error-attr", 2478 + "proc-macro2", 2479 + "quote", 2480 + "syn 1.0.109", 2481 + "version_check", 2482 + ] 2483 + 2484 + [[package]] 2485 + name = "proc-macro-error-attr" 2486 + version = "1.0.4" 2487 + source = "registry+https://github.com/rust-lang/crates.io-index" 2488 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 2489 + dependencies = [ 2490 + "proc-macro2", 2491 + "quote", 2492 + "version_check", 2493 + ] 2494 + 2495 + [[package]] 2496 + name = "proc-macro-hack" 2497 + version = "0.5.20+deprecated" 2498 + source = "registry+https://github.com/rust-lang/crates.io-index" 2499 + checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 2500 + 2501 + [[package]] 2502 + name = "proc-macro2" 2503 + version = "1.0.105" 2504 + source = "registry+https://github.com/rust-lang/crates.io-index" 2505 + checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" 2506 + dependencies = [ 2507 + "unicode-ident", 2508 + ] 2509 + 2510 + [[package]] 2511 + name = "quick-xml" 2512 + version = "0.38.4" 2513 + source = "registry+https://github.com/rust-lang/crates.io-index" 2514 + checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" 2515 + dependencies = [ 2516 + "memchr", 2517 + ] 2518 + 2519 + [[package]] 2520 + name = "quote" 2521 + version = "1.0.43" 2522 + source = "registry+https://github.com/rust-lang/crates.io-index" 2523 + checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" 2524 + dependencies = [ 2525 + "proc-macro2", 2526 + ] 2527 + 2528 + [[package]] 2529 + name = "r-efi" 2530 + version = "5.3.0" 2531 + source = "registry+https://github.com/rust-lang/crates.io-index" 2532 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 2533 + 2534 + [[package]] 2535 + name = "rand" 2536 + version = "0.7.3" 2537 + source = "registry+https://github.com/rust-lang/crates.io-index" 2538 + checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 2539 + dependencies = [ 2540 + "getrandom 0.1.16", 2541 + "libc", 2542 + "rand_chacha 0.2.2", 2543 + "rand_core 0.5.1", 2544 + "rand_hc", 2545 + "rand_pcg", 2546 + ] 2547 + 2548 + [[package]] 2549 + name = "rand" 2550 + version = "0.8.5" 2551 + source = "registry+https://github.com/rust-lang/crates.io-index" 2552 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 2553 + dependencies = [ 2554 + "libc", 2555 + "rand_chacha 0.3.1", 2556 + "rand_core 0.6.4", 2557 + ] 2558 + 2559 + [[package]] 2560 + name = "rand_chacha" 2561 + version = "0.2.2" 2562 + source = "registry+https://github.com/rust-lang/crates.io-index" 2563 + checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 2564 + dependencies = [ 2565 + "ppv-lite86", 2566 + "rand_core 0.5.1", 2567 + ] 2568 + 2569 + [[package]] 2570 + name = "rand_chacha" 2571 + version = "0.3.1" 2572 + source = "registry+https://github.com/rust-lang/crates.io-index" 2573 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 2574 + dependencies = [ 2575 + "ppv-lite86", 2576 + "rand_core 0.6.4", 2577 + ] 2578 + 2579 + [[package]] 2580 + name = "rand_core" 2581 + version = "0.5.1" 2582 + source = "registry+https://github.com/rust-lang/crates.io-index" 2583 + checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 2584 + dependencies = [ 2585 + "getrandom 0.1.16", 2586 + ] 2587 + 2588 + [[package]] 2589 + name = "rand_core" 2590 + version = "0.6.4" 2591 + source = "registry+https://github.com/rust-lang/crates.io-index" 2592 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 2593 + dependencies = [ 2594 + "getrandom 0.2.16", 2595 + ] 2596 + 2597 + [[package]] 2598 + name = "rand_hc" 2599 + version = "0.2.0" 2600 + source = "registry+https://github.com/rust-lang/crates.io-index" 2601 + checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 2602 + dependencies = [ 2603 + "rand_core 0.5.1", 2604 + ] 2605 + 2606 + [[package]] 2607 + name = "rand_pcg" 2608 + version = "0.2.1" 2609 + source = "registry+https://github.com/rust-lang/crates.io-index" 2610 + checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 2611 + dependencies = [ 2612 + "rand_core 0.5.1", 2613 + ] 2614 + 2615 + [[package]] 2616 + name = "raw-window-handle" 2617 + version = "0.6.2" 2618 + source = "registry+https://github.com/rust-lang/crates.io-index" 2619 + checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" 2620 + 2621 + [[package]] 2622 + name = "redox_syscall" 2623 + version = "0.5.18" 2624 + source = "registry+https://github.com/rust-lang/crates.io-index" 2625 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 2626 + dependencies = [ 2627 + "bitflags 2.10.0", 2628 + ] 2629 + 2630 + [[package]] 2631 + name = "redox_users" 2632 + version = "0.5.2" 2633 + source = "registry+https://github.com/rust-lang/crates.io-index" 2634 + checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" 2635 + dependencies = [ 2636 + "getrandom 0.2.16", 2637 + "libredox", 2638 + "thiserror 2.0.17", 2639 + ] 2640 + 2641 + [[package]] 2642 + name = "ref-cast" 2643 + version = "1.0.25" 2644 + source = "registry+https://github.com/rust-lang/crates.io-index" 2645 + checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" 2646 + dependencies = [ 2647 + "ref-cast-impl", 2648 + ] 2649 + 2650 + [[package]] 2651 + name = "ref-cast-impl" 2652 + version = "1.0.25" 2653 + source = "registry+https://github.com/rust-lang/crates.io-index" 2654 + checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" 2655 + dependencies = [ 2656 + "proc-macro2", 2657 + "quote", 2658 + "syn 2.0.114", 2659 + ] 2660 + 2661 + [[package]] 2662 + name = "regex" 2663 + version = "1.12.2" 2664 + source = "registry+https://github.com/rust-lang/crates.io-index" 2665 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 2666 + dependencies = [ 2667 + "aho-corasick", 2668 + "memchr", 2669 + "regex-automata", 2670 + "regex-syntax", 2671 + ] 2672 + 2673 + [[package]] 2674 + name = "regex-automata" 2675 + version = "0.4.13" 2676 + source = "registry+https://github.com/rust-lang/crates.io-index" 2677 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 2678 + dependencies = [ 2679 + "aho-corasick", 2680 + "memchr", 2681 + "regex-syntax", 2682 + ] 2683 + 2684 + [[package]] 2685 + name = "regex-syntax" 2686 + version = "0.8.8" 2687 + source = "registry+https://github.com/rust-lang/crates.io-index" 2688 + checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 2689 + 2690 + [[package]] 2691 + name = "reqwest" 2692 + version = "0.12.28" 2693 + source = "registry+https://github.com/rust-lang/crates.io-index" 2694 + checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 2695 + dependencies = [ 2696 + "base64 0.22.1", 2697 + "bytes", 2698 + "futures-core", 2699 + "futures-util", 2700 + "http", 2701 + "http-body", 2702 + "http-body-util", 2703 + "hyper", 2704 + "hyper-util", 2705 + "js-sys", 2706 + "log", 2707 + "percent-encoding", 2708 + "pin-project-lite", 2709 + "serde", 2710 + "serde_json", 2711 + "serde_urlencoded", 2712 + "sync_wrapper", 2713 + "tokio", 2714 + "tokio-util", 2715 + "tower", 2716 + "tower-http", 2717 + "tower-service", 2718 + "url", 2719 + "wasm-bindgen", 2720 + "wasm-bindgen-futures", 2721 + "wasm-streams", 2722 + "web-sys", 2723 + ] 2724 + 2725 + [[package]] 2726 + name = "rusqlite" 2727 + version = "0.32.1" 2728 + source = "registry+https://github.com/rust-lang/crates.io-index" 2729 + checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" 2730 + dependencies = [ 2731 + "bitflags 2.10.0", 2732 + "fallible-iterator", 2733 + "fallible-streaming-iterator", 2734 + "hashlink", 2735 + "libsqlite3-sys", 2736 + "smallvec", 2737 + ] 2738 + 2739 + [[package]] 2740 + name = "rustc_version" 2741 + version = "0.4.1" 2742 + source = "registry+https://github.com/rust-lang/crates.io-index" 2743 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 2744 + dependencies = [ 2745 + "semver", 2746 + ] 2747 + 2748 + [[package]] 2749 + name = "rustix" 2750 + version = "1.1.3" 2751 + source = "registry+https://github.com/rust-lang/crates.io-index" 2752 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 2753 + dependencies = [ 2754 + "bitflags 2.10.0", 2755 + "errno", 2756 + "libc", 2757 + "linux-raw-sys", 2758 + "windows-sys 0.61.2", 2759 + ] 2760 + 2761 + [[package]] 2762 + name = "rustversion" 2763 + version = "1.0.22" 2764 + source = "registry+https://github.com/rust-lang/crates.io-index" 2765 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 2766 + 2767 + [[package]] 2768 + name = "ryu" 2769 + version = "1.0.22" 2770 + source = "registry+https://github.com/rust-lang/crates.io-index" 2771 + checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 2772 + 2773 + [[package]] 2774 + name = "same-file" 2775 + version = "1.0.6" 2776 + source = "registry+https://github.com/rust-lang/crates.io-index" 2777 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 2778 + dependencies = [ 2779 + "winapi-util", 2780 + ] 2781 + 2782 + [[package]] 2783 + name = "schemars" 2784 + version = "0.8.22" 2785 + source = "registry+https://github.com/rust-lang/crates.io-index" 2786 + checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" 2787 + dependencies = [ 2788 + "dyn-clone", 2789 + "indexmap 1.9.3", 2790 + "schemars_derive", 2791 + "serde", 2792 + "serde_json", 2793 + "url", 2794 + "uuid", 2795 + ] 2796 + 2797 + [[package]] 2798 + name = "schemars" 2799 + version = "0.9.0" 2800 + source = "registry+https://github.com/rust-lang/crates.io-index" 2801 + checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" 2802 + dependencies = [ 2803 + "dyn-clone", 2804 + "ref-cast", 2805 + "serde", 2806 + "serde_json", 2807 + ] 2808 + 2809 + [[package]] 2810 + name = "schemars" 2811 + version = "1.2.0" 2812 + source = "registry+https://github.com/rust-lang/crates.io-index" 2813 + checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" 2814 + dependencies = [ 2815 + "dyn-clone", 2816 + "ref-cast", 2817 + "serde", 2818 + "serde_json", 2819 + ] 2820 + 2821 + [[package]] 2822 + name = "schemars_derive" 2823 + version = "0.8.22" 2824 + source = "registry+https://github.com/rust-lang/crates.io-index" 2825 + checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" 2826 + dependencies = [ 2827 + "proc-macro2", 2828 + "quote", 2829 + "serde_derive_internals", 2830 + "syn 2.0.114", 2831 + ] 2832 + 2833 + [[package]] 2834 + name = "scopeguard" 2835 + version = "1.2.0" 2836 + source = "registry+https://github.com/rust-lang/crates.io-index" 2837 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2838 + 2839 + [[package]] 2840 + name = "selectors" 2841 + version = "0.24.0" 2842 + source = "registry+https://github.com/rust-lang/crates.io-index" 2843 + checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" 2844 + dependencies = [ 2845 + "bitflags 1.3.2", 2846 + "cssparser", 2847 + "derive_more", 2848 + "fxhash", 2849 + "log", 2850 + "phf 0.8.0", 2851 + "phf_codegen 0.8.0", 2852 + "precomputed-hash", 2853 + "servo_arc", 2854 + "smallvec", 2855 + ] 2856 + 2857 + [[package]] 2858 + name = "semver" 2859 + version = "1.0.27" 2860 + source = "registry+https://github.com/rust-lang/crates.io-index" 2861 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 2862 + dependencies = [ 2863 + "serde", 2864 + "serde_core", 2865 + ] 2866 + 2867 + [[package]] 2868 + name = "serde" 2869 + version = "1.0.228" 2870 + source = "registry+https://github.com/rust-lang/crates.io-index" 2871 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 2872 + dependencies = [ 2873 + "serde_core", 2874 + "serde_derive", 2875 + ] 2876 + 2877 + [[package]] 2878 + name = "serde-untagged" 2879 + version = "0.1.9" 2880 + source = "registry+https://github.com/rust-lang/crates.io-index" 2881 + checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" 2882 + dependencies = [ 2883 + "erased-serde", 2884 + "serde", 2885 + "serde_core", 2886 + "typeid", 2887 + ] 2888 + 2889 + [[package]] 2890 + name = "serde_core" 2891 + version = "1.0.228" 2892 + source = "registry+https://github.com/rust-lang/crates.io-index" 2893 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 2894 + dependencies = [ 2895 + "serde_derive", 2896 + ] 2897 + 2898 + [[package]] 2899 + name = "serde_derive" 2900 + version = "1.0.228" 2901 + source = "registry+https://github.com/rust-lang/crates.io-index" 2902 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 2903 + dependencies = [ 2904 + "proc-macro2", 2905 + "quote", 2906 + "syn 2.0.114", 2907 + ] 2908 + 2909 + [[package]] 2910 + name = "serde_derive_internals" 2911 + version = "0.29.1" 2912 + source = "registry+https://github.com/rust-lang/crates.io-index" 2913 + checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" 2914 + dependencies = [ 2915 + "proc-macro2", 2916 + "quote", 2917 + "syn 2.0.114", 2918 + ] 2919 + 2920 + [[package]] 2921 + name = "serde_json" 2922 + version = "1.0.149" 2923 + source = "registry+https://github.com/rust-lang/crates.io-index" 2924 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 2925 + dependencies = [ 2926 + "itoa", 2927 + "memchr", 2928 + "serde", 2929 + "serde_core", 2930 + "zmij", 2931 + ] 2932 + 2933 + [[package]] 2934 + name = "serde_repr" 2935 + version = "0.1.20" 2936 + source = "registry+https://github.com/rust-lang/crates.io-index" 2937 + checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 2938 + dependencies = [ 2939 + "proc-macro2", 2940 + "quote", 2941 + "syn 2.0.114", 2942 + ] 2943 + 2944 + [[package]] 2945 + name = "serde_spanned" 2946 + version = "0.6.9" 2947 + source = "registry+https://github.com/rust-lang/crates.io-index" 2948 + checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 2949 + dependencies = [ 2950 + "serde", 2951 + ] 2952 + 2953 + [[package]] 2954 + name = "serde_spanned" 2955 + version = "1.0.4" 2956 + source = "registry+https://github.com/rust-lang/crates.io-index" 2957 + checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" 2958 + dependencies = [ 2959 + "serde_core", 2960 + ] 2961 + 2962 + [[package]] 2963 + name = "serde_urlencoded" 2964 + version = "0.7.1" 2965 + source = "registry+https://github.com/rust-lang/crates.io-index" 2966 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2967 + dependencies = [ 2968 + "form_urlencoded", 2969 + "itoa", 2970 + "ryu", 2971 + "serde", 2972 + ] 2973 + 2974 + [[package]] 2975 + name = "serde_with" 2976 + version = "3.16.1" 2977 + source = "registry+https://github.com/rust-lang/crates.io-index" 2978 + checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" 2979 + dependencies = [ 2980 + "base64 0.22.1", 2981 + "chrono", 2982 + "hex", 2983 + "indexmap 1.9.3", 2984 + "indexmap 2.13.0", 2985 + "schemars 0.9.0", 2986 + "schemars 1.2.0", 2987 + "serde_core", 2988 + "serde_json", 2989 + "serde_with_macros", 2990 + "time", 2991 + ] 2992 + 2993 + [[package]] 2994 + name = "serde_with_macros" 2995 + version = "3.16.1" 2996 + source = "registry+https://github.com/rust-lang/crates.io-index" 2997 + checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" 2998 + dependencies = [ 2999 + "darling", 3000 + "proc-macro2", 3001 + "quote", 3002 + "syn 2.0.114", 3003 + ] 3004 + 3005 + [[package]] 3006 + name = "serialize-to-javascript" 3007 + version = "0.1.2" 3008 + source = "registry+https://github.com/rust-lang/crates.io-index" 3009 + checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" 3010 + dependencies = [ 3011 + "serde", 3012 + "serde_json", 3013 + "serialize-to-javascript-impl", 3014 + ] 3015 + 3016 + [[package]] 3017 + name = "serialize-to-javascript-impl" 3018 + version = "0.1.2" 3019 + source = "registry+https://github.com/rust-lang/crates.io-index" 3020 + checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" 3021 + dependencies = [ 3022 + "proc-macro2", 3023 + "quote", 3024 + "syn 2.0.114", 3025 + ] 3026 + 3027 + [[package]] 3028 + name = "servo_arc" 3029 + version = "0.2.0" 3030 + source = "registry+https://github.com/rust-lang/crates.io-index" 3031 + checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" 3032 + dependencies = [ 3033 + "nodrop", 3034 + "stable_deref_trait", 3035 + ] 3036 + 3037 + [[package]] 3038 + name = "sha2" 3039 + version = "0.10.9" 3040 + source = "registry+https://github.com/rust-lang/crates.io-index" 3041 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 3042 + dependencies = [ 3043 + "cfg-if", 3044 + "cpufeatures", 3045 + "digest", 3046 + ] 3047 + 3048 + [[package]] 3049 + name = "shared_child" 3050 + version = "1.1.1" 3051 + source = "registry+https://github.com/rust-lang/crates.io-index" 3052 + checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" 3053 + dependencies = [ 3054 + "libc", 3055 + "sigchld", 3056 + "windows-sys 0.60.2", 3057 + ] 3058 + 3059 + [[package]] 3060 + name = "shlex" 3061 + version = "1.3.0" 3062 + source = "registry+https://github.com/rust-lang/crates.io-index" 3063 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 3064 + 3065 + [[package]] 3066 + name = "sigchld" 3067 + version = "0.2.4" 3068 + source = "registry+https://github.com/rust-lang/crates.io-index" 3069 + checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" 3070 + dependencies = [ 3071 + "libc", 3072 + "os_pipe", 3073 + "signal-hook", 3074 + ] 3075 + 3076 + [[package]] 3077 + name = "signal-hook" 3078 + version = "0.3.18" 3079 + source = "registry+https://github.com/rust-lang/crates.io-index" 3080 + checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 3081 + dependencies = [ 3082 + "libc", 3083 + "signal-hook-registry", 3084 + ] 3085 + 3086 + [[package]] 3087 + name = "signal-hook-registry" 3088 + version = "1.4.8" 3089 + source = "registry+https://github.com/rust-lang/crates.io-index" 3090 + checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 3091 + dependencies = [ 3092 + "errno", 3093 + "libc", 3094 + ] 3095 + 3096 + [[package]] 3097 + name = "simd-adler32" 3098 + version = "0.3.8" 3099 + source = "registry+https://github.com/rust-lang/crates.io-index" 3100 + checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" 3101 + 3102 + [[package]] 3103 + name = "siphasher" 3104 + version = "0.3.11" 3105 + source = "registry+https://github.com/rust-lang/crates.io-index" 3106 + checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 3107 + 3108 + [[package]] 3109 + name = "siphasher" 3110 + version = "1.0.1" 3111 + source = "registry+https://github.com/rust-lang/crates.io-index" 3112 + checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 3113 + 3114 + [[package]] 3115 + name = "slab" 3116 + version = "0.4.11" 3117 + source = "registry+https://github.com/rust-lang/crates.io-index" 3118 + checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 3119 + 3120 + [[package]] 3121 + name = "smallvec" 3122 + version = "1.15.1" 3123 + source = "registry+https://github.com/rust-lang/crates.io-index" 3124 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 3125 + 3126 + [[package]] 3127 + name = "socket2" 3128 + version = "0.6.1" 3129 + source = "registry+https://github.com/rust-lang/crates.io-index" 3130 + checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 3131 + dependencies = [ 3132 + "libc", 3133 + "windows-sys 0.60.2", 3134 + ] 3135 + 3136 + [[package]] 3137 + name = "softbuffer" 3138 + version = "0.4.8" 3139 + source = "registry+https://github.com/rust-lang/crates.io-index" 3140 + checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" 3141 + dependencies = [ 3142 + "bytemuck", 3143 + "js-sys", 3144 + "ndk", 3145 + "objc2", 3146 + "objc2-core-foundation", 3147 + "objc2-core-graphics", 3148 + "objc2-foundation", 3149 + "objc2-quartz-core", 3150 + "raw-window-handle", 3151 + "redox_syscall", 3152 + "tracing", 3153 + "wasm-bindgen", 3154 + "web-sys", 3155 + "windows-sys 0.61.2", 3156 + ] 3157 + 3158 + [[package]] 3159 + name = "soup3" 3160 + version = "0.5.0" 3161 + source = "registry+https://github.com/rust-lang/crates.io-index" 3162 + checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" 3163 + dependencies = [ 3164 + "futures-channel", 3165 + "gio", 3166 + "glib", 3167 + "libc", 3168 + "soup3-sys", 3169 + ] 3170 + 3171 + [[package]] 3172 + name = "soup3-sys" 3173 + version = "0.5.0" 3174 + source = "registry+https://github.com/rust-lang/crates.io-index" 3175 + checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" 3176 + dependencies = [ 3177 + "gio-sys", 3178 + "glib-sys", 3179 + "gobject-sys", 3180 + "libc", 3181 + "system-deps", 3182 + ] 3183 + 3184 + [[package]] 3185 + name = "stable_deref_trait" 3186 + version = "1.2.1" 3187 + source = "registry+https://github.com/rust-lang/crates.io-index" 3188 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 3189 + 3190 + [[package]] 3191 + name = "string_cache" 3192 + version = "0.8.9" 3193 + source = "registry+https://github.com/rust-lang/crates.io-index" 3194 + checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" 3195 + dependencies = [ 3196 + "new_debug_unreachable", 3197 + "parking_lot", 3198 + "phf_shared 0.11.3", 3199 + "precomputed-hash", 3200 + "serde", 3201 + ] 3202 + 3203 + [[package]] 3204 + name = "string_cache_codegen" 3205 + version = "0.5.4" 3206 + source = "registry+https://github.com/rust-lang/crates.io-index" 3207 + checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" 3208 + dependencies = [ 3209 + "phf_generator 0.11.3", 3210 + "phf_shared 0.11.3", 3211 + "proc-macro2", 3212 + "quote", 3213 + ] 3214 + 3215 + [[package]] 3216 + name = "strsim" 3217 + version = "0.11.1" 3218 + source = "registry+https://github.com/rust-lang/crates.io-index" 3219 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 3220 + 3221 + [[package]] 3222 + name = "swift-rs" 3223 + version = "1.0.7" 3224 + source = "registry+https://github.com/rust-lang/crates.io-index" 3225 + checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" 3226 + dependencies = [ 3227 + "base64 0.21.7", 3228 + "serde", 3229 + "serde_json", 3230 + ] 3231 + 3232 + [[package]] 3233 + name = "syn" 3234 + version = "1.0.109" 3235 + source = "registry+https://github.com/rust-lang/crates.io-index" 3236 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 3237 + dependencies = [ 3238 + "proc-macro2", 3239 + "quote", 3240 + "unicode-ident", 3241 + ] 3242 + 3243 + [[package]] 3244 + name = "syn" 3245 + version = "2.0.114" 3246 + source = "registry+https://github.com/rust-lang/crates.io-index" 3247 + checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 3248 + dependencies = [ 3249 + "proc-macro2", 3250 + "quote", 3251 + "unicode-ident", 3252 + ] 3253 + 3254 + [[package]] 3255 + name = "sync_wrapper" 3256 + version = "1.0.2" 3257 + source = "registry+https://github.com/rust-lang/crates.io-index" 3258 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3259 + dependencies = [ 3260 + "futures-core", 3261 + ] 3262 + 3263 + [[package]] 3264 + name = "synstructure" 3265 + version = "0.13.2" 3266 + source = "registry+https://github.com/rust-lang/crates.io-index" 3267 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 3268 + dependencies = [ 3269 + "proc-macro2", 3270 + "quote", 3271 + "syn 2.0.114", 3272 + ] 3273 + 3274 + [[package]] 3275 + name = "system-deps" 3276 + version = "6.2.2" 3277 + source = "registry+https://github.com/rust-lang/crates.io-index" 3278 + checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" 3279 + dependencies = [ 3280 + "cfg-expr", 3281 + "heck 0.5.0", 3282 + "pkg-config", 3283 + "toml 0.8.2", 3284 + "version-compare", 3285 + ] 3286 + 3287 + [[package]] 3288 + name = "tao" 3289 + version = "0.34.5" 3290 + source = "registry+https://github.com/rust-lang/crates.io-index" 3291 + checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" 3292 + dependencies = [ 3293 + "bitflags 2.10.0", 3294 + "block2", 3295 + "core-foundation", 3296 + "core-graphics", 3297 + "crossbeam-channel", 3298 + "dispatch", 3299 + "dlopen2", 3300 + "dpi", 3301 + "gdkwayland-sys", 3302 + "gdkx11-sys", 3303 + "gtk", 3304 + "jni", 3305 + "lazy_static", 3306 + "libc", 3307 + "log", 3308 + "ndk", 3309 + "ndk-context", 3310 + "ndk-sys", 3311 + "objc2", 3312 + "objc2-app-kit", 3313 + "objc2-foundation", 3314 + "once_cell", 3315 + "parking_lot", 3316 + "raw-window-handle", 3317 + "scopeguard", 3318 + "tao-macros", 3319 + "unicode-segmentation", 3320 + "url", 3321 + "windows", 3322 + "windows-core 0.61.2", 3323 + "windows-version", 3324 + "x11-dl", 3325 + ] 3326 + 3327 + [[package]] 3328 + name = "tao-macros" 3329 + version = "0.1.3" 3330 + source = "registry+https://github.com/rust-lang/crates.io-index" 3331 + checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" 3332 + dependencies = [ 3333 + "proc-macro2", 3334 + "quote", 3335 + "syn 2.0.114", 3336 + ] 3337 + 3338 + [[package]] 3339 + name = "target-lexicon" 3340 + version = "0.12.16" 3341 + source = "registry+https://github.com/rust-lang/crates.io-index" 3342 + checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" 3343 + 3344 + [[package]] 3345 + name = "tauri" 3346 + version = "2.9.5" 3347 + source = "registry+https://github.com/rust-lang/crates.io-index" 3348 + checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033" 3349 + dependencies = [ 3350 + "anyhow", 3351 + "bytes", 3352 + "cookie", 3353 + "dirs", 3354 + "dunce", 3355 + "embed_plist", 3356 + "getrandom 0.3.4", 3357 + "glob", 3358 + "gtk", 3359 + "heck 0.5.0", 3360 + "http", 3361 + "jni", 3362 + "libc", 3363 + "log", 3364 + "mime", 3365 + "muda", 3366 + "objc2", 3367 + "objc2-app-kit", 3368 + "objc2-foundation", 3369 + "objc2-ui-kit", 3370 + "objc2-web-kit", 3371 + "percent-encoding", 3372 + "plist", 3373 + "raw-window-handle", 3374 + "reqwest", 3375 + "serde", 3376 + "serde_json", 3377 + "serde_repr", 3378 + "serialize-to-javascript", 3379 + "swift-rs", 3380 + "tauri-build", 3381 + "tauri-macros", 3382 + "tauri-runtime", 3383 + "tauri-runtime-wry", 3384 + "tauri-utils", 3385 + "thiserror 2.0.17", 3386 + "tokio", 3387 + "tray-icon", 3388 + "url", 3389 + "webkit2gtk", 3390 + "webview2-com", 3391 + "window-vibrancy", 3392 + "windows", 3393 + ] 3394 + 3395 + [[package]] 3396 + name = "tauri-build" 3397 + version = "2.5.3" 3398 + source = "registry+https://github.com/rust-lang/crates.io-index" 3399 + checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08" 3400 + dependencies = [ 3401 + "anyhow", 3402 + "cargo_toml", 3403 + "dirs", 3404 + "glob", 3405 + "heck 0.5.0", 3406 + "json-patch", 3407 + "schemars 0.8.22", 3408 + "semver", 3409 + "serde", 3410 + "serde_json", 3411 + "tauri-utils", 3412 + "tauri-winres", 3413 + "toml 0.9.11+spec-1.1.0", 3414 + "walkdir", 3415 + ] 3416 + 3417 + [[package]] 3418 + name = "tauri-codegen" 3419 + version = "2.5.2" 3420 + source = "registry+https://github.com/rust-lang/crates.io-index" 3421 + checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf" 3422 + dependencies = [ 3423 + "base64 0.22.1", 3424 + "brotli", 3425 + "ico", 3426 + "json-patch", 3427 + "plist", 3428 + "png", 3429 + "proc-macro2", 3430 + "quote", 3431 + "semver", 3432 + "serde", 3433 + "serde_json", 3434 + "sha2", 3435 + "syn 2.0.114", 3436 + "tauri-utils", 3437 + "thiserror 2.0.17", 3438 + "time", 3439 + "url", 3440 + "uuid", 3441 + "walkdir", 3442 + ] 3443 + 3444 + [[package]] 3445 + name = "tauri-macros" 3446 + version = "2.5.2" 3447 + source = "registry+https://github.com/rust-lang/crates.io-index" 3448 + checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde" 3449 + dependencies = [ 3450 + "heck 0.5.0", 3451 + "proc-macro2", 3452 + "quote", 3453 + "syn 2.0.114", 3454 + "tauri-codegen", 3455 + "tauri-utils", 3456 + ] 3457 + 3458 + [[package]] 3459 + name = "tauri-plugin" 3460 + version = "2.5.2" 3461 + source = "registry+https://github.com/rust-lang/crates.io-index" 3462 + checksum = "0e1d0a4860b7ff570c891e1d2a586bf1ede205ff858fbc305e0b5ae5d14c1377" 3463 + dependencies = [ 3464 + "anyhow", 3465 + "glob", 3466 + "plist", 3467 + "schemars 0.8.22", 3468 + "serde", 3469 + "serde_json", 3470 + "tauri-utils", 3471 + "toml 0.9.11+spec-1.1.0", 3472 + "walkdir", 3473 + ] 3474 + 3475 + [[package]] 3476 + name = "tauri-plugin-shell" 3477 + version = "2.3.4" 3478 + source = "registry+https://github.com/rust-lang/crates.io-index" 3479 + checksum = "39b76f884a3937e04b631ffdc3be506088fa979369d25147361352f2f352e5ed" 3480 + dependencies = [ 3481 + "encoding_rs", 3482 + "log", 3483 + "open", 3484 + "os_pipe", 3485 + "regex", 3486 + "schemars 0.8.22", 3487 + "serde", 3488 + "serde_json", 3489 + "shared_child", 3490 + "tauri", 3491 + "tauri-plugin", 3492 + "thiserror 2.0.17", 3493 + "tokio", 3494 + ] 3495 + 3496 + [[package]] 3497 + name = "tauri-runtime" 3498 + version = "2.9.2" 3499 + source = "registry+https://github.com/rust-lang/crates.io-index" 3500 + checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892" 3501 + dependencies = [ 3502 + "cookie", 3503 + "dpi", 3504 + "gtk", 3505 + "http", 3506 + "jni", 3507 + "objc2", 3508 + "objc2-ui-kit", 3509 + "objc2-web-kit", 3510 + "raw-window-handle", 3511 + "serde", 3512 + "serde_json", 3513 + "tauri-utils", 3514 + "thiserror 2.0.17", 3515 + "url", 3516 + "webkit2gtk", 3517 + "webview2-com", 3518 + "windows", 3519 + ] 3520 + 3521 + [[package]] 3522 + name = "tauri-runtime-wry" 3523 + version = "2.9.3" 3524 + source = "registry+https://github.com/rust-lang/crates.io-index" 3525 + checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065" 3526 + dependencies = [ 3527 + "gtk", 3528 + "http", 3529 + "jni", 3530 + "log", 3531 + "objc2", 3532 + "objc2-app-kit", 3533 + "objc2-foundation", 3534 + "once_cell", 3535 + "percent-encoding", 3536 + "raw-window-handle", 3537 + "softbuffer", 3538 + "tao", 3539 + "tauri-runtime", 3540 + "tauri-utils", 3541 + "url", 3542 + "webkit2gtk", 3543 + "webview2-com", 3544 + "windows", 3545 + "wry", 3546 + ] 3547 + 3548 + [[package]] 3549 + name = "tauri-utils" 3550 + version = "2.8.1" 3551 + source = "registry+https://github.com/rust-lang/crates.io-index" 3552 + checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490" 3553 + dependencies = [ 3554 + "anyhow", 3555 + "brotli", 3556 + "cargo_metadata", 3557 + "ctor", 3558 + "dunce", 3559 + "glob", 3560 + "html5ever", 3561 + "http", 3562 + "infer", 3563 + "json-patch", 3564 + "kuchikiki", 3565 + "log", 3566 + "memchr", 3567 + "phf 0.11.3", 3568 + "proc-macro2", 3569 + "quote", 3570 + "regex", 3571 + "schemars 0.8.22", 3572 + "semver", 3573 + "serde", 3574 + "serde-untagged", 3575 + "serde_json", 3576 + "serde_with", 3577 + "swift-rs", 3578 + "thiserror 2.0.17", 3579 + "toml 0.9.11+spec-1.1.0", 3580 + "url", 3581 + "urlpattern", 3582 + "uuid", 3583 + "walkdir", 3584 + ] 3585 + 3586 + [[package]] 3587 + name = "tauri-winres" 3588 + version = "0.3.5" 3589 + source = "registry+https://github.com/rust-lang/crates.io-index" 3590 + checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" 3591 + dependencies = [ 3592 + "dunce", 3593 + "embed-resource", 3594 + "toml 0.9.11+spec-1.1.0", 3595 + ] 3596 + 3597 + [[package]] 3598 + name = "tempfile" 3599 + version = "3.24.0" 3600 + source = "registry+https://github.com/rust-lang/crates.io-index" 3601 + checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" 3602 + dependencies = [ 3603 + "fastrand", 3604 + "getrandom 0.3.4", 3605 + "once_cell", 3606 + "rustix", 3607 + "windows-sys 0.61.2", 3608 + ] 3609 + 3610 + [[package]] 3611 + name = "tendril" 3612 + version = "0.4.3" 3613 + source = "registry+https://github.com/rust-lang/crates.io-index" 3614 + checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 3615 + dependencies = [ 3616 + "futf", 3617 + "mac", 3618 + "utf-8", 3619 + ] 3620 + 3621 + [[package]] 3622 + name = "thiserror" 3623 + version = "1.0.69" 3624 + source = "registry+https://github.com/rust-lang/crates.io-index" 3625 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 3626 + dependencies = [ 3627 + "thiserror-impl 1.0.69", 3628 + ] 3629 + 3630 + [[package]] 3631 + name = "thiserror" 3632 + version = "2.0.17" 3633 + source = "registry+https://github.com/rust-lang/crates.io-index" 3634 + checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 3635 + dependencies = [ 3636 + "thiserror-impl 2.0.17", 3637 + ] 3638 + 3639 + [[package]] 3640 + name = "thiserror-impl" 3641 + version = "1.0.69" 3642 + source = "registry+https://github.com/rust-lang/crates.io-index" 3643 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 3644 + dependencies = [ 3645 + "proc-macro2", 3646 + "quote", 3647 + "syn 2.0.114", 3648 + ] 3649 + 3650 + [[package]] 3651 + name = "thiserror-impl" 3652 + version = "2.0.17" 3653 + source = "registry+https://github.com/rust-lang/crates.io-index" 3654 + checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 3655 + dependencies = [ 3656 + "proc-macro2", 3657 + "quote", 3658 + "syn 2.0.114", 3659 + ] 3660 + 3661 + [[package]] 3662 + name = "time" 3663 + version = "0.3.44" 3664 + source = "registry+https://github.com/rust-lang/crates.io-index" 3665 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 3666 + dependencies = [ 3667 + "deranged", 3668 + "itoa", 3669 + "num-conv", 3670 + "powerfmt", 3671 + "serde", 3672 + "time-core", 3673 + "time-macros", 3674 + ] 3675 + 3676 + [[package]] 3677 + name = "time-core" 3678 + version = "0.1.6" 3679 + source = "registry+https://github.com/rust-lang/crates.io-index" 3680 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 3681 + 3682 + [[package]] 3683 + name = "time-macros" 3684 + version = "0.2.24" 3685 + source = "registry+https://github.com/rust-lang/crates.io-index" 3686 + checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 3687 + dependencies = [ 3688 + "num-conv", 3689 + "time-core", 3690 + ] 3691 + 3692 + [[package]] 3693 + name = "tinystr" 3694 + version = "0.8.2" 3695 + source = "registry+https://github.com/rust-lang/crates.io-index" 3696 + checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 3697 + dependencies = [ 3698 + "displaydoc", 3699 + "zerovec", 3700 + ] 3701 + 3702 + [[package]] 3703 + name = "tokio" 3704 + version = "1.49.0" 3705 + source = "registry+https://github.com/rust-lang/crates.io-index" 3706 + checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 3707 + dependencies = [ 3708 + "bytes", 3709 + "libc", 3710 + "mio", 3711 + "pin-project-lite", 3712 + "socket2", 3713 + "windows-sys 0.61.2", 3714 + ] 3715 + 3716 + [[package]] 3717 + name = "tokio-util" 3718 + version = "0.7.18" 3719 + source = "registry+https://github.com/rust-lang/crates.io-index" 3720 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 3721 + dependencies = [ 3722 + "bytes", 3723 + "futures-core", 3724 + "futures-sink", 3725 + "pin-project-lite", 3726 + "tokio", 3727 + ] 3728 + 3729 + [[package]] 3730 + name = "toml" 3731 + version = "0.8.2" 3732 + source = "registry+https://github.com/rust-lang/crates.io-index" 3733 + checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" 3734 + dependencies = [ 3735 + "serde", 3736 + "serde_spanned 0.6.9", 3737 + "toml_datetime 0.6.3", 3738 + "toml_edit 0.20.2", 3739 + ] 3740 + 3741 + [[package]] 3742 + name = "toml" 3743 + version = "0.9.11+spec-1.1.0" 3744 + source = "registry+https://github.com/rust-lang/crates.io-index" 3745 + checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" 3746 + dependencies = [ 3747 + "indexmap 2.13.0", 3748 + "serde_core", 3749 + "serde_spanned 1.0.4", 3750 + "toml_datetime 0.7.5+spec-1.1.0", 3751 + "toml_parser", 3752 + "toml_writer", 3753 + "winnow 0.7.14", 3754 + ] 3755 + 3756 + [[package]] 3757 + name = "toml_datetime" 3758 + version = "0.6.3" 3759 + source = "registry+https://github.com/rust-lang/crates.io-index" 3760 + checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 3761 + dependencies = [ 3762 + "serde", 3763 + ] 3764 + 3765 + [[package]] 3766 + name = "toml_datetime" 3767 + version = "0.7.5+spec-1.1.0" 3768 + source = "registry+https://github.com/rust-lang/crates.io-index" 3769 + checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" 3770 + dependencies = [ 3771 + "serde_core", 3772 + ] 3773 + 3774 + [[package]] 3775 + name = "toml_edit" 3776 + version = "0.19.15" 3777 + source = "registry+https://github.com/rust-lang/crates.io-index" 3778 + checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 3779 + dependencies = [ 3780 + "indexmap 2.13.0", 3781 + "toml_datetime 0.6.3", 3782 + "winnow 0.5.40", 3783 + ] 3784 + 3785 + [[package]] 3786 + name = "toml_edit" 3787 + version = "0.20.2" 3788 + source = "registry+https://github.com/rust-lang/crates.io-index" 3789 + checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" 3790 + dependencies = [ 3791 + "indexmap 2.13.0", 3792 + "serde", 3793 + "serde_spanned 0.6.9", 3794 + "toml_datetime 0.6.3", 3795 + "winnow 0.5.40", 3796 + ] 3797 + 3798 + [[package]] 3799 + name = "toml_edit" 3800 + version = "0.23.10+spec-1.0.0" 3801 + source = "registry+https://github.com/rust-lang/crates.io-index" 3802 + checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" 3803 + dependencies = [ 3804 + "indexmap 2.13.0", 3805 + "toml_datetime 0.7.5+spec-1.1.0", 3806 + "toml_parser", 3807 + "winnow 0.7.14", 3808 + ] 3809 + 3810 + [[package]] 3811 + name = "toml_parser" 3812 + version = "1.0.6+spec-1.1.0" 3813 + source = "registry+https://github.com/rust-lang/crates.io-index" 3814 + checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" 3815 + dependencies = [ 3816 + "winnow 0.7.14", 3817 + ] 3818 + 3819 + [[package]] 3820 + name = "toml_writer" 3821 + version = "1.0.6+spec-1.1.0" 3822 + source = "registry+https://github.com/rust-lang/crates.io-index" 3823 + checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" 3824 + 3825 + [[package]] 3826 + name = "tower" 3827 + version = "0.5.2" 3828 + source = "registry+https://github.com/rust-lang/crates.io-index" 3829 + checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 3830 + dependencies = [ 3831 + "futures-core", 3832 + "futures-util", 3833 + "pin-project-lite", 3834 + "sync_wrapper", 3835 + "tokio", 3836 + "tower-layer", 3837 + "tower-service", 3838 + ] 3839 + 3840 + [[package]] 3841 + name = "tower-http" 3842 + version = "0.6.8" 3843 + source = "registry+https://github.com/rust-lang/crates.io-index" 3844 + checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 3845 + dependencies = [ 3846 + "bitflags 2.10.0", 3847 + "bytes", 3848 + "futures-util", 3849 + "http", 3850 + "http-body", 3851 + "iri-string", 3852 + "pin-project-lite", 3853 + "tower", 3854 + "tower-layer", 3855 + "tower-service", 3856 + ] 3857 + 3858 + [[package]] 3859 + name = "tower-layer" 3860 + version = "0.3.3" 3861 + source = "registry+https://github.com/rust-lang/crates.io-index" 3862 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 3863 + 3864 + [[package]] 3865 + name = "tower-service" 3866 + version = "0.3.3" 3867 + source = "registry+https://github.com/rust-lang/crates.io-index" 3868 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 3869 + 3870 + [[package]] 3871 + name = "tracing" 3872 + version = "0.1.44" 3873 + source = "registry+https://github.com/rust-lang/crates.io-index" 3874 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 3875 + dependencies = [ 3876 + "pin-project-lite", 3877 + "tracing-core", 3878 + ] 3879 + 3880 + [[package]] 3881 + name = "tracing-core" 3882 + version = "0.1.36" 3883 + source = "registry+https://github.com/rust-lang/crates.io-index" 3884 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 3885 + dependencies = [ 3886 + "once_cell", 3887 + ] 3888 + 3889 + [[package]] 3890 + name = "tray-icon" 3891 + version = "0.21.3" 3892 + source = "registry+https://github.com/rust-lang/crates.io-index" 3893 + checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" 3894 + dependencies = [ 3895 + "crossbeam-channel", 3896 + "dirs", 3897 + "libappindicator", 3898 + "muda", 3899 + "objc2", 3900 + "objc2-app-kit", 3901 + "objc2-core-foundation", 3902 + "objc2-core-graphics", 3903 + "objc2-foundation", 3904 + "once_cell", 3905 + "png", 3906 + "serde", 3907 + "thiserror 2.0.17", 3908 + "windows-sys 0.60.2", 3909 + ] 3910 + 3911 + [[package]] 3912 + name = "try-lock" 3913 + version = "0.2.5" 3914 + source = "registry+https://github.com/rust-lang/crates.io-index" 3915 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3916 + 3917 + [[package]] 3918 + name = "typeid" 3919 + version = "1.0.3" 3920 + source = "registry+https://github.com/rust-lang/crates.io-index" 3921 + checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" 3922 + 3923 + [[package]] 3924 + name = "typenum" 3925 + version = "1.19.0" 3926 + source = "registry+https://github.com/rust-lang/crates.io-index" 3927 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 3928 + 3929 + [[package]] 3930 + name = "unic-char-property" 3931 + version = "0.9.0" 3932 + source = "registry+https://github.com/rust-lang/crates.io-index" 3933 + checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" 3934 + dependencies = [ 3935 + "unic-char-range", 3936 + ] 3937 + 3938 + [[package]] 3939 + name = "unic-char-range" 3940 + version = "0.9.0" 3941 + source = "registry+https://github.com/rust-lang/crates.io-index" 3942 + checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" 3943 + 3944 + [[package]] 3945 + name = "unic-common" 3946 + version = "0.9.0" 3947 + source = "registry+https://github.com/rust-lang/crates.io-index" 3948 + checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" 3949 + 3950 + [[package]] 3951 + name = "unic-ucd-ident" 3952 + version = "0.9.0" 3953 + source = "registry+https://github.com/rust-lang/crates.io-index" 3954 + checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" 3955 + dependencies = [ 3956 + "unic-char-property", 3957 + "unic-char-range", 3958 + "unic-ucd-version", 3959 + ] 3960 + 3961 + [[package]] 3962 + name = "unic-ucd-version" 3963 + version = "0.9.0" 3964 + source = "registry+https://github.com/rust-lang/crates.io-index" 3965 + checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" 3966 + dependencies = [ 3967 + "unic-common", 3968 + ] 3969 + 3970 + [[package]] 3971 + name = "unicase" 3972 + version = "2.9.0" 3973 + source = "registry+https://github.com/rust-lang/crates.io-index" 3974 + checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" 3975 + 3976 + [[package]] 3977 + name = "unicode-ident" 3978 + version = "1.0.22" 3979 + source = "registry+https://github.com/rust-lang/crates.io-index" 3980 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 3981 + 3982 + [[package]] 3983 + name = "unicode-segmentation" 3984 + version = "1.12.0" 3985 + source = "registry+https://github.com/rust-lang/crates.io-index" 3986 + checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 3987 + 3988 + [[package]] 3989 + name = "url" 3990 + version = "2.5.8" 3991 + source = "registry+https://github.com/rust-lang/crates.io-index" 3992 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 3993 + dependencies = [ 3994 + "form_urlencoded", 3995 + "idna", 3996 + "percent-encoding", 3997 + "serde", 3998 + "serde_derive", 3999 + ] 4000 + 4001 + [[package]] 4002 + name = "urlpattern" 4003 + version = "0.3.0" 4004 + source = "registry+https://github.com/rust-lang/crates.io-index" 4005 + checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" 4006 + dependencies = [ 4007 + "regex", 4008 + "serde", 4009 + "unic-ucd-ident", 4010 + "url", 4011 + ] 4012 + 4013 + [[package]] 4014 + name = "utf-8" 4015 + version = "0.7.6" 4016 + source = "registry+https://github.com/rust-lang/crates.io-index" 4017 + checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 4018 + 4019 + [[package]] 4020 + name = "utf8_iter" 4021 + version = "1.0.4" 4022 + source = "registry+https://github.com/rust-lang/crates.io-index" 4023 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 4024 + 4025 + [[package]] 4026 + name = "uuid" 4027 + version = "1.19.0" 4028 + source = "registry+https://github.com/rust-lang/crates.io-index" 4029 + checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" 4030 + dependencies = [ 4031 + "getrandom 0.3.4", 4032 + "js-sys", 4033 + "serde_core", 4034 + "wasm-bindgen", 4035 + ] 4036 + 4037 + [[package]] 4038 + name = "vcpkg" 4039 + version = "0.2.15" 4040 + source = "registry+https://github.com/rust-lang/crates.io-index" 4041 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 4042 + 4043 + [[package]] 4044 + name = "version-compare" 4045 + version = "0.2.1" 4046 + source = "registry+https://github.com/rust-lang/crates.io-index" 4047 + checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" 4048 + 4049 + [[package]] 4050 + name = "version_check" 4051 + version = "0.9.5" 4052 + source = "registry+https://github.com/rust-lang/crates.io-index" 4053 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 4054 + 4055 + [[package]] 4056 + name = "vswhom" 4057 + version = "0.1.0" 4058 + source = "registry+https://github.com/rust-lang/crates.io-index" 4059 + checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" 4060 + dependencies = [ 4061 + "libc", 4062 + "vswhom-sys", 4063 + ] 4064 + 4065 + [[package]] 4066 + name = "vswhom-sys" 4067 + version = "0.1.3" 4068 + source = "registry+https://github.com/rust-lang/crates.io-index" 4069 + checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" 4070 + dependencies = [ 4071 + "cc", 4072 + "libc", 4073 + ] 4074 + 4075 + [[package]] 4076 + name = "walkdir" 4077 + version = "2.5.0" 4078 + source = "registry+https://github.com/rust-lang/crates.io-index" 4079 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 4080 + dependencies = [ 4081 + "same-file", 4082 + "winapi-util", 4083 + ] 4084 + 4085 + [[package]] 4086 + name = "want" 4087 + version = "0.3.1" 4088 + source = "registry+https://github.com/rust-lang/crates.io-index" 4089 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 4090 + dependencies = [ 4091 + "try-lock", 4092 + ] 4093 + 4094 + [[package]] 4095 + name = "wasi" 4096 + version = "0.9.0+wasi-snapshot-preview1" 4097 + source = "registry+https://github.com/rust-lang/crates.io-index" 4098 + checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 4099 + 4100 + [[package]] 4101 + name = "wasi" 4102 + version = "0.11.1+wasi-snapshot-preview1" 4103 + source = "registry+https://github.com/rust-lang/crates.io-index" 4104 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 4105 + 4106 + [[package]] 4107 + name = "wasip2" 4108 + version = "1.0.1+wasi-0.2.4" 4109 + source = "registry+https://github.com/rust-lang/crates.io-index" 4110 + checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 4111 + dependencies = [ 4112 + "wit-bindgen", 4113 + ] 4114 + 4115 + [[package]] 4116 + name = "wasm-bindgen" 4117 + version = "0.2.106" 4118 + source = "registry+https://github.com/rust-lang/crates.io-index" 4119 + checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" 4120 + dependencies = [ 4121 + "cfg-if", 4122 + "once_cell", 4123 + "rustversion", 4124 + "wasm-bindgen-macro", 4125 + "wasm-bindgen-shared", 4126 + ] 4127 + 4128 + [[package]] 4129 + name = "wasm-bindgen-futures" 4130 + version = "0.4.56" 4131 + source = "registry+https://github.com/rust-lang/crates.io-index" 4132 + checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" 4133 + dependencies = [ 4134 + "cfg-if", 4135 + "js-sys", 4136 + "once_cell", 4137 + "wasm-bindgen", 4138 + "web-sys", 4139 + ] 4140 + 4141 + [[package]] 4142 + name = "wasm-bindgen-macro" 4143 + version = "0.2.106" 4144 + source = "registry+https://github.com/rust-lang/crates.io-index" 4145 + checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" 4146 + dependencies = [ 4147 + "quote", 4148 + "wasm-bindgen-macro-support", 4149 + ] 4150 + 4151 + [[package]] 4152 + name = "wasm-bindgen-macro-support" 4153 + version = "0.2.106" 4154 + source = "registry+https://github.com/rust-lang/crates.io-index" 4155 + checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" 4156 + dependencies = [ 4157 + "bumpalo", 4158 + "proc-macro2", 4159 + "quote", 4160 + "syn 2.0.114", 4161 + "wasm-bindgen-shared", 4162 + ] 4163 + 4164 + [[package]] 4165 + name = "wasm-bindgen-shared" 4166 + version = "0.2.106" 4167 + source = "registry+https://github.com/rust-lang/crates.io-index" 4168 + checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" 4169 + dependencies = [ 4170 + "unicode-ident", 4171 + ] 4172 + 4173 + [[package]] 4174 + name = "wasm-streams" 4175 + version = "0.4.2" 4176 + source = "registry+https://github.com/rust-lang/crates.io-index" 4177 + checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 4178 + dependencies = [ 4179 + "futures-util", 4180 + "js-sys", 4181 + "wasm-bindgen", 4182 + "wasm-bindgen-futures", 4183 + "web-sys", 4184 + ] 4185 + 4186 + [[package]] 4187 + name = "web-sys" 4188 + version = "0.3.83" 4189 + source = "registry+https://github.com/rust-lang/crates.io-index" 4190 + checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" 4191 + dependencies = [ 4192 + "js-sys", 4193 + "wasm-bindgen", 4194 + ] 4195 + 4196 + [[package]] 4197 + name = "webkit2gtk" 4198 + version = "2.0.1" 4199 + source = "registry+https://github.com/rust-lang/crates.io-index" 4200 + checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" 4201 + dependencies = [ 4202 + "bitflags 1.3.2", 4203 + "cairo-rs", 4204 + "gdk", 4205 + "gdk-sys", 4206 + "gio", 4207 + "gio-sys", 4208 + "glib", 4209 + "glib-sys", 4210 + "gobject-sys", 4211 + "gtk", 4212 + "gtk-sys", 4213 + "javascriptcore-rs", 4214 + "libc", 4215 + "once_cell", 4216 + "soup3", 4217 + "webkit2gtk-sys", 4218 + ] 4219 + 4220 + [[package]] 4221 + name = "webkit2gtk-sys" 4222 + version = "2.0.1" 4223 + source = "registry+https://github.com/rust-lang/crates.io-index" 4224 + checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" 4225 + dependencies = [ 4226 + "bitflags 1.3.2", 4227 + "cairo-sys-rs", 4228 + "gdk-sys", 4229 + "gio-sys", 4230 + "glib-sys", 4231 + "gobject-sys", 4232 + "gtk-sys", 4233 + "javascriptcore-rs-sys", 4234 + "libc", 4235 + "pkg-config", 4236 + "soup3-sys", 4237 + "system-deps", 4238 + ] 4239 + 4240 + [[package]] 4241 + name = "webview2-com" 4242 + version = "0.38.2" 4243 + source = "registry+https://github.com/rust-lang/crates.io-index" 4244 + checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" 4245 + dependencies = [ 4246 + "webview2-com-macros", 4247 + "webview2-com-sys", 4248 + "windows", 4249 + "windows-core 0.61.2", 4250 + "windows-implement", 4251 + "windows-interface", 4252 + ] 4253 + 4254 + [[package]] 4255 + name = "webview2-com-macros" 4256 + version = "0.8.1" 4257 + source = "registry+https://github.com/rust-lang/crates.io-index" 4258 + checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" 4259 + dependencies = [ 4260 + "proc-macro2", 4261 + "quote", 4262 + "syn 2.0.114", 4263 + ] 4264 + 4265 + [[package]] 4266 + name = "webview2-com-sys" 4267 + version = "0.38.2" 4268 + source = "registry+https://github.com/rust-lang/crates.io-index" 4269 + checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" 4270 + dependencies = [ 4271 + "thiserror 2.0.17", 4272 + "windows", 4273 + "windows-core 0.61.2", 4274 + ] 4275 + 4276 + [[package]] 4277 + name = "winapi" 4278 + version = "0.3.9" 4279 + source = "registry+https://github.com/rust-lang/crates.io-index" 4280 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 4281 + dependencies = [ 4282 + "winapi-i686-pc-windows-gnu", 4283 + "winapi-x86_64-pc-windows-gnu", 4284 + ] 4285 + 4286 + [[package]] 4287 + name = "winapi-i686-pc-windows-gnu" 4288 + version = "0.4.0" 4289 + source = "registry+https://github.com/rust-lang/crates.io-index" 4290 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 4291 + 4292 + [[package]] 4293 + name = "winapi-util" 4294 + version = "0.1.11" 4295 + source = "registry+https://github.com/rust-lang/crates.io-index" 4296 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 4297 + dependencies = [ 4298 + "windows-sys 0.61.2", 4299 + ] 4300 + 4301 + [[package]] 4302 + name = "winapi-x86_64-pc-windows-gnu" 4303 + version = "0.4.0" 4304 + source = "registry+https://github.com/rust-lang/crates.io-index" 4305 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 4306 + 4307 + [[package]] 4308 + name = "window-vibrancy" 4309 + version = "0.6.0" 4310 + source = "registry+https://github.com/rust-lang/crates.io-index" 4311 + checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" 4312 + dependencies = [ 4313 + "objc2", 4314 + "objc2-app-kit", 4315 + "objc2-core-foundation", 4316 + "objc2-foundation", 4317 + "raw-window-handle", 4318 + "windows-sys 0.59.0", 4319 + "windows-version", 4320 + ] 4321 + 4322 + [[package]] 4323 + name = "windows" 4324 + version = "0.61.3" 4325 + source = "registry+https://github.com/rust-lang/crates.io-index" 4326 + checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 4327 + dependencies = [ 4328 + "windows-collections", 4329 + "windows-core 0.61.2", 4330 + "windows-future", 4331 + "windows-link 0.1.3", 4332 + "windows-numerics", 4333 + ] 4334 + 4335 + [[package]] 4336 + name = "windows-collections" 4337 + version = "0.2.0" 4338 + source = "registry+https://github.com/rust-lang/crates.io-index" 4339 + checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 4340 + dependencies = [ 4341 + "windows-core 0.61.2", 4342 + ] 4343 + 4344 + [[package]] 4345 + name = "windows-core" 4346 + version = "0.61.2" 4347 + source = "registry+https://github.com/rust-lang/crates.io-index" 4348 + checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 4349 + dependencies = [ 4350 + "windows-implement", 4351 + "windows-interface", 4352 + "windows-link 0.1.3", 4353 + "windows-result 0.3.4", 4354 + "windows-strings 0.4.2", 4355 + ] 4356 + 4357 + [[package]] 4358 + name = "windows-core" 4359 + version = "0.62.2" 4360 + source = "registry+https://github.com/rust-lang/crates.io-index" 4361 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 4362 + dependencies = [ 4363 + "windows-implement", 4364 + "windows-interface", 4365 + "windows-link 0.2.1", 4366 + "windows-result 0.4.1", 4367 + "windows-strings 0.5.1", 4368 + ] 4369 + 4370 + [[package]] 4371 + name = "windows-future" 4372 + version = "0.2.1" 4373 + source = "registry+https://github.com/rust-lang/crates.io-index" 4374 + checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 4375 + dependencies = [ 4376 + "windows-core 0.61.2", 4377 + "windows-link 0.1.3", 4378 + "windows-threading", 4379 + ] 4380 + 4381 + [[package]] 4382 + name = "windows-implement" 4383 + version = "0.60.2" 4384 + source = "registry+https://github.com/rust-lang/crates.io-index" 4385 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 4386 + dependencies = [ 4387 + "proc-macro2", 4388 + "quote", 4389 + "syn 2.0.114", 4390 + ] 4391 + 4392 + [[package]] 4393 + name = "windows-interface" 4394 + version = "0.59.3" 4395 + source = "registry+https://github.com/rust-lang/crates.io-index" 4396 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 4397 + dependencies = [ 4398 + "proc-macro2", 4399 + "quote", 4400 + "syn 2.0.114", 4401 + ] 4402 + 4403 + [[package]] 4404 + name = "windows-link" 4405 + version = "0.1.3" 4406 + source = "registry+https://github.com/rust-lang/crates.io-index" 4407 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 4408 + 4409 + [[package]] 4410 + name = "windows-link" 4411 + version = "0.2.1" 4412 + source = "registry+https://github.com/rust-lang/crates.io-index" 4413 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4414 + 4415 + [[package]] 4416 + name = "windows-numerics" 4417 + version = "0.2.0" 4418 + source = "registry+https://github.com/rust-lang/crates.io-index" 4419 + checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 4420 + dependencies = [ 4421 + "windows-core 0.61.2", 4422 + "windows-link 0.1.3", 4423 + ] 4424 + 4425 + [[package]] 4426 + name = "windows-result" 4427 + version = "0.3.4" 4428 + source = "registry+https://github.com/rust-lang/crates.io-index" 4429 + checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 4430 + dependencies = [ 4431 + "windows-link 0.1.3", 4432 + ] 4433 + 4434 + [[package]] 4435 + name = "windows-result" 4436 + version = "0.4.1" 4437 + source = "registry+https://github.com/rust-lang/crates.io-index" 4438 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 4439 + dependencies = [ 4440 + "windows-link 0.2.1", 4441 + ] 4442 + 4443 + [[package]] 4444 + name = "windows-strings" 4445 + version = "0.4.2" 4446 + source = "registry+https://github.com/rust-lang/crates.io-index" 4447 + checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 4448 + dependencies = [ 4449 + "windows-link 0.1.3", 4450 + ] 4451 + 4452 + [[package]] 4453 + name = "windows-strings" 4454 + version = "0.5.1" 4455 + source = "registry+https://github.com/rust-lang/crates.io-index" 4456 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 4457 + dependencies = [ 4458 + "windows-link 0.2.1", 4459 + ] 4460 + 4461 + [[package]] 4462 + name = "windows-sys" 4463 + version = "0.45.0" 4464 + source = "registry+https://github.com/rust-lang/crates.io-index" 4465 + checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 4466 + dependencies = [ 4467 + "windows-targets 0.42.2", 4468 + ] 4469 + 4470 + [[package]] 4471 + name = "windows-sys" 4472 + version = "0.59.0" 4473 + source = "registry+https://github.com/rust-lang/crates.io-index" 4474 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 4475 + dependencies = [ 4476 + "windows-targets 0.52.6", 4477 + ] 4478 + 4479 + [[package]] 4480 + name = "windows-sys" 4481 + version = "0.60.2" 4482 + source = "registry+https://github.com/rust-lang/crates.io-index" 4483 + checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 4484 + dependencies = [ 4485 + "windows-targets 0.53.5", 4486 + ] 4487 + 4488 + [[package]] 4489 + name = "windows-sys" 4490 + version = "0.61.2" 4491 + source = "registry+https://github.com/rust-lang/crates.io-index" 4492 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 4493 + dependencies = [ 4494 + "windows-link 0.2.1", 4495 + ] 4496 + 4497 + [[package]] 4498 + name = "windows-targets" 4499 + version = "0.42.2" 4500 + source = "registry+https://github.com/rust-lang/crates.io-index" 4501 + checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 4502 + dependencies = [ 4503 + "windows_aarch64_gnullvm 0.42.2", 4504 + "windows_aarch64_msvc 0.42.2", 4505 + "windows_i686_gnu 0.42.2", 4506 + "windows_i686_msvc 0.42.2", 4507 + "windows_x86_64_gnu 0.42.2", 4508 + "windows_x86_64_gnullvm 0.42.2", 4509 + "windows_x86_64_msvc 0.42.2", 4510 + ] 4511 + 4512 + [[package]] 4513 + name = "windows-targets" 4514 + version = "0.52.6" 4515 + source = "registry+https://github.com/rust-lang/crates.io-index" 4516 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 4517 + dependencies = [ 4518 + "windows_aarch64_gnullvm 0.52.6", 4519 + "windows_aarch64_msvc 0.52.6", 4520 + "windows_i686_gnu 0.52.6", 4521 + "windows_i686_gnullvm 0.52.6", 4522 + "windows_i686_msvc 0.52.6", 4523 + "windows_x86_64_gnu 0.52.6", 4524 + "windows_x86_64_gnullvm 0.52.6", 4525 + "windows_x86_64_msvc 0.52.6", 4526 + ] 4527 + 4528 + [[package]] 4529 + name = "windows-targets" 4530 + version = "0.53.5" 4531 + source = "registry+https://github.com/rust-lang/crates.io-index" 4532 + checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 4533 + dependencies = [ 4534 + "windows-link 0.2.1", 4535 + "windows_aarch64_gnullvm 0.53.1", 4536 + "windows_aarch64_msvc 0.53.1", 4537 + "windows_i686_gnu 0.53.1", 4538 + "windows_i686_gnullvm 0.53.1", 4539 + "windows_i686_msvc 0.53.1", 4540 + "windows_x86_64_gnu 0.53.1", 4541 + "windows_x86_64_gnullvm 0.53.1", 4542 + "windows_x86_64_msvc 0.53.1", 4543 + ] 4544 + 4545 + [[package]] 4546 + name = "windows-threading" 4547 + version = "0.1.0" 4548 + source = "registry+https://github.com/rust-lang/crates.io-index" 4549 + checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 4550 + dependencies = [ 4551 + "windows-link 0.1.3", 4552 + ] 4553 + 4554 + [[package]] 4555 + name = "windows-version" 4556 + version = "0.1.7" 4557 + source = "registry+https://github.com/rust-lang/crates.io-index" 4558 + checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" 4559 + dependencies = [ 4560 + "windows-link 0.2.1", 4561 + ] 4562 + 4563 + [[package]] 4564 + name = "windows_aarch64_gnullvm" 4565 + version = "0.42.2" 4566 + source = "registry+https://github.com/rust-lang/crates.io-index" 4567 + checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 4568 + 4569 + [[package]] 4570 + name = "windows_aarch64_gnullvm" 4571 + version = "0.52.6" 4572 + source = "registry+https://github.com/rust-lang/crates.io-index" 4573 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 4574 + 4575 + [[package]] 4576 + name = "windows_aarch64_gnullvm" 4577 + version = "0.53.1" 4578 + source = "registry+https://github.com/rust-lang/crates.io-index" 4579 + checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 4580 + 4581 + [[package]] 4582 + name = "windows_aarch64_msvc" 4583 + version = "0.42.2" 4584 + source = "registry+https://github.com/rust-lang/crates.io-index" 4585 + checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 4586 + 4587 + [[package]] 4588 + name = "windows_aarch64_msvc" 4589 + version = "0.52.6" 4590 + source = "registry+https://github.com/rust-lang/crates.io-index" 4591 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 4592 + 4593 + [[package]] 4594 + name = "windows_aarch64_msvc" 4595 + version = "0.53.1" 4596 + source = "registry+https://github.com/rust-lang/crates.io-index" 4597 + checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 4598 + 4599 + [[package]] 4600 + name = "windows_i686_gnu" 4601 + version = "0.42.2" 4602 + source = "registry+https://github.com/rust-lang/crates.io-index" 4603 + checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 4604 + 4605 + [[package]] 4606 + name = "windows_i686_gnu" 4607 + version = "0.52.6" 4608 + source = "registry+https://github.com/rust-lang/crates.io-index" 4609 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 4610 + 4611 + [[package]] 4612 + name = "windows_i686_gnu" 4613 + version = "0.53.1" 4614 + source = "registry+https://github.com/rust-lang/crates.io-index" 4615 + checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 4616 + 4617 + [[package]] 4618 + name = "windows_i686_gnullvm" 4619 + version = "0.52.6" 4620 + source = "registry+https://github.com/rust-lang/crates.io-index" 4621 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 4622 + 4623 + [[package]] 4624 + name = "windows_i686_gnullvm" 4625 + version = "0.53.1" 4626 + source = "registry+https://github.com/rust-lang/crates.io-index" 4627 + checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 4628 + 4629 + [[package]] 4630 + name = "windows_i686_msvc" 4631 + version = "0.42.2" 4632 + source = "registry+https://github.com/rust-lang/crates.io-index" 4633 + checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 4634 + 4635 + [[package]] 4636 + name = "windows_i686_msvc" 4637 + version = "0.52.6" 4638 + source = "registry+https://github.com/rust-lang/crates.io-index" 4639 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 4640 + 4641 + [[package]] 4642 + name = "windows_i686_msvc" 4643 + version = "0.53.1" 4644 + source = "registry+https://github.com/rust-lang/crates.io-index" 4645 + checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 4646 + 4647 + [[package]] 4648 + name = "windows_x86_64_gnu" 4649 + version = "0.42.2" 4650 + source = "registry+https://github.com/rust-lang/crates.io-index" 4651 + checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 4652 + 4653 + [[package]] 4654 + name = "windows_x86_64_gnu" 4655 + version = "0.52.6" 4656 + source = "registry+https://github.com/rust-lang/crates.io-index" 4657 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 4658 + 4659 + [[package]] 4660 + name = "windows_x86_64_gnu" 4661 + version = "0.53.1" 4662 + source = "registry+https://github.com/rust-lang/crates.io-index" 4663 + checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 4664 + 4665 + [[package]] 4666 + name = "windows_x86_64_gnullvm" 4667 + version = "0.42.2" 4668 + source = "registry+https://github.com/rust-lang/crates.io-index" 4669 + checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 4670 + 4671 + [[package]] 4672 + name = "windows_x86_64_gnullvm" 4673 + version = "0.52.6" 4674 + source = "registry+https://github.com/rust-lang/crates.io-index" 4675 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 4676 + 4677 + [[package]] 4678 + name = "windows_x86_64_gnullvm" 4679 + version = "0.53.1" 4680 + source = "registry+https://github.com/rust-lang/crates.io-index" 4681 + checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 4682 + 4683 + [[package]] 4684 + name = "windows_x86_64_msvc" 4685 + version = "0.42.2" 4686 + source = "registry+https://github.com/rust-lang/crates.io-index" 4687 + checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 4688 + 4689 + [[package]] 4690 + name = "windows_x86_64_msvc" 4691 + version = "0.52.6" 4692 + source = "registry+https://github.com/rust-lang/crates.io-index" 4693 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 4694 + 4695 + [[package]] 4696 + name = "windows_x86_64_msvc" 4697 + version = "0.53.1" 4698 + source = "registry+https://github.com/rust-lang/crates.io-index" 4699 + checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 4700 + 4701 + [[package]] 4702 + name = "winnow" 4703 + version = "0.5.40" 4704 + source = "registry+https://github.com/rust-lang/crates.io-index" 4705 + checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 4706 + dependencies = [ 4707 + "memchr", 4708 + ] 4709 + 4710 + [[package]] 4711 + name = "winnow" 4712 + version = "0.7.14" 4713 + source = "registry+https://github.com/rust-lang/crates.io-index" 4714 + checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" 4715 + dependencies = [ 4716 + "memchr", 4717 + ] 4718 + 4719 + [[package]] 4720 + name = "winreg" 4721 + version = "0.55.0" 4722 + source = "registry+https://github.com/rust-lang/crates.io-index" 4723 + checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" 4724 + dependencies = [ 4725 + "cfg-if", 4726 + "windows-sys 0.59.0", 4727 + ] 4728 + 4729 + [[package]] 4730 + name = "wit-bindgen" 4731 + version = "0.46.0" 4732 + source = "registry+https://github.com/rust-lang/crates.io-index" 4733 + checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 4734 + 4735 + [[package]] 4736 + name = "writeable" 4737 + version = "0.6.2" 4738 + source = "registry+https://github.com/rust-lang/crates.io-index" 4739 + checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 4740 + 4741 + [[package]] 4742 + name = "wry" 4743 + version = "0.53.5" 4744 + source = "registry+https://github.com/rust-lang/crates.io-index" 4745 + checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" 4746 + dependencies = [ 4747 + "base64 0.22.1", 4748 + "block2", 4749 + "cookie", 4750 + "crossbeam-channel", 4751 + "dirs", 4752 + "dpi", 4753 + "dunce", 4754 + "gdkx11", 4755 + "gtk", 4756 + "html5ever", 4757 + "http", 4758 + "javascriptcore-rs", 4759 + "jni", 4760 + "kuchikiki", 4761 + "libc", 4762 + "ndk", 4763 + "objc2", 4764 + "objc2-app-kit", 4765 + "objc2-core-foundation", 4766 + "objc2-foundation", 4767 + "objc2-ui-kit", 4768 + "objc2-web-kit", 4769 + "once_cell", 4770 + "percent-encoding", 4771 + "raw-window-handle", 4772 + "sha2", 4773 + "soup3", 4774 + "tao-macros", 4775 + "thiserror 2.0.17", 4776 + "url", 4777 + "webkit2gtk", 4778 + "webkit2gtk-sys", 4779 + "webview2-com", 4780 + "windows", 4781 + "windows-core 0.61.2", 4782 + "windows-version", 4783 + "x11-dl", 4784 + ] 4785 + 4786 + [[package]] 4787 + name = "x11" 4788 + version = "2.21.0" 4789 + source = "registry+https://github.com/rust-lang/crates.io-index" 4790 + checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" 4791 + dependencies = [ 4792 + "libc", 4793 + "pkg-config", 4794 + ] 4795 + 4796 + [[package]] 4797 + name = "x11-dl" 4798 + version = "2.21.0" 4799 + source = "registry+https://github.com/rust-lang/crates.io-index" 4800 + checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" 4801 + dependencies = [ 4802 + "libc", 4803 + "once_cell", 4804 + "pkg-config", 4805 + ] 4806 + 4807 + [[package]] 4808 + name = "yoke" 4809 + version = "0.8.1" 4810 + source = "registry+https://github.com/rust-lang/crates.io-index" 4811 + checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 4812 + dependencies = [ 4813 + "stable_deref_trait", 4814 + "yoke-derive", 4815 + "zerofrom", 4816 + ] 4817 + 4818 + [[package]] 4819 + name = "yoke-derive" 4820 + version = "0.8.1" 4821 + source = "registry+https://github.com/rust-lang/crates.io-index" 4822 + checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 4823 + dependencies = [ 4824 + "proc-macro2", 4825 + "quote", 4826 + "syn 2.0.114", 4827 + "synstructure", 4828 + ] 4829 + 4830 + [[package]] 4831 + name = "zerocopy" 4832 + version = "0.8.33" 4833 + source = "registry+https://github.com/rust-lang/crates.io-index" 4834 + checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" 4835 + dependencies = [ 4836 + "zerocopy-derive", 4837 + ] 4838 + 4839 + [[package]] 4840 + name = "zerocopy-derive" 4841 + version = "0.8.33" 4842 + source = "registry+https://github.com/rust-lang/crates.io-index" 4843 + checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" 4844 + dependencies = [ 4845 + "proc-macro2", 4846 + "quote", 4847 + "syn 2.0.114", 4848 + ] 4849 + 4850 + [[package]] 4851 + name = "zerofrom" 4852 + version = "0.1.6" 4853 + source = "registry+https://github.com/rust-lang/crates.io-index" 4854 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 4855 + dependencies = [ 4856 + "zerofrom-derive", 4857 + ] 4858 + 4859 + [[package]] 4860 + name = "zerofrom-derive" 4861 + version = "0.1.6" 4862 + source = "registry+https://github.com/rust-lang/crates.io-index" 4863 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 4864 + dependencies = [ 4865 + "proc-macro2", 4866 + "quote", 4867 + "syn 2.0.114", 4868 + "synstructure", 4869 + ] 4870 + 4871 + [[package]] 4872 + name = "zerotrie" 4873 + version = "0.2.3" 4874 + source = "registry+https://github.com/rust-lang/crates.io-index" 4875 + checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 4876 + dependencies = [ 4877 + "displaydoc", 4878 + "yoke", 4879 + "zerofrom", 4880 + ] 4881 + 4882 + [[package]] 4883 + name = "zerovec" 4884 + version = "0.11.5" 4885 + source = "registry+https://github.com/rust-lang/crates.io-index" 4886 + checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 4887 + dependencies = [ 4888 + "yoke", 4889 + "zerofrom", 4890 + "zerovec-derive", 4891 + ] 4892 + 4893 + [[package]] 4894 + name = "zerovec-derive" 4895 + version = "0.11.2" 4896 + source = "registry+https://github.com/rust-lang/crates.io-index" 4897 + checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 4898 + dependencies = [ 4899 + "proc-macro2", 4900 + "quote", 4901 + "syn 2.0.114", 4902 + ] 4903 + 4904 + [[package]] 4905 + name = "zmij" 4906 + version = "1.0.12" 4907 + source = "registry+https://github.com/rust-lang/crates.io-index" 4908 + checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
+34
backend/tauri/src-tauri/Cargo.toml
··· 1 + [package] 2 + name = "peek-tauri" 3 + version = "0.1.0" 4 + description = "Peek web user agent - Tauri backend" 5 + authors = ["Dietrich Ayala"] 6 + edition = "2021" 7 + 8 + [lib] 9 + name = "peek_tauri_lib" 10 + crate-type = ["staticlib", "cdylib", "rlib"] 11 + 12 + [build-dependencies] 13 + tauri-build = { version = "2", features = [] } 14 + 15 + [dependencies] 16 + tauri = { version = "2", features = ["devtools"] } 17 + tauri-plugin-shell = "2" 18 + serde = { version = "1", features = ["derive"] } 19 + serde_json = "1" 20 + rusqlite = { version = "0.32", features = ["bundled"] } 21 + uuid = { version = "1", features = ["v4"] } 22 + chrono = "0.4" 23 + url = "2" 24 + mime_guess = "2" 25 + tokio = { version = "1", features = ["sync"] } 26 + 27 + [dev-dependencies] 28 + tempfile = "3" 29 + 30 + [profile.release] 31 + strip = true 32 + lto = true 33 + codegen-units = 1 34 + panic = "abort"
+3
backend/tauri/src-tauri/build.rs
··· 1 + fn main() { 2 + tauri_build::build() 3 + }
+20
backend/tauri/src-tauri/capabilities/default.json
··· 1 + { 2 + "$schema": "https://schemas.tauri.app/capability/1.0.0", 3 + "identifier": "default", 4 + "description": "Default capabilities for Peek", 5 + "windows": ["*"], 6 + "permissions": [ 7 + "core:default", 8 + "core:event:default", 9 + "core:event:allow-emit", 10 + "core:event:allow-listen", 11 + "core:window:default", 12 + "core:window:allow-create", 13 + "core:window:allow-close", 14 + "core:window:allow-show", 15 + "core:window:allow-hide", 16 + "core:window:allow-set-focus", 17 + "core:webview:default", 18 + "shell:default" 19 + ] 20 + }
backend/tauri/src-tauri/icons/128x128.png

This is a binary file and will not be displayed.

backend/tauri/src-tauri/icons/32x32.png

This is a binary file and will not be displayed.

backend/tauri/src-tauri/icons/icon.png

This is a binary file and will not be displayed.

+218
backend/tauri/src-tauri/src/commands/datastore.rs
··· 1 + //! Datastore commands - IPC handlers for SQLite operations 2 + 3 + use super::CommandResponse; 4 + use crate::datastore::{self, Address, AddressFilter, AddressOptions, AddressTag, DatastoreStats, Tag, Visit, VisitFilter, VisitOptions}; 5 + use crate::state::AppState; 6 + use serde::{Deserialize, Serialize}; 7 + use std::collections::HashMap; 8 + use std::sync::Arc; 9 + 10 + // ==================== Address Commands ==================== 11 + 12 + #[derive(Debug, Serialize)] 13 + pub struct AddAddressResult { 14 + pub id: String, 15 + } 16 + 17 + #[tauri::command] 18 + pub async fn datastore_add_address( 19 + state: tauri::State<'_, Arc<AppState>>, 20 + uri: String, 21 + options: Option<AddressOptions>, 22 + ) -> Result<CommandResponse<AddAddressResult>, String> { 23 + let db = state.db.lock().unwrap(); 24 + let options = options.unwrap_or_default(); 25 + 26 + match datastore::add_address(&db, &uri, &options) { 27 + Ok(id) => Ok(CommandResponse::success(AddAddressResult { id })), 28 + Err(e) => Ok(CommandResponse::error(format!("Failed to add address: {}", e))), 29 + } 30 + } 31 + 32 + #[tauri::command] 33 + pub async fn datastore_get_address( 34 + state: tauri::State<'_, Arc<AppState>>, 35 + id: String, 36 + ) -> Result<CommandResponse<Option<Address>>, String> { 37 + let db = state.db.lock().unwrap(); 38 + 39 + match datastore::get_address(&db, &id) { 40 + Ok(addr) => Ok(CommandResponse::success(addr)), 41 + Err(e) => Ok(CommandResponse::error(format!("Failed to get address: {}", e))), 42 + } 43 + } 44 + 45 + #[tauri::command] 46 + pub async fn datastore_update_address( 47 + state: tauri::State<'_, Arc<AppState>>, 48 + id: String, 49 + updates: HashMap<String, serde_json::Value>, 50 + ) -> Result<CommandResponse<bool>, String> { 51 + let db = state.db.lock().unwrap(); 52 + 53 + match datastore::update_address(&db, &id, &updates) { 54 + Ok(updated) => Ok(CommandResponse::success(updated)), 55 + Err(e) => Ok(CommandResponse::error(format!("Failed to update address: {}", e))), 56 + } 57 + } 58 + 59 + #[tauri::command] 60 + pub async fn datastore_query_addresses( 61 + state: tauri::State<'_, Arc<AppState>>, 62 + filter: Option<AddressFilter>, 63 + ) -> Result<CommandResponse<Vec<Address>>, String> { 64 + let db = state.db.lock().unwrap(); 65 + let filter = filter.unwrap_or_default(); 66 + 67 + match datastore::query_addresses(&db, &filter) { 68 + Ok(addresses) => Ok(CommandResponse::success(addresses)), 69 + Err(e) => Ok(CommandResponse::error(format!("Failed to query addresses: {}", e))), 70 + } 71 + } 72 + 73 + // ==================== Visit Commands ==================== 74 + 75 + #[derive(Debug, Serialize)] 76 + pub struct AddVisitResult { 77 + pub id: String, 78 + } 79 + 80 + #[tauri::command] 81 + pub async fn datastore_add_visit( 82 + state: tauri::State<'_, Arc<AppState>>, 83 + address_id: String, 84 + options: Option<VisitOptions>, 85 + ) -> Result<CommandResponse<AddVisitResult>, String> { 86 + let db = state.db.lock().unwrap(); 87 + let options = options.unwrap_or_default(); 88 + 89 + match datastore::add_visit(&db, &address_id, &options) { 90 + Ok(id) => Ok(CommandResponse::success(AddVisitResult { id })), 91 + Err(e) => Ok(CommandResponse::error(format!("Failed to add visit: {}", e))), 92 + } 93 + } 94 + 95 + #[tauri::command] 96 + pub async fn datastore_query_visits( 97 + state: tauri::State<'_, Arc<AppState>>, 98 + filter: Option<VisitFilter>, 99 + ) -> Result<CommandResponse<Vec<Visit>>, String> { 100 + let db = state.db.lock().unwrap(); 101 + let filter = filter.unwrap_or_default(); 102 + 103 + match datastore::query_visits(&db, &filter) { 104 + Ok(visits) => Ok(CommandResponse::success(visits)), 105 + Err(e) => Ok(CommandResponse::error(format!("Failed to query visits: {}", e))), 106 + } 107 + } 108 + 109 + // ==================== Tag Commands ==================== 110 + 111 + #[derive(Debug, Serialize)] 112 + pub struct GetOrCreateTagResult { 113 + pub tag: Tag, 114 + pub created: bool, 115 + } 116 + 117 + #[tauri::command] 118 + pub async fn datastore_get_or_create_tag( 119 + state: tauri::State<'_, Arc<AppState>>, 120 + name: String, 121 + ) -> Result<CommandResponse<GetOrCreateTagResult>, String> { 122 + let db = state.db.lock().unwrap(); 123 + 124 + match datastore::get_or_create_tag(&db, &name) { 125 + Ok((tag, created)) => Ok(CommandResponse::success(GetOrCreateTagResult { tag, created })), 126 + Err(e) => Ok(CommandResponse::error(format!("Failed to get/create tag: {}", e))), 127 + } 128 + } 129 + 130 + #[derive(Debug, Serialize)] 131 + #[serde(rename_all = "camelCase")] 132 + pub struct TagAddressResult { 133 + pub link: AddressTag, 134 + pub already_exists: bool, 135 + } 136 + 137 + #[tauri::command] 138 + pub async fn datastore_tag_address( 139 + state: tauri::State<'_, Arc<AppState>>, 140 + address_id: String, 141 + tag_id: String, 142 + ) -> Result<CommandResponse<TagAddressResult>, String> { 143 + let db = state.db.lock().unwrap(); 144 + 145 + match datastore::tag_address(&db, &address_id, &tag_id) { 146 + Ok((link, already_exists)) => Ok(CommandResponse::success(TagAddressResult { link, already_exists })), 147 + Err(e) => Ok(CommandResponse::error(format!("Failed to tag address: {}", e))), 148 + } 149 + } 150 + 151 + #[tauri::command] 152 + pub async fn datastore_untag_address( 153 + state: tauri::State<'_, Arc<AppState>>, 154 + address_id: String, 155 + tag_id: String, 156 + ) -> Result<CommandResponse<bool>, String> { 157 + let db = state.db.lock().unwrap(); 158 + 159 + match datastore::untag_address(&db, &address_id, &tag_id) { 160 + Ok(removed) => Ok(CommandResponse::success(removed)), 161 + Err(e) => Ok(CommandResponse::error(format!("Failed to untag address: {}", e))), 162 + } 163 + } 164 + 165 + #[tauri::command] 166 + pub async fn datastore_get_address_tags( 167 + state: tauri::State<'_, Arc<AppState>>, 168 + address_id: String, 169 + ) -> Result<CommandResponse<Vec<Tag>>, String> { 170 + let db = state.db.lock().unwrap(); 171 + 172 + match datastore::get_address_tags(&db, &address_id) { 173 + Ok(tags) => Ok(CommandResponse::success(tags)), 174 + Err(e) => Ok(CommandResponse::error(format!("Failed to get address tags: {}", e))), 175 + } 176 + } 177 + 178 + // ==================== Generic Table Commands ==================== 179 + 180 + #[tauri::command] 181 + pub async fn datastore_get_table( 182 + state: tauri::State<'_, Arc<AppState>>, 183 + table_name: String, 184 + ) -> Result<CommandResponse<HashMap<String, HashMap<String, serde_json::Value>>>, String> { 185 + let db = state.db.lock().unwrap(); 186 + 187 + match datastore::get_table(&db, &table_name) { 188 + Ok(table) => Ok(CommandResponse::success(table)), 189 + Err(e) => Ok(CommandResponse::error(format!("Failed to get table: {}", e))), 190 + } 191 + } 192 + 193 + #[tauri::command] 194 + pub async fn datastore_set_row( 195 + state: tauri::State<'_, Arc<AppState>>, 196 + table_name: String, 197 + row_id: String, 198 + row_data: HashMap<String, serde_json::Value>, 199 + ) -> Result<CommandResponse<bool>, String> { 200 + let db = state.db.lock().unwrap(); 201 + 202 + match datastore::set_row(&db, &table_name, &row_id, &row_data) { 203 + Ok(()) => Ok(CommandResponse::success(true)), 204 + Err(e) => Ok(CommandResponse::error(format!("Failed to set row: {}", e))), 205 + } 206 + } 207 + 208 + #[tauri::command] 209 + pub async fn datastore_get_stats( 210 + state: tauri::State<'_, Arc<AppState>>, 211 + ) -> Result<CommandResponse<DatastoreStats>, String> { 212 + let db = state.db.lock().unwrap(); 213 + 214 + match datastore::get_stats(&db) { 215 + Ok(stats) => Ok(CommandResponse::success(stats)), 216 + Err(e) => Ok(CommandResponse::error(format!("Failed to get stats: {}", e))), 217 + } 218 + }
+50
backend/tauri/src-tauri/src/commands/mod.rs
··· 1 + //! Tauri command handlers 2 + //! 3 + //! These commands mirror the Electron IPC handlers in backend/electron/ipc.ts 4 + 5 + pub mod datastore; 6 + pub mod window; 7 + 8 + use serde::Serialize; 9 + 10 + /// Standard response format matching Electron's { success, data?, error? } 11 + #[derive(Debug, Serialize)] 12 + pub struct CommandResponse<T> { 13 + pub success: bool, 14 + #[serde(skip_serializing_if = "Option::is_none")] 15 + pub data: Option<T>, 16 + #[serde(skip_serializing_if = "Option::is_none")] 17 + pub error: Option<String>, 18 + } 19 + 20 + impl<T> CommandResponse<T> { 21 + pub fn success(data: T) -> Self { 22 + Self { 23 + success: true, 24 + data: Some(data), 25 + error: None, 26 + } 27 + } 28 + 29 + pub fn error(message: impl Into<String>) -> Self { 30 + Self { 31 + success: false, 32 + data: None, 33 + error: Some(message.into()), 34 + } 35 + } 36 + } 37 + 38 + /// Log message command - forwards renderer logs to stdout 39 + #[tauri::command] 40 + pub fn log_message(source: String, args: Vec<serde_json::Value>) { 41 + let formatted_args: Vec<String> = args 42 + .iter() 43 + .map(|v| match v { 44 + serde_json::Value::String(s) => s.clone(), 45 + _ => v.to_string(), 46 + }) 47 + .collect(); 48 + 49 + println!("[{}] {}", source, formatted_args.join(" ")); 50 + }
+265
backend/tauri/src-tauri/src/commands/window.rs
··· 1 + //! Window management commands 2 + 3 + use super::CommandResponse; 4 + use crate::state::AppState; 5 + use crate::PRELOAD_SCRIPT; 6 + use serde::{Deserialize, Serialize}; 7 + use std::sync::Arc; 8 + use tauri::{Manager, WebviewUrl, WebviewWindowBuilder}; 9 + 10 + /// Window open options - matches Electron's window options 11 + #[derive(Debug, Default, Deserialize)] 12 + #[serde(rename_all = "camelCase")] 13 + pub struct WindowOpenOptions { 14 + pub key: Option<String>, 15 + pub width: Option<f64>, 16 + pub height: Option<f64>, 17 + pub x: Option<f64>, 18 + pub y: Option<f64>, 19 + pub title: Option<String>, 20 + pub modal: Option<bool>, 21 + pub transparent: Option<bool>, 22 + pub decorations: Option<bool>, 23 + pub always_on_top: Option<bool>, 24 + pub visible: Option<bool>, 25 + pub resizable: Option<bool>, 26 + pub keep_live: Option<bool>, 27 + } 28 + 29 + /// Window info returned by list command 30 + #[derive(Debug, Serialize)] 31 + #[serde(rename_all = "camelCase")] 32 + pub struct WindowListItem { 33 + pub id: String, 34 + pub label: String, 35 + pub url: String, 36 + pub source: String, 37 + pub visible: bool, 38 + pub focused: bool, 39 + } 40 + 41 + /// Result of window_open command 42 + #[derive(Debug, Serialize)] 43 + pub struct WindowOpenResult { 44 + pub id: String, 45 + } 46 + 47 + /// Open a new window 48 + #[tauri::command] 49 + pub async fn window_open( 50 + app: tauri::AppHandle, 51 + state: tauri::State<'_, Arc<AppState>>, 52 + source: String, 53 + url: String, 54 + options: Option<WindowOpenOptions>, 55 + ) -> Result<CommandResponse<WindowOpenResult>, String> { 56 + println!("[tauri:window] window_open called: url={}, source={}", url, source); 57 + let options = options.unwrap_or_default(); 58 + 59 + // Generate window label - must only contain alphanumeric, -, /, :, _ 60 + let raw_label = options 61 + .key 62 + .clone() 63 + .unwrap_or_else(|| format!("window_{}", uuid::Uuid::new_v4())); 64 + 65 + // Sanitize label: replace invalid characters with underscores 66 + let label: String = raw_label 67 + .chars() 68 + .map(|c| { 69 + if c.is_alphanumeric() || c == '-' || c == '/' || c == ':' || c == '_' { 70 + c 71 + } else { 72 + '_' 73 + } 74 + }) 75 + .collect(); 76 + 77 + println!("[tauri:window] Using label: {}", label); 78 + 79 + // Check if window with this key already exists and reuse it 80 + if let Some(existing) = app.get_webview_window(&label) { 81 + // Window exists, just show and focus it 82 + let _ = existing.show(); 83 + let _ = existing.set_focus(); 84 + 85 + return Ok(CommandResponse::success(WindowOpenResult { id: label })); 86 + } 87 + 88 + // Parse URL for Tauri 89 + let webview_url = if url.starts_with("peek://") { 90 + WebviewUrl::CustomProtocol(url.parse().map_err(|e| format!("Invalid URL: {}", e))?) 91 + } else if url.starts_with("http://") || url.starts_with("https://") { 92 + WebviewUrl::External(url.parse().map_err(|e| format!("Invalid URL: {}", e))?) 93 + } else { 94 + WebviewUrl::CustomProtocol(format!("peek://app/{}", url).parse().unwrap()) 95 + }; 96 + 97 + // Check headless mode - force windows to be hidden 98 + let visible = if state.headless { 99 + false 100 + } else { 101 + options.visible.unwrap_or(true) 102 + }; 103 + 104 + // Create window builder with preload script injection 105 + let mut builder = WebviewWindowBuilder::new(&app, &label, webview_url.clone()) 106 + .title(options.title.as_deref().unwrap_or("Peek")) 107 + .inner_size( 108 + options.width.unwrap_or(800.0), 109 + options.height.unwrap_or(600.0), 110 + ) 111 + .resizable(options.resizable.unwrap_or(true)) 112 + .visible(visible) 113 + .initialization_script(PRELOAD_SCRIPT); 114 + 115 + // Apply optional settings 116 + if let (Some(x), Some(y)) = (options.x, options.y) { 117 + builder = builder.position(x, y); 118 + } 119 + 120 + if let Some(decorations) = options.decorations { 121 + builder = builder.decorations(decorations); 122 + } 123 + 124 + if options.always_on_top.unwrap_or(false) { 125 + builder = builder.always_on_top(true); 126 + } 127 + 128 + // Build the window 129 + let window = builder 130 + .build() 131 + .map_err(|e| format!("Failed to create window: {}", e))?; 132 + 133 + println!("[tauri:window] Window created: label={}", label); 134 + 135 + // Register in state 136 + let url_str = match webview_url { 137 + WebviewUrl::CustomProtocol(u) => u.to_string(), 138 + WebviewUrl::External(u) => u.to_string(), 139 + _ => url.clone(), 140 + }; 141 + state.register_window(&label, &source, &url_str); 142 + 143 + // Set up close handler to unregister window 144 + let state_clone = state.inner().clone(); 145 + let label_clone = label.clone(); 146 + window.on_window_event(move |event| { 147 + if let tauri::WindowEvent::Destroyed = event { 148 + state_clone.unregister_window(&label_clone); 149 + } 150 + }); 151 + 152 + Ok(CommandResponse::success(WindowOpenResult { id: label })) 153 + } 154 + 155 + /// Close a window 156 + #[tauri::command] 157 + pub async fn window_close( 158 + app: tauri::AppHandle, 159 + state: tauri::State<'_, Arc<AppState>>, 160 + id: Option<String>, 161 + ) -> Result<CommandResponse<bool>, String> { 162 + let label = id.unwrap_or_else(|| "main".to_string()); 163 + 164 + if let Some(window) = app.get_webview_window(&label) { 165 + let _ = window.close(); 166 + state.unregister_window(&label); 167 + Ok(CommandResponse::success(true)) 168 + } else { 169 + Ok(CommandResponse::error(format!( 170 + "Window not found: {}", 171 + label 172 + ))) 173 + } 174 + } 175 + 176 + /// Hide a window 177 + #[tauri::command] 178 + pub async fn window_hide( 179 + app: tauri::AppHandle, 180 + id: Option<String>, 181 + ) -> Result<CommandResponse<bool>, String> { 182 + let label = id.unwrap_or_else(|| "main".to_string()); 183 + 184 + if let Some(window) = app.get_webview_window(&label) { 185 + window 186 + .hide() 187 + .map_err(|e| format!("Failed to hide: {}", e))?; 188 + Ok(CommandResponse::success(true)) 189 + } else { 190 + Ok(CommandResponse::error(format!( 191 + "Window not found: {}", 192 + label 193 + ))) 194 + } 195 + } 196 + 197 + /// Show a window 198 + #[tauri::command] 199 + pub async fn window_show( 200 + app: tauri::AppHandle, 201 + id: Option<String>, 202 + ) -> Result<CommandResponse<bool>, String> { 203 + let label = id.unwrap_or_else(|| "main".to_string()); 204 + 205 + if let Some(window) = app.get_webview_window(&label) { 206 + window 207 + .show() 208 + .map_err(|e| format!("Failed to show: {}", e))?; 209 + Ok(CommandResponse::success(true)) 210 + } else { 211 + Ok(CommandResponse::error(format!( 212 + "Window not found: {}", 213 + label 214 + ))) 215 + } 216 + } 217 + 218 + /// Focus a window 219 + #[tauri::command] 220 + pub async fn window_focus( 221 + app: tauri::AppHandle, 222 + id: Option<String>, 223 + ) -> Result<CommandResponse<bool>, String> { 224 + let label = id.unwrap_or_else(|| "main".to_string()); 225 + 226 + if let Some(window) = app.get_webview_window(&label) { 227 + window 228 + .show() 229 + .map_err(|e| format!("Failed to show: {}", e))?; 230 + window 231 + .set_focus() 232 + .map_err(|e| format!("Failed to focus: {}", e))?; 233 + Ok(CommandResponse::success(true)) 234 + } else { 235 + Ok(CommandResponse::error(format!( 236 + "Window not found: {}", 237 + label 238 + ))) 239 + } 240 + } 241 + 242 + /// List all windows 243 + #[tauri::command] 244 + pub async fn window_list( 245 + app: tauri::AppHandle, 246 + state: tauri::State<'_, Arc<AppState>>, 247 + ) -> Result<CommandResponse<Vec<WindowListItem>>, String> { 248 + let registered = state.list_windows(); 249 + 250 + let windows: Vec<WindowListItem> = registered 251 + .into_iter() 252 + .filter_map(|info| { 253 + app.get_webview_window(&info.label).map(|win| WindowListItem { 254 + id: info.label.clone(), 255 + label: info.label, 256 + url: info.url, 257 + source: info.source, 258 + visible: win.is_visible().unwrap_or(false), 259 + focused: win.is_focused().unwrap_or(false), 260 + }) 261 + }) 262 + .collect(); 263 + 264 + Ok(CommandResponse::success(windows)) 265 + }
+988
backend/tauri/src-tauri/src/datastore.rs
··· 1 + //! SQLite datastore implementation 2 + //! 3 + //! Mirrors the Electron backend's datastore.ts functionality using rusqlite. 4 + 5 + use rusqlite::{params, Connection, Result}; 6 + use serde::{Deserialize, Serialize}; 7 + use std::collections::HashMap; 8 + use std::path::Path; 9 + use url::Url; 10 + 11 + /// SQL schema - matches Electron backend exactly 12 + const CREATE_TABLE_STATEMENTS: &str = r#" 13 + CREATE TABLE IF NOT EXISTS addresses ( 14 + id TEXT PRIMARY KEY, 15 + uri TEXT NOT NULL, 16 + protocol TEXT DEFAULT 'https', 17 + domain TEXT, 18 + path TEXT DEFAULT '', 19 + title TEXT DEFAULT '', 20 + mimeType TEXT DEFAULT 'text/html', 21 + favicon TEXT DEFAULT '', 22 + description TEXT DEFAULT '', 23 + tags TEXT DEFAULT '', 24 + metadata TEXT DEFAULT '{}', 25 + createdAt INTEGER, 26 + updatedAt INTEGER, 27 + lastVisitAt INTEGER DEFAULT 0, 28 + visitCount INTEGER DEFAULT 0, 29 + starred INTEGER DEFAULT 0, 30 + archived INTEGER DEFAULT 0 31 + ); 32 + CREATE INDEX IF NOT EXISTS idx_addresses_uri ON addresses(uri); 33 + CREATE INDEX IF NOT EXISTS idx_addresses_domain ON addresses(domain); 34 + CREATE INDEX IF NOT EXISTS idx_addresses_protocol ON addresses(protocol); 35 + CREATE INDEX IF NOT EXISTS idx_addresses_lastVisitAt ON addresses(lastVisitAt); 36 + CREATE INDEX IF NOT EXISTS idx_addresses_visitCount ON addresses(visitCount); 37 + CREATE INDEX IF NOT EXISTS idx_addresses_starred ON addresses(starred); 38 + 39 + CREATE TABLE IF NOT EXISTS visits ( 40 + id TEXT PRIMARY KEY, 41 + addressId TEXT, 42 + timestamp INTEGER, 43 + duration INTEGER DEFAULT 0, 44 + source TEXT DEFAULT 'direct', 45 + sourceId TEXT DEFAULT '', 46 + windowType TEXT DEFAULT 'main', 47 + metadata TEXT DEFAULT '{}', 48 + scrollDepth INTEGER DEFAULT 0, 49 + interacted INTEGER DEFAULT 0 50 + ); 51 + CREATE INDEX IF NOT EXISTS idx_visits_addressId ON visits(addressId); 52 + CREATE INDEX IF NOT EXISTS idx_visits_timestamp ON visits(timestamp); 53 + CREATE INDEX IF NOT EXISTS idx_visits_source ON visits(source); 54 + 55 + CREATE TABLE IF NOT EXISTS content ( 56 + id TEXT PRIMARY KEY, 57 + title TEXT DEFAULT 'Untitled', 58 + content TEXT DEFAULT '', 59 + mimeType TEXT DEFAULT 'text/plain', 60 + contentType TEXT DEFAULT 'plain', 61 + language TEXT DEFAULT '', 62 + encoding TEXT DEFAULT 'utf-8', 63 + tags TEXT DEFAULT '', 64 + addressRefs TEXT DEFAULT '', 65 + parentId TEXT DEFAULT '', 66 + metadata TEXT DEFAULT '{}', 67 + createdAt INTEGER, 68 + updatedAt INTEGER, 69 + syncPath TEXT DEFAULT '', 70 + synced INTEGER DEFAULT 0, 71 + starred INTEGER DEFAULT 0, 72 + archived INTEGER DEFAULT 0 73 + ); 74 + CREATE INDEX IF NOT EXISTS idx_content_contentType ON content(contentType); 75 + CREATE INDEX IF NOT EXISTS idx_content_mimeType ON content(mimeType); 76 + CREATE INDEX IF NOT EXISTS idx_content_synced ON content(synced); 77 + CREATE INDEX IF NOT EXISTS idx_content_updatedAt ON content(updatedAt); 78 + 79 + CREATE TABLE IF NOT EXISTS tags ( 80 + id TEXT PRIMARY KEY, 81 + name TEXT NOT NULL, 82 + slug TEXT, 83 + color TEXT DEFAULT '#999999', 84 + parentId TEXT DEFAULT '', 85 + description TEXT DEFAULT '', 86 + metadata TEXT DEFAULT '{}', 87 + createdAt INTEGER, 88 + updatedAt INTEGER, 89 + frequency INTEGER DEFAULT 0, 90 + lastUsedAt INTEGER DEFAULT 0, 91 + frecencyScore INTEGER DEFAULT 0 92 + ); 93 + CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name); 94 + CREATE INDEX IF NOT EXISTS idx_tags_slug ON tags(slug); 95 + CREATE INDEX IF NOT EXISTS idx_tags_parentId ON tags(parentId); 96 + CREATE INDEX IF NOT EXISTS idx_tags_frecencyScore ON tags(frecencyScore); 97 + 98 + CREATE TABLE IF NOT EXISTS address_tags ( 99 + id TEXT PRIMARY KEY, 100 + addressId TEXT NOT NULL, 101 + tagId TEXT NOT NULL, 102 + createdAt INTEGER 103 + ); 104 + CREATE INDEX IF NOT EXISTS idx_address_tags_addressId ON address_tags(addressId); 105 + CREATE INDEX IF NOT EXISTS idx_address_tags_tagId ON address_tags(tagId); 106 + CREATE UNIQUE INDEX IF NOT EXISTS idx_address_tags_unique ON address_tags(addressId, tagId); 107 + 108 + CREATE TABLE IF NOT EXISTS blobs ( 109 + id TEXT PRIMARY KEY, 110 + filename TEXT, 111 + mimeType TEXT, 112 + mediaType TEXT, 113 + size INTEGER, 114 + hash TEXT, 115 + extension TEXT, 116 + path TEXT, 117 + addressId TEXT DEFAULT '', 118 + contentId TEXT DEFAULT '', 119 + tags TEXT DEFAULT '', 120 + metadata TEXT DEFAULT '{}', 121 + createdAt INTEGER, 122 + width INTEGER DEFAULT 0, 123 + height INTEGER DEFAULT 0, 124 + duration INTEGER DEFAULT 0, 125 + thumbnail TEXT DEFAULT '' 126 + ); 127 + CREATE INDEX IF NOT EXISTS idx_blobs_mediaType ON blobs(mediaType); 128 + CREATE INDEX IF NOT EXISTS idx_blobs_mimeType ON blobs(mimeType); 129 + CREATE INDEX IF NOT EXISTS idx_blobs_addressId ON blobs(addressId); 130 + CREATE INDEX IF NOT EXISTS idx_blobs_contentId ON blobs(contentId); 131 + 132 + CREATE TABLE IF NOT EXISTS scripts_data ( 133 + id TEXT PRIMARY KEY, 134 + scriptId TEXT, 135 + scriptName TEXT, 136 + addressId TEXT, 137 + selector TEXT, 138 + content TEXT, 139 + contentType TEXT DEFAULT 'text', 140 + metadata TEXT DEFAULT '{}', 141 + extractedAt INTEGER, 142 + previousValue TEXT DEFAULT '', 143 + changed INTEGER DEFAULT 0 144 + ); 145 + CREATE INDEX IF NOT EXISTS idx_scripts_data_scriptId ON scripts_data(scriptId); 146 + CREATE INDEX IF NOT EXISTS idx_scripts_data_addressId ON scripts_data(addressId); 147 + CREATE INDEX IF NOT EXISTS idx_scripts_data_changed ON scripts_data(changed); 148 + 149 + CREATE TABLE IF NOT EXISTS feeds ( 150 + id TEXT PRIMARY KEY, 151 + name TEXT, 152 + description TEXT DEFAULT '', 153 + type TEXT, 154 + query TEXT DEFAULT '', 155 + schedule TEXT DEFAULT '', 156 + source TEXT DEFAULT 'internal', 157 + tags TEXT DEFAULT '', 158 + metadata TEXT DEFAULT '{}', 159 + createdAt INTEGER, 160 + updatedAt INTEGER, 161 + lastFetchedAt INTEGER DEFAULT 0, 162 + enabled INTEGER DEFAULT 1 163 + ); 164 + CREATE INDEX IF NOT EXISTS idx_feeds_type ON feeds(type); 165 + CREATE INDEX IF NOT EXISTS idx_feeds_enabled ON feeds(enabled); 166 + 167 + CREATE TABLE IF NOT EXISTS extensions ( 168 + id TEXT PRIMARY KEY, 169 + name TEXT, 170 + description TEXT DEFAULT '', 171 + version TEXT DEFAULT '1.0.0', 172 + path TEXT, 173 + backgroundUrl TEXT DEFAULT '', 174 + settingsUrl TEXT DEFAULT '', 175 + iconPath TEXT DEFAULT '', 176 + builtin INTEGER DEFAULT 0, 177 + enabled INTEGER DEFAULT 1, 178 + status TEXT DEFAULT 'installed', 179 + installedAt INTEGER, 180 + updatedAt INTEGER, 181 + lastErrorAt INTEGER DEFAULT 0, 182 + lastError TEXT DEFAULT '', 183 + metadata TEXT DEFAULT '{}' 184 + ); 185 + CREATE INDEX IF NOT EXISTS idx_extensions_enabled ON extensions(enabled); 186 + CREATE INDEX IF NOT EXISTS idx_extensions_status ON extensions(status); 187 + CREATE INDEX IF NOT EXISTS idx_extensions_builtin ON extensions(builtin); 188 + 189 + CREATE TABLE IF NOT EXISTS extension_settings ( 190 + id TEXT PRIMARY KEY, 191 + extensionId TEXT NOT NULL, 192 + key TEXT NOT NULL, 193 + value TEXT, 194 + updatedAt INTEGER 195 + ); 196 + CREATE INDEX IF NOT EXISTS idx_extension_settings_extensionId ON extension_settings(extensionId); 197 + CREATE UNIQUE INDEX IF NOT EXISTS idx_extension_settings_unique ON extension_settings(extensionId, key); 198 + "#; 199 + 200 + // ==================== Types ==================== 201 + 202 + #[derive(Debug, Clone, Serialize, Deserialize)] 203 + #[serde(rename_all = "camelCase")] 204 + pub struct Address { 205 + pub id: String, 206 + pub uri: String, 207 + pub protocol: String, 208 + pub domain: Option<String>, 209 + pub path: String, 210 + pub title: String, 211 + pub mime_type: String, 212 + pub favicon: String, 213 + pub description: String, 214 + pub tags: String, 215 + pub metadata: String, 216 + pub created_at: i64, 217 + pub updated_at: i64, 218 + pub last_visit_at: i64, 219 + pub visit_count: i64, 220 + pub starred: i64, 221 + pub archived: i64, 222 + } 223 + 224 + #[derive(Debug, Clone, Serialize, Deserialize)] 225 + #[serde(rename_all = "camelCase")] 226 + pub struct Visit { 227 + pub id: String, 228 + pub address_id: String, 229 + pub timestamp: i64, 230 + pub duration: i64, 231 + pub source: String, 232 + pub source_id: String, 233 + pub window_type: String, 234 + pub metadata: String, 235 + pub scroll_depth: i64, 236 + pub interacted: i64, 237 + } 238 + 239 + #[derive(Debug, Clone, Serialize, Deserialize)] 240 + #[serde(rename_all = "camelCase")] 241 + pub struct Tag { 242 + pub id: String, 243 + pub name: String, 244 + pub slug: Option<String>, 245 + pub color: String, 246 + pub parent_id: String, 247 + pub description: String, 248 + pub metadata: String, 249 + pub created_at: i64, 250 + pub updated_at: i64, 251 + pub frequency: i64, 252 + pub last_used_at: i64, 253 + pub frecency_score: i64, 254 + } 255 + 256 + #[derive(Debug, Clone, Serialize, Deserialize)] 257 + #[serde(rename_all = "camelCase")] 258 + pub struct AddressTag { 259 + pub id: String, 260 + pub address_id: String, 261 + pub tag_id: String, 262 + pub created_at: i64, 263 + } 264 + 265 + #[derive(Debug, Clone, Serialize, Deserialize)] 266 + #[serde(rename_all = "camelCase")] 267 + pub struct DatastoreStats { 268 + pub total_addresses: i64, 269 + pub total_visits: i64, 270 + pub avg_visit_duration: f64, 271 + pub total_content: i64, 272 + pub synced_content: i64, 273 + } 274 + 275 + #[derive(Debug, Clone, Default, Serialize, Deserialize)] 276 + #[serde(rename_all = "camelCase")] 277 + pub struct AddressOptions { 278 + pub protocol: Option<String>, 279 + pub domain: Option<String>, 280 + pub path: Option<String>, 281 + pub title: Option<String>, 282 + pub mime_type: Option<String>, 283 + pub favicon: Option<String>, 284 + pub description: Option<String>, 285 + pub tags: Option<String>, 286 + pub metadata: Option<String>, 287 + pub last_visit_at: Option<i64>, 288 + pub visit_count: Option<i64>, 289 + pub starred: Option<i64>, 290 + pub archived: Option<i64>, 291 + } 292 + 293 + #[derive(Debug, Clone, Default, Serialize, Deserialize)] 294 + #[serde(rename_all = "camelCase")] 295 + pub struct AddressFilter { 296 + pub domain: Option<String>, 297 + pub protocol: Option<String>, 298 + pub starred: Option<i64>, 299 + pub tag: Option<String>, 300 + pub sort_by: Option<String>, 301 + pub limit: Option<i64>, 302 + } 303 + 304 + #[derive(Debug, Clone, Default, Serialize, Deserialize)] 305 + #[serde(rename_all = "camelCase")] 306 + pub struct VisitOptions { 307 + pub timestamp: Option<i64>, 308 + pub duration: Option<i64>, 309 + pub source: Option<String>, 310 + pub source_id: Option<String>, 311 + pub window_type: Option<String>, 312 + pub metadata: Option<String>, 313 + pub scroll_depth: Option<i64>, 314 + pub interacted: Option<i64>, 315 + } 316 + 317 + #[derive(Debug, Clone, Default, Serialize, Deserialize)] 318 + #[serde(rename_all = "camelCase")] 319 + pub struct VisitFilter { 320 + pub address_id: Option<String>, 321 + pub source: Option<String>, 322 + pub since: Option<i64>, 323 + pub limit: Option<i64>, 324 + } 325 + 326 + // ==================== Helpers ==================== 327 + 328 + pub fn generate_id(prefix: &str) -> String { 329 + format!( 330 + "{}_{}_{}", 331 + prefix, 332 + chrono::Utc::now().timestamp_millis(), 333 + uuid::Uuid::new_v4().to_string().split('-').next().unwrap() 334 + ) 335 + } 336 + 337 + pub fn now() -> i64 { 338 + chrono::Utc::now().timestamp_millis() 339 + } 340 + 341 + pub fn parse_url(uri: &str) -> (String, String, String) { 342 + match Url::parse(uri) { 343 + Ok(url) => ( 344 + url.scheme().to_string(), 345 + url.host_str().unwrap_or(uri).to_string(), 346 + format!( 347 + "{}{}{}", 348 + url.path(), 349 + url.query().map(|q| format!("?{}", q)).unwrap_or_default(), 350 + url.fragment() 351 + .map(|f| format!("#{}", f)) 352 + .unwrap_or_default() 353 + ), 354 + ), 355 + Err(_) => ("unknown".to_string(), uri.to_string(), String::new()), 356 + } 357 + } 358 + 359 + pub fn normalize_url(uri: &str) -> String { 360 + match Url::parse(uri) { 361 + Ok(mut url) => { 362 + // Remove trailing slash from path (except for root) 363 + let path = url.path().to_string(); 364 + if path != "/" && path.ends_with('/') { 365 + url.set_path(&path[..path.len() - 1]); 366 + } 367 + 368 + // Remove default ports 369 + if (url.scheme() == "http" && url.port() == Some(80)) 370 + || (url.scheme() == "https" && url.port() == Some(443)) 371 + { 372 + let _ = url.set_port(None); 373 + } 374 + 375 + url.to_string() 376 + } 377 + Err(_) => uri.to_string(), 378 + } 379 + } 380 + 381 + pub fn calculate_frecency(frequency: i64, last_used_at: i64) -> i64 { 382 + let current_time = now(); 383 + let days_since_use = (current_time - last_used_at) as f64 / (1000.0 * 60.0 * 60.0 * 24.0); 384 + let decay_factor = 1.0 / (1.0 + days_since_use / 7.0); 385 + (frequency as f64 * 10.0 * decay_factor).round() as i64 386 + } 387 + 388 + // ==================== Database Initialization ==================== 389 + 390 + pub fn init_database(db_path: &Path) -> Result<Connection> { 391 + let conn = Connection::open(db_path)?; 392 + 393 + // Enable WAL mode for better concurrent access 394 + conn.pragma_update(None, "journal_mode", "WAL")?; 395 + 396 + // Execute schema 397 + conn.execute_batch(CREATE_TABLE_STATEMENTS)?; 398 + 399 + println!("[tauri] Database initialized successfully"); 400 + Ok(conn) 401 + } 402 + 403 + // ==================== Address Operations ==================== 404 + 405 + pub fn add_address(conn: &Connection, uri: &str, options: &AddressOptions) -> Result<String> { 406 + let normalized_uri = normalize_url(uri); 407 + let (protocol, domain, path) = parse_url(&normalized_uri); 408 + let address_id = generate_id("addr"); 409 + let timestamp = now(); 410 + 411 + conn.execute( 412 + r#"INSERT INTO addresses 413 + (id, uri, protocol, domain, path, title, mimeType, favicon, description, tags, metadata, createdAt, updatedAt, lastVisitAt, visitCount, starred, archived) 414 + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)"#, 415 + params![ 416 + address_id, 417 + normalized_uri, 418 + options.protocol.as_deref().unwrap_or(&protocol), 419 + options.domain.as_deref().unwrap_or(&domain), 420 + options.path.as_deref().unwrap_or(&path), 421 + options.title.as_deref().unwrap_or(""), 422 + options.mime_type.as_deref().unwrap_or("text/html"), 423 + options.favicon.as_deref().unwrap_or(""), 424 + options.description.as_deref().unwrap_or(""), 425 + options.tags.as_deref().unwrap_or(""), 426 + options.metadata.as_deref().unwrap_or("{}"), 427 + timestamp, 428 + timestamp, 429 + options.last_visit_at.unwrap_or(0), 430 + options.visit_count.unwrap_or(0), 431 + options.starred.unwrap_or(0), 432 + options.archived.unwrap_or(0), 433 + ], 434 + )?; 435 + 436 + Ok(address_id) 437 + } 438 + 439 + pub fn get_address(conn: &Connection, id: &str) -> Result<Option<Address>> { 440 + let mut stmt = conn.prepare( 441 + "SELECT id, uri, protocol, domain, path, title, mimeType, favicon, description, tags, metadata, createdAt, updatedAt, lastVisitAt, visitCount, starred, archived FROM addresses WHERE id = ?1", 442 + )?; 443 + 444 + let result = stmt.query_row(params![id], |row| { 445 + Ok(Address { 446 + id: row.get(0)?, 447 + uri: row.get(1)?, 448 + protocol: row.get(2)?, 449 + domain: row.get(3)?, 450 + path: row.get(4)?, 451 + title: row.get(5)?, 452 + mime_type: row.get(6)?, 453 + favicon: row.get(7)?, 454 + description: row.get(8)?, 455 + tags: row.get(9)?, 456 + metadata: row.get(10)?, 457 + created_at: row.get(11)?, 458 + updated_at: row.get(12)?, 459 + last_visit_at: row.get(13)?, 460 + visit_count: row.get(14)?, 461 + starred: row.get(15)?, 462 + archived: row.get(16)?, 463 + }) 464 + }); 465 + 466 + match result { 467 + Ok(addr) => Ok(Some(addr)), 468 + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), 469 + Err(e) => Err(e), 470 + } 471 + } 472 + 473 + pub fn update_address( 474 + conn: &Connection, 475 + id: &str, 476 + updates: &HashMap<String, serde_json::Value>, 477 + ) -> Result<bool> { 478 + if updates.is_empty() { 479 + return Ok(false); 480 + } 481 + 482 + let mut set_clauses = vec!["updatedAt = ?1".to_string()]; 483 + let mut params_vec: Vec<Box<dyn rusqlite::ToSql>> = vec![Box::new(now())]; 484 + let mut param_idx = 2; 485 + 486 + for (key, value) in updates { 487 + set_clauses.push(format!("{} = ?{}", key, param_idx)); 488 + match value { 489 + serde_json::Value::String(s) => params_vec.push(Box::new(s.clone())), 490 + serde_json::Value::Number(n) => { 491 + if let Some(i) = n.as_i64() { 492 + params_vec.push(Box::new(i)); 493 + } else if let Some(f) = n.as_f64() { 494 + params_vec.push(Box::new(f)); 495 + } 496 + } 497 + _ => params_vec.push(Box::new(value.to_string())), 498 + } 499 + param_idx += 1; 500 + } 501 + 502 + let sql = format!( 503 + "UPDATE addresses SET {} WHERE id = ?{}", 504 + set_clauses.join(", "), 505 + param_idx 506 + ); 507 + params_vec.push(Box::new(id.to_string())); 508 + 509 + let params_ref: Vec<&dyn rusqlite::ToSql> = params_vec.iter().map(|p| p.as_ref()).collect(); 510 + let rows = conn.execute(&sql, params_ref.as_slice())?; 511 + 512 + Ok(rows > 0) 513 + } 514 + 515 + pub fn query_addresses(conn: &Connection, filter: &AddressFilter) -> Result<Vec<Address>> { 516 + let mut sql = "SELECT id, uri, protocol, domain, path, title, mimeType, favicon, description, tags, metadata, createdAt, updatedAt, lastVisitAt, visitCount, starred, archived FROM addresses WHERE 1=1".to_string(); 517 + let mut params_vec: Vec<Box<dyn rusqlite::ToSql>> = vec![]; 518 + 519 + if let Some(domain) = &filter.domain { 520 + sql.push_str(&format!(" AND domain = ?{}", params_vec.len() + 1)); 521 + params_vec.push(Box::new(domain.clone())); 522 + } 523 + if let Some(protocol) = &filter.protocol { 524 + sql.push_str(&format!(" AND protocol = ?{}", params_vec.len() + 1)); 525 + params_vec.push(Box::new(protocol.clone())); 526 + } 527 + if let Some(starred) = filter.starred { 528 + sql.push_str(&format!(" AND starred = ?{}", params_vec.len() + 1)); 529 + params_vec.push(Box::new(starred)); 530 + } 531 + if let Some(tag) = &filter.tag { 532 + sql.push_str(&format!(" AND tags LIKE ?{}", params_vec.len() + 1)); 533 + params_vec.push(Box::new(format!("%{}%", tag))); 534 + } 535 + 536 + let sort = match filter.sort_by.as_deref() { 537 + Some("lastVisit") => "lastVisitAt DESC", 538 + Some("visitCount") => "visitCount DESC", 539 + Some("created") => "createdAt DESC", 540 + _ => "updatedAt DESC", 541 + }; 542 + sql.push_str(&format!(" ORDER BY {}", sort)); 543 + 544 + if let Some(limit) = filter.limit { 545 + sql.push_str(&format!(" LIMIT {}", limit)); 546 + } 547 + 548 + let params_ref: Vec<&dyn rusqlite::ToSql> = params_vec.iter().map(|p| p.as_ref()).collect(); 549 + let mut stmt = conn.prepare(&sql)?; 550 + 551 + let addresses = stmt.query_map(params_ref.as_slice(), |row| { 552 + Ok(Address { 553 + id: row.get(0)?, 554 + uri: row.get(1)?, 555 + protocol: row.get(2)?, 556 + domain: row.get(3)?, 557 + path: row.get(4)?, 558 + title: row.get(5)?, 559 + mime_type: row.get(6)?, 560 + favicon: row.get(7)?, 561 + description: row.get(8)?, 562 + tags: row.get(9)?, 563 + metadata: row.get(10)?, 564 + created_at: row.get(11)?, 565 + updated_at: row.get(12)?, 566 + last_visit_at: row.get(13)?, 567 + visit_count: row.get(14)?, 568 + starred: row.get(15)?, 569 + archived: row.get(16)?, 570 + }) 571 + })?; 572 + 573 + addresses.collect() 574 + } 575 + 576 + // ==================== Visit Operations ==================== 577 + 578 + pub fn add_visit(conn: &Connection, address_id: &str, options: &VisitOptions) -> Result<String> { 579 + let visit_id = generate_id("visit"); 580 + let timestamp = options.timestamp.unwrap_or_else(now); 581 + 582 + conn.execute( 583 + r#"INSERT INTO visits (id, addressId, timestamp, duration, source, sourceId, windowType, metadata, scrollDepth, interacted) 584 + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)"#, 585 + params![ 586 + visit_id, 587 + address_id, 588 + timestamp, 589 + options.duration.unwrap_or(0), 590 + options.source.as_deref().unwrap_or("direct"), 591 + options.source_id.as_deref().unwrap_or(""), 592 + options.window_type.as_deref().unwrap_or("main"), 593 + options.metadata.as_deref().unwrap_or("{}"), 594 + options.scroll_depth.unwrap_or(0), 595 + options.interacted.unwrap_or(0), 596 + ], 597 + )?; 598 + 599 + // Update address visit stats 600 + conn.execute( 601 + "UPDATE addresses SET lastVisitAt = ?1, visitCount = visitCount + 1, updatedAt = ?1 WHERE id = ?2", 602 + params![timestamp, address_id], 603 + )?; 604 + 605 + Ok(visit_id) 606 + } 607 + 608 + pub fn query_visits(conn: &Connection, filter: &VisitFilter) -> Result<Vec<Visit>> { 609 + let mut sql = "SELECT id, addressId, timestamp, duration, source, sourceId, windowType, metadata, scrollDepth, interacted FROM visits WHERE 1=1".to_string(); 610 + let mut params_vec: Vec<Box<dyn rusqlite::ToSql>> = vec![]; 611 + 612 + if let Some(address_id) = &filter.address_id { 613 + sql.push_str(&format!(" AND addressId = ?{}", params_vec.len() + 1)); 614 + params_vec.push(Box::new(address_id.clone())); 615 + } 616 + if let Some(source) = &filter.source { 617 + sql.push_str(&format!(" AND source = ?{}", params_vec.len() + 1)); 618 + params_vec.push(Box::new(source.clone())); 619 + } 620 + if let Some(since) = filter.since { 621 + sql.push_str(&format!(" AND timestamp >= ?{}", params_vec.len() + 1)); 622 + params_vec.push(Box::new(since)); 623 + } 624 + 625 + sql.push_str(" ORDER BY timestamp DESC"); 626 + 627 + if let Some(limit) = filter.limit { 628 + sql.push_str(&format!(" LIMIT {}", limit)); 629 + } 630 + 631 + let params_ref: Vec<&dyn rusqlite::ToSql> = params_vec.iter().map(|p| p.as_ref()).collect(); 632 + let mut stmt = conn.prepare(&sql)?; 633 + 634 + let visits = stmt.query_map(params_ref.as_slice(), |row| { 635 + Ok(Visit { 636 + id: row.get(0)?, 637 + address_id: row.get(1)?, 638 + timestamp: row.get(2)?, 639 + duration: row.get(3)?, 640 + source: row.get(4)?, 641 + source_id: row.get(5)?, 642 + window_type: row.get(6)?, 643 + metadata: row.get(7)?, 644 + scroll_depth: row.get(8)?, 645 + interacted: row.get(9)?, 646 + }) 647 + })?; 648 + 649 + visits.collect() 650 + } 651 + 652 + // ==================== Tag Operations ==================== 653 + 654 + pub fn get_or_create_tag(conn: &Connection, name: &str) -> Result<(Tag, bool)> { 655 + let slug = name.to_lowercase().trim().replace(' ', "-"); 656 + let timestamp = now(); 657 + 658 + // Check if tag exists 659 + let mut stmt = 660 + conn.prepare("SELECT id, name, slug, color, parentId, description, metadata, createdAt, updatedAt, frequency, lastUsedAt, frecencyScore FROM tags WHERE LOWER(name) = LOWER(?1)")?; 661 + 662 + let existing = stmt.query_row(params![name], |row| { 663 + Ok(Tag { 664 + id: row.get(0)?, 665 + name: row.get(1)?, 666 + slug: row.get(2)?, 667 + color: row.get(3)?, 668 + parent_id: row.get(4)?, 669 + description: row.get(5)?, 670 + metadata: row.get(6)?, 671 + created_at: row.get(7)?, 672 + updated_at: row.get(8)?, 673 + frequency: row.get(9)?, 674 + last_used_at: row.get(10)?, 675 + frecency_score: row.get(11)?, 676 + }) 677 + }); 678 + 679 + match existing { 680 + Ok(tag) => Ok((tag, false)), 681 + Err(rusqlite::Error::QueryReturnedNoRows) => { 682 + let tag_id = generate_id("tag"); 683 + conn.execute( 684 + r#"INSERT INTO tags (id, name, slug, color, parentId, description, metadata, createdAt, updatedAt, frequency, lastUsedAt, frecencyScore) 685 + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)"#, 686 + params![ 687 + tag_id, 688 + name.trim(), 689 + slug, 690 + "#999999", 691 + "", 692 + "", 693 + "{}", 694 + timestamp, 695 + timestamp, 696 + 0, 697 + 0, 698 + 0 699 + ], 700 + )?; 701 + 702 + let tag = Tag { 703 + id: tag_id, 704 + name: name.trim().to_string(), 705 + slug: Some(slug), 706 + color: "#999999".to_string(), 707 + parent_id: String::new(), 708 + description: String::new(), 709 + metadata: "{}".to_string(), 710 + created_at: timestamp, 711 + updated_at: timestamp, 712 + frequency: 0, 713 + last_used_at: 0, 714 + frecency_score: 0, 715 + }; 716 + Ok((tag, true)) 717 + } 718 + Err(e) => Err(e), 719 + } 720 + } 721 + 722 + pub fn tag_address(conn: &Connection, address_id: &str, tag_id: &str) -> Result<(AddressTag, bool)> { 723 + let timestamp = now(); 724 + 725 + // Check if link already exists 726 + let mut stmt = conn.prepare( 727 + "SELECT id, addressId, tagId, createdAt FROM address_tags WHERE addressId = ?1 AND tagId = ?2", 728 + )?; 729 + 730 + let existing = stmt.query_row(params![address_id, tag_id], |row| { 731 + Ok(AddressTag { 732 + id: row.get(0)?, 733 + address_id: row.get(1)?, 734 + tag_id: row.get(2)?, 735 + created_at: row.get(3)?, 736 + }) 737 + }); 738 + 739 + match existing { 740 + Ok(link) => Ok((link, true)), 741 + Err(rusqlite::Error::QueryReturnedNoRows) => { 742 + let link_id = generate_id("address_tag"); 743 + conn.execute( 744 + "INSERT INTO address_tags (id, addressId, tagId, createdAt) VALUES (?1, ?2, ?3, ?4)", 745 + params![link_id, address_id, tag_id, timestamp], 746 + )?; 747 + 748 + // Update tag frequency 749 + conn.execute( 750 + "UPDATE tags SET frequency = frequency + 1, lastUsedAt = ?1, frecencyScore = ?2, updatedAt = ?1 WHERE id = ?3", 751 + params![timestamp, calculate_frecency(1, timestamp), tag_id], 752 + )?; 753 + 754 + let link = AddressTag { 755 + id: link_id, 756 + address_id: address_id.to_string(), 757 + tag_id: tag_id.to_string(), 758 + created_at: timestamp, 759 + }; 760 + Ok((link, false)) 761 + } 762 + Err(e) => Err(e), 763 + } 764 + } 765 + 766 + pub fn untag_address(conn: &Connection, address_id: &str, tag_id: &str) -> Result<bool> { 767 + let rows = conn.execute( 768 + "DELETE FROM address_tags WHERE addressId = ?1 AND tagId = ?2", 769 + params![address_id, tag_id], 770 + )?; 771 + Ok(rows > 0) 772 + } 773 + 774 + pub fn get_address_tags(conn: &Connection, address_id: &str) -> Result<Vec<Tag>> { 775 + let mut stmt = conn.prepare( 776 + r#"SELECT t.id, t.name, t.slug, t.color, t.parentId, t.description, t.metadata, t.createdAt, t.updatedAt, t.frequency, t.lastUsedAt, t.frecencyScore 777 + FROM tags t 778 + JOIN address_tags at ON t.id = at.tagId 779 + WHERE at.addressId = ?1"#, 780 + )?; 781 + 782 + let tags = stmt.query_map(params![address_id], |row| { 783 + Ok(Tag { 784 + id: row.get(0)?, 785 + name: row.get(1)?, 786 + slug: row.get(2)?, 787 + color: row.get(3)?, 788 + parent_id: row.get(4)?, 789 + description: row.get(5)?, 790 + metadata: row.get(6)?, 791 + created_at: row.get(7)?, 792 + updated_at: row.get(8)?, 793 + frequency: row.get(9)?, 794 + last_used_at: row.get(10)?, 795 + frecency_score: row.get(11)?, 796 + }) 797 + })?; 798 + 799 + tags.collect() 800 + } 801 + 802 + // ==================== Generic Table Operations ==================== 803 + 804 + pub fn get_table( 805 + conn: &Connection, 806 + table_name: &str, 807 + ) -> Result<HashMap<String, HashMap<String, serde_json::Value>>> { 808 + // Validate table name to prevent SQL injection 809 + let valid_tables = [ 810 + "addresses", 811 + "visits", 812 + "content", 813 + "tags", 814 + "address_tags", 815 + "blobs", 816 + "scripts_data", 817 + "feeds", 818 + "extensions", 819 + "extension_settings", 820 + ]; 821 + if !valid_tables.contains(&table_name) { 822 + return Err(rusqlite::Error::InvalidParameterName(format!( 823 + "Invalid table: {}", 824 + table_name 825 + ))); 826 + } 827 + 828 + let sql = format!("SELECT * FROM {}", table_name); 829 + let mut stmt = conn.prepare(&sql)?; 830 + 831 + let column_names: Vec<String> = stmt.column_names().iter().map(|s| s.to_string()).collect(); 832 + let mut result: HashMap<String, HashMap<String, serde_json::Value>> = HashMap::new(); 833 + 834 + let mut rows = stmt.query([])?; 835 + while let Some(row) = rows.next()? { 836 + let id: String = row.get(0)?; 837 + let mut row_data: HashMap<String, serde_json::Value> = HashMap::new(); 838 + 839 + for (i, col_name) in column_names.iter().enumerate() { 840 + let value: rusqlite::types::Value = row.get(i)?; 841 + let json_value = match value { 842 + rusqlite::types::Value::Null => serde_json::Value::Null, 843 + rusqlite::types::Value::Integer(i) => serde_json::Value::Number(i.into()), 844 + rusqlite::types::Value::Real(f) => { 845 + serde_json::Number::from_f64(f).map_or(serde_json::Value::Null, |n| { 846 + serde_json::Value::Number(n) 847 + }) 848 + } 849 + rusqlite::types::Value::Text(s) => serde_json::Value::String(s), 850 + rusqlite::types::Value::Blob(b) => { 851 + serde_json::Value::String(base64::encode_config(&b, base64::STANDARD)) 852 + } 853 + }; 854 + row_data.insert(col_name.clone(), json_value); 855 + } 856 + 857 + result.insert(id, row_data); 858 + } 859 + 860 + Ok(result) 861 + } 862 + 863 + pub fn set_row( 864 + conn: &Connection, 865 + table_name: &str, 866 + row_id: &str, 867 + row_data: &HashMap<String, serde_json::Value>, 868 + ) -> Result<()> { 869 + // Validate table name 870 + let valid_tables = [ 871 + "addresses", 872 + "visits", 873 + "content", 874 + "tags", 875 + "address_tags", 876 + "blobs", 877 + "scripts_data", 878 + "feeds", 879 + "extensions", 880 + "extension_settings", 881 + ]; 882 + if !valid_tables.contains(&table_name) { 883 + return Err(rusqlite::Error::InvalidParameterName(format!( 884 + "Invalid table: {}", 885 + table_name 886 + ))); 887 + } 888 + 889 + let mut columns = vec!["id".to_string()]; 890 + let mut placeholders = vec!["?1".to_string()]; 891 + let mut values: Vec<Box<dyn rusqlite::ToSql>> = vec![Box::new(row_id.to_string())]; 892 + let mut idx = 2; 893 + 894 + for (key, value) in row_data { 895 + if key == "id" { 896 + continue; 897 + } 898 + columns.push(key.clone()); 899 + placeholders.push(format!("?{}", idx)); 900 + match value { 901 + serde_json::Value::Null => values.push(Box::new(Option::<String>::None)), 902 + serde_json::Value::Bool(b) => values.push(Box::new(if *b { 1i64 } else { 0i64 })), 903 + serde_json::Value::Number(n) => { 904 + if let Some(i) = n.as_i64() { 905 + values.push(Box::new(i)); 906 + } else if let Some(f) = n.as_f64() { 907 + values.push(Box::new(f)); 908 + } else { 909 + values.push(Box::new(n.to_string())); 910 + } 911 + } 912 + serde_json::Value::String(s) => values.push(Box::new(s.clone())), 913 + _ => values.push(Box::new(value.to_string())), 914 + } 915 + idx += 1; 916 + } 917 + 918 + let sql = format!( 919 + "INSERT OR REPLACE INTO {} ({}) VALUES ({})", 920 + table_name, 921 + columns.join(", "), 922 + placeholders.join(", ") 923 + ); 924 + 925 + let params_ref: Vec<&dyn rusqlite::ToSql> = values.iter().map(|p| p.as_ref()).collect(); 926 + conn.execute(&sql, params_ref.as_slice())?; 927 + 928 + Ok(()) 929 + } 930 + 931 + pub fn get_stats(conn: &Connection) -> Result<DatastoreStats> { 932 + let total_addresses: i64 = 933 + conn.query_row("SELECT COUNT(*) FROM addresses", [], |row| row.get(0))?; 934 + let total_visits: i64 = conn.query_row("SELECT COUNT(*) FROM visits", [], |row| row.get(0))?; 935 + let avg_visit_duration: f64 = conn 936 + .query_row("SELECT AVG(duration) FROM visits", [], |row| { 937 + row.get::<_, Option<f64>>(0) 938 + })? 939 + .unwrap_or(0.0); 940 + let total_content: i64 = conn.query_row("SELECT COUNT(*) FROM content", [], |row| row.get(0))?; 941 + let synced_content: i64 = conn.query_row( 942 + "SELECT COUNT(*) FROM content WHERE synced = 1", 943 + [], 944 + |row| row.get(0), 945 + )?; 946 + 947 + Ok(DatastoreStats { 948 + total_addresses, 949 + total_visits, 950 + avg_visit_duration, 951 + total_content, 952 + synced_content, 953 + }) 954 + } 955 + 956 + // Simple base64 encoding (avoiding external dependency for now) 957 + mod base64 { 958 + pub const STANDARD: () = (); 959 + 960 + pub fn encode_config(data: &[u8], _config: ()) -> String { 961 + const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 962 + let mut result = String::new(); 963 + 964 + for chunk in data.chunks(3) { 965 + let n = match chunk.len() { 966 + 3 => ((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8) | (chunk[2] as u32), 967 + 2 => ((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8), 968 + 1 => (chunk[0] as u32) << 16, 969 + _ => continue, 970 + }; 971 + 972 + result.push(ALPHABET[((n >> 18) & 0x3F) as usize] as char); 973 + result.push(ALPHABET[((n >> 12) & 0x3F) as usize] as char); 974 + if chunk.len() > 1 { 975 + result.push(ALPHABET[((n >> 6) & 0x3F) as usize] as char); 976 + } else { 977 + result.push('='); 978 + } 979 + if chunk.len() > 2 { 980 + result.push(ALPHABET[(n & 0x3F) as usize] as char); 981 + } else { 982 + result.push('='); 983 + } 984 + } 985 + 986 + result 987 + } 988 + }
+112
backend/tauri/src-tauri/src/lib.rs
··· 1 + //! Peek Tauri Backend 2 + //! 3 + //! This is the Tauri backend for Peek, providing window management, 4 + //! SQLite datastore, and custom protocol handling. 5 + 6 + mod commands; 7 + mod datastore; 8 + mod protocol; 9 + mod state; 10 + 11 + use state::AppState; 12 + use std::sync::Arc; 13 + use tauri::{Manager, WebviewUrl, WebviewWindowBuilder}; 14 + 15 + /// The preload script that provides window.app API 16 + /// This is injected into all windows to match Electron's preload behavior 17 + pub const PRELOAD_SCRIPT: &str = include_str!("../../preload.js"); 18 + 19 + /// Initialize and run the Tauri application 20 + pub fn run() { 21 + tauri::Builder::default() 22 + .plugin(tauri_plugin_shell::init()) 23 + .setup(|app| { 24 + // Check for headless mode (for testing) 25 + let headless = std::env::var("HEADLESS").is_ok(); 26 + if headless { 27 + println!("[tauri] Running in HEADLESS mode - no visible windows"); 28 + } 29 + 30 + // Determine profile based on environment 31 + let profile = std::env::var("PROFILE").unwrap_or_else(|_| { 32 + if cfg!(debug_assertions) { 33 + "dev".to_string() 34 + } else { 35 + "default".to_string() 36 + } 37 + }); 38 + 39 + // Set up data directory 40 + let app_data_dir = app 41 + .path() 42 + .app_data_dir() 43 + .expect("Failed to get app data directory"); 44 + let profile_dir = app_data_dir.join(&profile); 45 + std::fs::create_dir_all(&profile_dir).expect("Failed to create profile directory"); 46 + 47 + // Initialize database 48 + let db_path = profile_dir.join("datastore.sqlite"); 49 + println!("[tauri] Initializing database at: {:?}", db_path); 50 + 51 + let db = datastore::init_database(&db_path).expect("Failed to initialize database"); 52 + 53 + // Create app state 54 + let state = AppState::new(db, profile, profile_dir, headless); 55 + app.manage(Arc::new(state)); 56 + 57 + // Create main window programmatically with preload script injection 58 + let main_url = WebviewUrl::CustomProtocol( 59 + "peek://app/background.html" 60 + .parse() 61 + .expect("Invalid main URL"), 62 + ); 63 + 64 + let main_window = WebviewWindowBuilder::new(app, "main", main_url) 65 + .title("Peek (Tauri)") 66 + .inner_size(800.0, 600.0) 67 + .visible(false) 68 + .initialization_script(PRELOAD_SCRIPT) 69 + .build() 70 + .expect("Failed to create main window"); 71 + 72 + // DevTools can be opened via keyboard shortcut or menu 73 + // Set PEEK_DEVTOOLS=1 to auto-open devtools on startup 74 + #[cfg(debug_assertions)] 75 + if std::env::var("PEEK_DEVTOOLS").is_ok() { 76 + main_window.open_devtools(); 77 + println!("[tauri] DevTools opened for main window"); 78 + } 79 + 80 + println!("[tauri] App setup complete"); 81 + 82 + Ok(()) 83 + }) 84 + .register_asynchronous_uri_scheme_protocol("peek", protocol::handle_peek_protocol) 85 + .invoke_handler(tauri::generate_handler![ 86 + // Window commands 87 + commands::window::window_open, 88 + commands::window::window_close, 89 + commands::window::window_hide, 90 + commands::window::window_show, 91 + commands::window::window_focus, 92 + commands::window::window_list, 93 + // Datastore commands 94 + commands::datastore::datastore_add_address, 95 + commands::datastore::datastore_get_address, 96 + commands::datastore::datastore_update_address, 97 + commands::datastore::datastore_query_addresses, 98 + commands::datastore::datastore_add_visit, 99 + commands::datastore::datastore_query_visits, 100 + commands::datastore::datastore_get_or_create_tag, 101 + commands::datastore::datastore_tag_address, 102 + commands::datastore::datastore_untag_address, 103 + commands::datastore::datastore_get_address_tags, 104 + commands::datastore::datastore_get_table, 105 + commands::datastore::datastore_set_row, 106 + commands::datastore::datastore_get_stats, 107 + // Utility commands 108 + commands::log_message, 109 + ]) 110 + .run(tauri::generate_context!()) 111 + .expect("error while running tauri application"); 112 + }
+6
backend/tauri/src-tauri/src/main.rs
··· 1 + // Prevents additional console window on Windows in release 2 + #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 + 4 + fn main() { 5 + peek_tauri_lib::run() 6 + }
+221
backend/tauri/src-tauri/src/protocol.rs
··· 1 + //! Custom peek:// protocol handler 2 + //! 3 + //! Handles routing for: 4 + //! - peek://app/... → Application files 5 + //! - peek://ext/{id}/... → Extension files 6 + //! - peek://extensions/... → Extension infrastructure 7 + 8 + use std::borrow::Cow; 9 + use std::path::{Path, PathBuf}; 10 + use tauri::http::{Request, Response}; 11 + use tauri::{Manager, UriSchemeContext, UriSchemeResponder}; 12 + 13 + /// Handle peek:// protocol requests 14 + pub fn handle_peek_protocol<R: tauri::Runtime>( 15 + ctx: UriSchemeContext<'_, R>, 16 + request: Request<Vec<u8>>, 17 + responder: UriSchemeResponder, 18 + ) { 19 + let uri = request.uri(); 20 + let uri_str = uri.to_string(); 21 + 22 + println!("[tauri:protocol] Handling request: {}", uri_str); 23 + 24 + // Parse the URL and serve 25 + let response = match parse_and_serve(&ctx, &uri_str) { 26 + Ok(resp) => resp, 27 + Err(e) => { 28 + eprintln!("[tauri:protocol] Error: {}", e); 29 + Response::builder() 30 + .status(500) 31 + .header("Content-Type", "text/plain") 32 + .body(Cow::Owned(format!("Error: {}", e).into_bytes())) 33 + .unwrap() 34 + } 35 + }; 36 + 37 + responder.respond(response); 38 + } 39 + 40 + fn parse_and_serve<R: tauri::Runtime>( 41 + ctx: &UriSchemeContext<'_, R>, 42 + uri: &str, 43 + ) -> Result<Response<Cow<'static, [u8]>>, String> { 44 + // Parse the URI: peek://host/path 45 + let uri = uri.strip_prefix("peek://").ok_or("Invalid peek:// URL")?; 46 + 47 + let (host, path) = match uri.find('/') { 48 + Some(idx) => (&uri[..idx], &uri[idx + 1..]), 49 + None => (uri, ""), 50 + }; 51 + 52 + // Strip query string and fragment from path for file lookup 53 + let clean_path = path 54 + .split('?') 55 + .next() 56 + .unwrap_or(path) 57 + .split('#') 58 + .next() 59 + .unwrap_or(path); 60 + 61 + match host { 62 + "app" => serve_app_file(ctx, clean_path), 63 + "ext" => serve_extension_file(ctx, clean_path), 64 + "extensions" => serve_extensions_file(ctx, clean_path), 65 + "tauri" => serve_tauri_file(ctx, clean_path), 66 + "system" => { 67 + // System URLs are virtual, return empty response 68 + Ok(Response::builder() 69 + .status(200) 70 + .header("Content-Type", "text/html") 71 + .body(Cow::Borrowed(b"" as &[u8])) 72 + .unwrap()) 73 + } 74 + _ => Err(format!("Unknown host: {}", host)), 75 + } 76 + } 77 + 78 + /// Serve files from the app/ directory 79 + fn serve_app_file<R: tauri::Runtime>( 80 + ctx: &UriSchemeContext<'_, R>, 81 + path: &str, 82 + ) -> Result<Response<Cow<'static, [u8]>>, String> { 83 + // Get the resource directory (where the app files are bundled) 84 + let resource_dir = get_resource_dir(ctx)?; 85 + 86 + // Handle node_modules specially 87 + let file_path = if path.starts_with("node_modules/") { 88 + resource_dir.join(path) 89 + } else { 90 + resource_dir.join("app").join(path) 91 + }; 92 + 93 + serve_file(&file_path) 94 + } 95 + 96 + /// Serve extension files from peek://ext/{ext_id}/path 97 + fn serve_extension_file<R: tauri::Runtime>( 98 + ctx: &UriSchemeContext<'_, R>, 99 + path: &str, 100 + ) -> Result<Response<Cow<'static, [u8]>>, String> { 101 + // Parse extension ID from path: {ext_id}/{rest} 102 + let (ext_id, ext_path) = match path.find('/') { 103 + Some(idx) => (&path[..idx], &path[idx + 1..]), 104 + None => (path, "index.html"), 105 + }; 106 + 107 + let ext_path = if ext_path.is_empty() { 108 + "index.html" 109 + } else { 110 + ext_path 111 + }; 112 + 113 + // Get extension base path 114 + let resource_dir = get_resource_dir(ctx)?; 115 + let ext_base_path = resource_dir.join("extensions").join(ext_id); 116 + 117 + // Security: Prevent path traversal 118 + let requested_path = ext_base_path.join(ext_path); 119 + let canonical_base = ext_base_path 120 + .canonicalize() 121 + .map_err(|e| format!("Extension not found: {} ({})", ext_id, e))?; 122 + let canonical_path = requested_path 123 + .canonicalize() 124 + .map_err(|e| format!("File not found: {} ({})", ext_path, e))?; 125 + 126 + if !canonical_path.starts_with(&canonical_base) { 127 + return Err("Forbidden: Path traversal attempt".to_string()); 128 + } 129 + 130 + serve_file(&canonical_path) 131 + } 132 + 133 + /// Serve extension infrastructure files 134 + fn serve_extensions_file<R: tauri::Runtime>( 135 + ctx: &UriSchemeContext<'_, R>, 136 + path: &str, 137 + ) -> Result<Response<Cow<'static, [u8]>>, String> { 138 + let resource_dir = get_resource_dir(ctx)?; 139 + let file_path = resource_dir.join("extensions").join(path); 140 + serve_file(&file_path) 141 + } 142 + 143 + /// Serve Tauri backend files from peek://tauri/... 144 + /// This keeps backend-specific code out of the app/ directory 145 + fn serve_tauri_file<R: tauri::Runtime>( 146 + ctx: &UriSchemeContext<'_, R>, 147 + path: &str, 148 + ) -> Result<Response<Cow<'static, [u8]>>, String> { 149 + let resource_dir = get_resource_dir(ctx)?; 150 + let file_path = resource_dir.join("backend").join("tauri").join(path); 151 + serve_file(&file_path) 152 + } 153 + 154 + /// Get the resource directory based on build mode 155 + fn get_resource_dir<R: tauri::Runtime>( 156 + ctx: &UriSchemeContext<'_, R>, 157 + ) -> Result<PathBuf, String> { 158 + // In development, use the project root 159 + // In production, use the resource directory 160 + if cfg!(debug_assertions) { 161 + // Development: go up from src-tauri to project root 162 + let manifest_dir = 163 + std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()); 164 + let manifest_path = PathBuf::from(manifest_dir); 165 + 166 + // Go up from backend/tauri/src-tauri to project root 167 + Ok(manifest_path 168 + .join("../../..") 169 + .canonicalize() 170 + .unwrap_or(manifest_path)) 171 + } else { 172 + // Production: use Tauri's resource directory 173 + ctx.app_handle() 174 + .path() 175 + .resource_dir() 176 + .map_err(|e| format!("Failed to get resource dir: {}", e)) 177 + } 178 + } 179 + 180 + /// Serve a file from the filesystem 181 + fn serve_file(path: &Path) -> Result<Response<Cow<'static, [u8]>>, String> { 182 + // Check if file exists 183 + if !path.exists() { 184 + return Ok(Response::builder() 185 + .status(404) 186 + .header("Content-Type", "text/plain") 187 + .body(Cow::Borrowed(b"Not Found" as &[u8])) 188 + .unwrap()); 189 + } 190 + 191 + // If it's a directory, try index.html 192 + let file_path = if path.is_dir() { 193 + path.join("index.html") 194 + } else { 195 + path.to_path_buf() 196 + }; 197 + 198 + if !file_path.exists() { 199 + return Ok(Response::builder() 200 + .status(404) 201 + .header("Content-Type", "text/plain") 202 + .body(Cow::Borrowed(b"Not Found" as &[u8])) 203 + .unwrap()); 204 + } 205 + 206 + // Read the file 207 + let content = 208 + std::fs::read(&file_path).map_err(|e| format!("Failed to read file: {}", e))?; 209 + 210 + // Determine MIME type 211 + let mime_type = mime_guess::from_path(&file_path) 212 + .first_or_octet_stream() 213 + .to_string(); 214 + 215 + Ok(Response::builder() 216 + .status(200) 217 + .header("Content-Type", &mime_type) 218 + .header("Access-Control-Allow-Origin", "*") 219 + .body(Cow::Owned(content)) 220 + .unwrap()) 221 + }
+71
backend/tauri/src-tauri/src/state.rs
··· 1 + //! Application state management 2 + 3 + use rusqlite::Connection; 4 + use std::collections::HashMap; 5 + use std::path::PathBuf; 6 + use std::sync::Mutex; 7 + 8 + /// Window information stored in registry 9 + #[derive(Debug, Clone)] 10 + pub struct WindowInfo { 11 + pub label: String, 12 + pub source: String, 13 + pub url: String, 14 + pub created_at: i64, 15 + } 16 + 17 + /// Application state shared across all commands 18 + pub struct AppState { 19 + /// SQLite database connection (mutex for thread safety) 20 + pub db: Mutex<Connection>, 21 + 22 + /// Current profile name (dev, default, etc.) 23 + pub profile: String, 24 + 25 + /// Profile data directory 26 + pub profile_dir: PathBuf, 27 + 28 + /// Window registry - tracks all open windows 29 + pub windows: Mutex<HashMap<String, WindowInfo>>, 30 + 31 + /// Headless mode - no visible windows (for testing) 32 + pub headless: bool, 33 + } 34 + 35 + impl AppState { 36 + pub fn new(db: Connection, profile: String, profile_dir: PathBuf, headless: bool) -> Self { 37 + Self { 38 + db: Mutex::new(db), 39 + profile, 40 + profile_dir, 41 + windows: Mutex::new(HashMap::new()), 42 + headless, 43 + } 44 + } 45 + 46 + /// Register a window in the registry 47 + pub fn register_window(&self, label: &str, source: &str, url: &str) { 48 + let mut windows = self.windows.lock().unwrap(); 49 + windows.insert( 50 + label.to_string(), 51 + WindowInfo { 52 + label: label.to_string(), 53 + source: source.to_string(), 54 + url: url.to_string(), 55 + created_at: chrono::Utc::now().timestamp_millis(), 56 + }, 57 + ); 58 + } 59 + 60 + /// Unregister a window from the registry 61 + pub fn unregister_window(&self, label: &str) { 62 + let mut windows = self.windows.lock().unwrap(); 63 + windows.remove(label); 64 + } 65 + 66 + /// Get all registered windows 67 + pub fn list_windows(&self) -> Vec<WindowInfo> { 68 + let windows = self.windows.lock().unwrap(); 69 + windows.values().cloned().collect() 70 + } 71 + }
+25
backend/tauri/src-tauri/tauri.conf.json
··· 1 + { 2 + "$schema": "https://schema.tauri.app/config/2", 3 + "productName": "Peek", 4 + "version": "0.1.0", 5 + "identifier": "io.peek.app", 6 + "build": { 7 + "beforeDevCommand": "", 8 + "beforeBuildCommand": "", 9 + "frontendDist": "../../../app" 10 + }, 11 + "app": { 12 + "withGlobalTauri": true, 13 + "windows": [], 14 + "security": { 15 + "csp": null, 16 + "dangerousDisableAssetCspModification": true 17 + } 18 + }, 19 + "bundle": { 20 + "active": false, 21 + "targets": [], 22 + "icon": [] 23 + }, 24 + "plugins": {} 25 + }
+275
backend/tauri/src-tauri/tests/smoke.rs
··· 1 + //! Tauri Backend Smoke Tests 2 + //! 3 + //! These tests verify core Tauri backend functionality: 4 + //! - Database initialization and operations 5 + //! - Address CRUD operations 6 + //! - Visit tracking 7 + //! - Tag operations 8 + //! 9 + //! Run with: cargo test --test smoke 10 + 11 + use tempfile::TempDir; 12 + 13 + // Import our modules 14 + #[path = "../src/datastore.rs"] 15 + mod datastore; 16 + 17 + /// Test database initialization 18 + #[test] 19 + fn test_database_init() { 20 + let temp_dir = TempDir::new().unwrap(); 21 + let db_path = temp_dir.path().join("test.sqlite"); 22 + 23 + let conn = datastore::init_database(&db_path).expect("Failed to init database"); 24 + 25 + // Verify tables exist 26 + let tables: Vec<String> = conn 27 + .prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") 28 + .unwrap() 29 + .query_map([], |row| row.get(0)) 30 + .unwrap() 31 + .filter_map(|r| r.ok()) 32 + .collect(); 33 + 34 + assert!(tables.contains(&"addresses".to_string())); 35 + assert!(tables.contains(&"visits".to_string())); 36 + assert!(tables.contains(&"tags".to_string())); 37 + assert!(tables.contains(&"content".to_string())); 38 + assert!(tables.contains(&"extensions".to_string())); 39 + 40 + println!("✓ Database initialization works"); 41 + } 42 + 43 + /// Test address operations 44 + #[test] 45 + fn test_address_operations() { 46 + let temp_dir = TempDir::new().unwrap(); 47 + let db_path = temp_dir.path().join("test.sqlite"); 48 + let conn = datastore::init_database(&db_path).unwrap(); 49 + 50 + // Add an address 51 + let options = datastore::AddressOptions { 52 + title: Some("Test Page".to_string()), 53 + ..Default::default() 54 + }; 55 + let id = datastore::add_address(&conn, "https://example.com/test", &options) 56 + .expect("Failed to add address"); 57 + 58 + assert!(id.starts_with("addr_")); 59 + println!("✓ Address added: {}", id); 60 + 61 + // Get the address 62 + let addr = datastore::get_address(&conn, &id) 63 + .expect("Failed to get address") 64 + .expect("Address not found"); 65 + 66 + assert_eq!(addr.uri, "https://example.com/test"); 67 + assert_eq!(addr.title, "Test Page"); 68 + assert_eq!(addr.domain, Some("example.com".to_string())); 69 + assert_eq!(addr.protocol, "https"); 70 + println!("✓ Address retrieved correctly"); 71 + 72 + // Query addresses 73 + let filter = datastore::AddressFilter { 74 + domain: Some("example.com".to_string()), 75 + ..Default::default() 76 + }; 77 + let results = datastore::query_addresses(&conn, &filter).expect("Failed to query"); 78 + 79 + assert_eq!(results.len(), 1); 80 + assert_eq!(results[0].id, id); 81 + println!("✓ Address query works"); 82 + 83 + // Update address 84 + let mut updates = std::collections::HashMap::new(); 85 + updates.insert("title".to_string(), serde_json::json!("Updated Title")); 86 + let updated = datastore::update_address(&conn, &id, &updates).expect("Failed to update"); 87 + 88 + assert!(updated); 89 + 90 + let addr = datastore::get_address(&conn, &id).unwrap().unwrap(); 91 + assert_eq!(addr.title, "Updated Title"); 92 + println!("✓ Address update works"); 93 + } 94 + 95 + /// Test visit tracking 96 + #[test] 97 + fn test_visit_tracking() { 98 + let temp_dir = TempDir::new().unwrap(); 99 + let db_path = temp_dir.path().join("test.sqlite"); 100 + let conn = datastore::init_database(&db_path).unwrap(); 101 + 102 + // Add an address first 103 + let addr_id = datastore::add_address(&conn, "https://example.com", &Default::default()).unwrap(); 104 + 105 + // Add a visit 106 + let visit_options = datastore::VisitOptions { 107 + source: Some("test".to_string()), 108 + ..Default::default() 109 + }; 110 + let visit_id = 111 + datastore::add_visit(&conn, &addr_id, &visit_options).expect("Failed to add visit"); 112 + 113 + assert!(visit_id.starts_with("visit_")); 114 + println!("✓ Visit added: {}", visit_id); 115 + 116 + // Verify address visit count updated 117 + let addr = datastore::get_address(&conn, &addr_id).unwrap().unwrap(); 118 + assert_eq!(addr.visit_count, 1); 119 + assert!(addr.last_visit_at > 0); 120 + println!("✓ Address visit count updated"); 121 + 122 + // Query visits 123 + let filter = datastore::VisitFilter { 124 + address_id: Some(addr_id.clone()), 125 + ..Default::default() 126 + }; 127 + let visits = datastore::query_visits(&conn, &filter).expect("Failed to query visits"); 128 + 129 + assert_eq!(visits.len(), 1); 130 + assert_eq!(visits[0].source, "test"); 131 + println!("✓ Visit query works"); 132 + } 133 + 134 + /// Test tag operations 135 + #[test] 136 + fn test_tag_operations() { 137 + let temp_dir = TempDir::new().unwrap(); 138 + let db_path = temp_dir.path().join("test.sqlite"); 139 + let conn = datastore::init_database(&db_path).unwrap(); 140 + 141 + // Create a tag 142 + let (tag, created) = 143 + datastore::get_or_create_tag(&conn, "Test Tag").expect("Failed to create tag"); 144 + 145 + assert!(created); 146 + assert!(tag.id.starts_with("tag_")); 147 + assert_eq!(tag.name, "Test Tag"); 148 + assert_eq!(tag.slug, Some("test-tag".to_string())); 149 + println!("✓ Tag created: {}", tag.id); 150 + 151 + // Get same tag again (should not create new) 152 + let (tag2, created2) = datastore::get_or_create_tag(&conn, "Test Tag").unwrap(); 153 + 154 + assert!(!created2); 155 + assert_eq!(tag.id, tag2.id); 156 + println!("✓ Tag retrieval works (no duplicate)"); 157 + 158 + // Tag an address 159 + let addr_id = datastore::add_address(&conn, "https://example.com", &Default::default()).unwrap(); 160 + let (link, already_exists) = 161 + datastore::tag_address(&conn, &addr_id, &tag.id).expect("Failed to tag address"); 162 + 163 + assert!(!already_exists); 164 + assert!(link.id.starts_with("address_tag_")); 165 + println!("✓ Address tagged"); 166 + 167 + // Get address tags 168 + let tags = datastore::get_address_tags(&conn, &addr_id).expect("Failed to get address tags"); 169 + 170 + assert_eq!(tags.len(), 1); 171 + assert_eq!(tags[0].name, "Test Tag"); 172 + println!("✓ Address tags retrieved"); 173 + 174 + // Untag address 175 + let removed = datastore::untag_address(&conn, &addr_id, &tag.id).expect("Failed to untag"); 176 + 177 + assert!(removed); 178 + 179 + let tags = datastore::get_address_tags(&conn, &addr_id).unwrap(); 180 + assert_eq!(tags.len(), 0); 181 + println!("✓ Address untagged"); 182 + } 183 + 184 + /// Test generic table operations 185 + #[test] 186 + fn test_table_operations() { 187 + let temp_dir = TempDir::new().unwrap(); 188 + let db_path = temp_dir.path().join("test.sqlite"); 189 + let conn = datastore::init_database(&db_path).unwrap(); 190 + 191 + // Add some data 192 + datastore::add_address(&conn, "https://example1.com", &Default::default()).unwrap(); 193 + datastore::add_address(&conn, "https://example2.com", &Default::default()).unwrap(); 194 + 195 + // Get table 196 + let table = datastore::get_table(&conn, "addresses").expect("Failed to get table"); 197 + 198 + assert_eq!(table.len(), 2); 199 + println!("✓ Get table works ({} rows)", table.len()); 200 + 201 + // Set row - provide all required fields 202 + let now = datastore::now(); 203 + let mut row_data = std::collections::HashMap::new(); 204 + row_data.insert("title".to_string(), serde_json::json!("Custom Title")); 205 + row_data.insert("uri".to_string(), serde_json::json!("https://custom.com")); 206 + row_data.insert("protocol".to_string(), serde_json::json!("https")); 207 + row_data.insert("domain".to_string(), serde_json::json!("custom.com")); 208 + row_data.insert("path".to_string(), serde_json::json!("")); 209 + row_data.insert("mimeType".to_string(), serde_json::json!("text/html")); 210 + row_data.insert("createdAt".to_string(), serde_json::json!(now)); 211 + row_data.insert("updatedAt".to_string(), serde_json::json!(now)); 212 + 213 + datastore::set_row(&conn, "addresses", "custom_id", &row_data).expect("Failed to set row"); 214 + 215 + // Verify via get_table (get_address expects all fields) 216 + let table = datastore::get_table(&conn, "addresses").expect("Failed to get table"); 217 + assert_eq!(table.len(), 3); 218 + assert!(table.contains_key("custom_id")); 219 + assert_eq!(table["custom_id"]["title"], serde_json::json!("Custom Title")); 220 + println!("✓ Set row works"); 221 + } 222 + 223 + /// Test stats 224 + #[test] 225 + fn test_stats() { 226 + let temp_dir = TempDir::new().unwrap(); 227 + let db_path = temp_dir.path().join("test.sqlite"); 228 + let conn = datastore::init_database(&db_path).unwrap(); 229 + 230 + // Add some data 231 + let addr_id = datastore::add_address(&conn, "https://example.com", &Default::default()).unwrap(); 232 + datastore::add_visit(&conn, &addr_id, &Default::default()).unwrap(); 233 + datastore::add_visit(&conn, &addr_id, &Default::default()).unwrap(); 234 + 235 + let stats = datastore::get_stats(&conn).expect("Failed to get stats"); 236 + 237 + assert_eq!(stats.total_addresses, 1); 238 + assert_eq!(stats.total_visits, 2); 239 + println!("✓ Stats work: {} addresses, {} visits", stats.total_addresses, stats.total_visits); 240 + } 241 + 242 + /// Test URL normalization 243 + #[test] 244 + fn test_url_normalization() { 245 + // Trailing slash removal 246 + assert_eq!( 247 + datastore::normalize_url("https://example.com/path/"), 248 + "https://example.com/path" 249 + ); 250 + 251 + // Root path preserved 252 + assert_eq!( 253 + datastore::normalize_url("https://example.com/"), 254 + "https://example.com/" 255 + ); 256 + 257 + // Default port removal 258 + assert_eq!( 259 + datastore::normalize_url("https://example.com:443/path"), 260 + "https://example.com/path" 261 + ); 262 + 263 + assert_eq!( 264 + datastore::normalize_url("http://example.com:80/path"), 265 + "http://example.com/path" 266 + ); 267 + 268 + println!("✓ URL normalization works"); 269 + } 270 + 271 + /// Main test runner - prints summary 272 + fn main() { 273 + println!("\n🧪 Tauri Backend Smoke Tests\n"); 274 + println!("Run with: cargo test --test smoke\n"); 275 + }
+381
backend/tauri/src/preload.ts
··· 1 + /** 2 + * Tauri Preload Adapter 3 + * 4 + * This module provides the same `window.app` API as the Electron preload.js, 5 + * but using Tauri's invoke() for IPC communication. 6 + * 7 + * Usage: Include this script in your HTML before other app scripts: 8 + * <script type="module" src="peek://app/backend/tauri/src/preload.js"></script> 9 + */ 10 + 11 + import { invoke } from '@tauri-apps/api/core'; 12 + import { emit, listen } from '@tauri-apps/api/event'; 13 + import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; 14 + 15 + const DEBUG = false; 16 + const sourceAddress = window.location.toString(); 17 + 18 + // Helper to generate random IDs 19 + const rndm = () => Math.random().toString(16).slice(2); 20 + 21 + // Context detection 22 + const isCore = sourceAddress.startsWith('peek://app/'); 23 + const isExtension = sourceAddress.startsWith('peek://ext/'); 24 + 25 + const getExtensionId = (): string | null => { 26 + if (!isExtension) return null; 27 + const match = sourceAddress.match(/peek:\/\/ext\/([^/]+)/); 28 + return match ? match[1] : null; 29 + }; 30 + 31 + // Local shortcut handlers (for shortcuts registered from this window) 32 + const localShortcutHandlers = new Map<string, () => void>(); 33 + 34 + // PubSub subscriptions 35 + const pubsubSubscriptions = new Map<string, (msg: unknown) => void>(); 36 + 37 + interface Api { 38 + debug: boolean; 39 + log: (...args: unknown[]) => void; 40 + scopes: { 41 + SYSTEM: number; 42 + SELF: number; 43 + GLOBAL: number; 44 + }; 45 + shortcuts: { 46 + register: (shortcut: string, cb: () => void, options?: { global?: boolean }) => void; 47 + unregister: (shortcut: string, options?: { global?: boolean }) => void; 48 + }; 49 + window: { 50 + open: (url: string, options?: Record<string, unknown>) => Promise<{ success: boolean; data?: { id: string }; error?: string }>; 51 + close: (id?: string | null) => Promise<{ success: boolean; error?: string }>; 52 + hide: (id?: string) => Promise<{ success: boolean; error?: string }>; 53 + show: (id?: string) => Promise<{ success: boolean; error?: string }>; 54 + focus: (id?: string) => Promise<{ success: boolean; error?: string }>; 55 + list: (options?: Record<string, unknown>) => Promise<{ success: boolean; data?: unknown[]; error?: string }>; 56 + exists: (id: string) => Promise<{ success: boolean; data?: boolean; error?: string }>; 57 + }; 58 + publish: (topic: string, msg: unknown, scope?: number) => void; 59 + subscribe: (topic: string, callback: (msg: unknown) => void, scope?: number) => void; 60 + datastore: { 61 + addAddress: (uri: string, options?: Record<string, unknown>) => Promise<{ success: boolean; data?: { id: string }; error?: string }>; 62 + getAddress: (id: string) => Promise<{ success: boolean; data?: unknown; error?: string }>; 63 + updateAddress: (id: string, updates: Record<string, unknown>) => Promise<{ success: boolean; data?: boolean; error?: string }>; 64 + queryAddresses: (filter?: Record<string, unknown>) => Promise<{ success: boolean; data?: unknown[]; error?: string }>; 65 + addVisit: (addressId: string, options?: Record<string, unknown>) => Promise<{ success: boolean; data?: { id: string }; error?: string }>; 66 + queryVisits: (filter?: Record<string, unknown>) => Promise<{ success: boolean; data?: unknown[]; error?: string }>; 67 + getOrCreateTag: (name: string) => Promise<{ success: boolean; data?: { tag: unknown; created: boolean }; error?: string }>; 68 + tagAddress: (addressId: string, tagId: string) => Promise<{ success: boolean; data?: unknown; error?: string }>; 69 + untagAddress: (addressId: string, tagId: string) => Promise<{ success: boolean; data?: boolean; error?: string }>; 70 + getAddressTags: (addressId: string) => Promise<{ success: boolean; data?: unknown[]; error?: string }>; 71 + getTable: (tableName: string) => Promise<{ success: boolean; data?: Record<string, unknown>; error?: string }>; 72 + setRow: (tableName: string, rowId: string, rowData: Record<string, unknown>) => Promise<{ success: boolean; data?: boolean; error?: string }>; 73 + getStats: () => Promise<{ success: boolean; data?: unknown; error?: string }>; 74 + }; 75 + commands: { 76 + register: (command: { name: string; description?: string; execute: (ctx: unknown) => void | Promise<void> }) => void; 77 + unregister: (name: string) => void; 78 + getAll: () => Promise<{ success: boolean; data?: unknown[]; error?: string }>; 79 + }; 80 + extensions: { 81 + list: () => Promise<{ success: boolean; data?: unknown[]; error?: string }>; 82 + load: (id: string) => Promise<{ success: boolean; error?: string }>; 83 + unload: (id: string) => Promise<{ success: boolean; error?: string }>; 84 + reload: (id: string) => Promise<{ success: boolean; error?: string }>; 85 + }; 86 + settings: { 87 + get: () => Promise<{ success: boolean; data?: unknown; error?: string }>; 88 + set: (settings: unknown) => Promise<{ success: boolean; error?: string }>; 89 + getKey: (key: string) => Promise<{ success: boolean; data?: unknown; error?: string }>; 90 + setKey: (key: string, value: unknown) => Promise<{ success: boolean; error?: string }>; 91 + }; 92 + } 93 + 94 + const api: Api = { 95 + debug: DEBUG, 96 + 97 + log: (...args: unknown[]) => { 98 + invoke('log_message', { source: sourceAddress, args }); 99 + }, 100 + 101 + scopes: { 102 + SYSTEM: 1, 103 + SELF: 2, 104 + GLOBAL: 3, 105 + }, 106 + 107 + // ==================== Shortcuts ==================== 108 + 109 + shortcuts: { 110 + register: (shortcut: string, cb: () => void, options: { global?: boolean } = {}) => { 111 + const isGlobal = options.global === true; 112 + console.log(`[tauri:preload] Registering ${isGlobal ? 'global' : 'local'} shortcut: ${shortcut}`); 113 + 114 + if (isGlobal) { 115 + // Global shortcuts - would need tauri-plugin-global-shortcut 116 + // For MVP, we'll skip this and implement in Phase 2 117 + console.warn('[tauri:preload] Global shortcuts not yet implemented in Tauri backend'); 118 + } else { 119 + // Local shortcuts - handle via keyboard events 120 + localShortcutHandlers.set(shortcut, cb); 121 + } 122 + }, 123 + 124 + unregister: (shortcut: string, options: { global?: boolean } = {}) => { 125 + const isGlobal = options.global === true; 126 + console.log(`[tauri:preload] Unregistering ${isGlobal ? 'global' : 'local'} shortcut: ${shortcut}`); 127 + 128 + if (!isGlobal) { 129 + localShortcutHandlers.delete(shortcut); 130 + } 131 + }, 132 + }, 133 + 134 + // ==================== Window Management ==================== 135 + 136 + window: { 137 + open: async (url: string, options: Record<string, unknown> = {}) => { 138 + console.log('[tauri:preload] window.open', url, options); 139 + return invoke('window_open', { source: sourceAddress, url, options }); 140 + }, 141 + 142 + close: async (id: string | null = null) => { 143 + console.log('[tauri:preload] window.close', id); 144 + if (id === null) { 145 + const currentWindow = getCurrentWebviewWindow(); 146 + await currentWindow.close(); 147 + return { success: true }; 148 + } 149 + return invoke('window_close', { id }); 150 + }, 151 + 152 + hide: async (id?: string) => { 153 + console.log('[tauri:preload] window.hide', id); 154 + return invoke('window_hide', { id }); 155 + }, 156 + 157 + show: async (id?: string) => { 158 + console.log('[tauri:preload] window.show', id); 159 + return invoke('window_show', { id }); 160 + }, 161 + 162 + focus: async (id?: string) => { 163 + console.log('[tauri:preload] window.focus', id); 164 + // Tauri doesn't have a separate focus command, use show 165 + return invoke('window_show', { id }); 166 + }, 167 + 168 + list: async (options: Record<string, unknown> = {}) => { 169 + return invoke('window_list', { options }); 170 + }, 171 + 172 + exists: async (id: string) => { 173 + const result = await invoke<{ success: boolean; data: unknown[] }>('window_list', {}); 174 + if (result.success && result.data) { 175 + const exists = result.data.some((w: any) => w.id === id || w.label === id); 176 + return { success: true, data: exists }; 177 + } 178 + return { success: false, data: false }; 179 + }, 180 + }, 181 + 182 + // ==================== PubSub ==================== 183 + 184 + publish: (topic: string, msg: unknown, scope: number = 2) => { 185 + console.log('[tauri:preload] publish', topic); 186 + // For MVP, emit Tauri events 187 + emit(`pubsub:${topic}`, { source: sourceAddress, scope, data: msg }); 188 + }, 189 + 190 + subscribe: (topic: string, callback: (msg: unknown) => void, scope: number = 2) => { 191 + console.log('[tauri:preload] subscribe', topic); 192 + pubsubSubscriptions.set(topic, callback); 193 + 194 + // Listen for Tauri events 195 + listen(`pubsub:${topic}`, (event) => { 196 + const msg = event.payload as { source: string; scope: number; data: unknown }; 197 + try { 198 + callback(msg); 199 + } catch (ex) { 200 + console.error('[tauri:preload] subscriber callback error for topic', topic, ex); 201 + } 202 + }); 203 + }, 204 + 205 + // ==================== Datastore ==================== 206 + 207 + datastore: { 208 + addAddress: async (uri: string, options: Record<string, unknown> = {}) => { 209 + return invoke('datastore_add_address', { uri, options }); 210 + }, 211 + 212 + getAddress: async (id: string) => { 213 + return invoke('datastore_get_address', { id }); 214 + }, 215 + 216 + updateAddress: async (id: string, updates: Record<string, unknown>) => { 217 + return invoke('datastore_update_address', { id, updates }); 218 + }, 219 + 220 + queryAddresses: async (filter: Record<string, unknown> = {}) => { 221 + return invoke('datastore_query_addresses', { filter }); 222 + }, 223 + 224 + addVisit: async (addressId: string, options: Record<string, unknown> = {}) => { 225 + return invoke('datastore_add_visit', { addressId, options }); 226 + }, 227 + 228 + queryVisits: async (filter: Record<string, unknown> = {}) => { 229 + return invoke('datastore_query_visits', { filter }); 230 + }, 231 + 232 + getOrCreateTag: async (name: string) => { 233 + return invoke('datastore_get_or_create_tag', { name }); 234 + }, 235 + 236 + tagAddress: async (addressId: string, tagId: string) => { 237 + return invoke('datastore_tag_address', { addressId, tagId }); 238 + }, 239 + 240 + untagAddress: async (addressId: string, tagId: string) => { 241 + return invoke('datastore_untag_address', { addressId, tagId }); 242 + }, 243 + 244 + getAddressTags: async (addressId: string) => { 245 + return invoke('datastore_get_address_tags', { addressId }); 246 + }, 247 + 248 + getTable: async (tableName: string) => { 249 + return invoke('datastore_get_table', { tableName }); 250 + }, 251 + 252 + setRow: async (tableName: string, rowId: string, rowData: Record<string, unknown>) => { 253 + return invoke('datastore_set_row', { tableName, rowId, rowData }); 254 + }, 255 + 256 + getStats: async () => { 257 + return invoke('datastore_get_stats', {}); 258 + }, 259 + }, 260 + 261 + // ==================== Commands (Stub for MVP) ==================== 262 + 263 + commands: { 264 + register: (command) => { 265 + console.log('[tauri:preload] commands.register', command.name); 266 + // MVP: Store locally, full implementation in Phase 2 267 + }, 268 + 269 + unregister: (name: string) => { 270 + console.log('[tauri:preload] commands.unregister', name); 271 + }, 272 + 273 + getAll: async () => { 274 + return { success: true, data: [] }; 275 + }, 276 + }, 277 + 278 + // ==================== Extensions (Stub for MVP) ==================== 279 + 280 + extensions: { 281 + list: async () => { 282 + return { success: true, data: [] }; 283 + }, 284 + 285 + load: async (id: string) => { 286 + console.warn('[tauri:preload] extensions.load not yet implemented'); 287 + return { success: false, error: 'Not implemented' }; 288 + }, 289 + 290 + unload: async (id: string) => { 291 + console.warn('[tauri:preload] extensions.unload not yet implemented'); 292 + return { success: false, error: 'Not implemented' }; 293 + }, 294 + 295 + reload: async (id: string) => { 296 + console.warn('[tauri:preload] extensions.reload not yet implemented'); 297 + return { success: false, error: 'Not implemented' }; 298 + }, 299 + }, 300 + 301 + // ==================== Settings (Stub for MVP) ==================== 302 + 303 + settings: { 304 + get: async () => { 305 + const extId = getExtensionId(); 306 + if (!extId) return { success: false, error: 'Not in extension context' }; 307 + // MVP: Return empty settings 308 + return { success: true, data: {} }; 309 + }, 310 + 311 + set: async (settings: unknown) => { 312 + const extId = getExtensionId(); 313 + if (!extId) return { success: false, error: 'Not in extension context' }; 314 + return { success: true }; 315 + }, 316 + 317 + getKey: async (key: string) => { 318 + const extId = getExtensionId(); 319 + if (!extId) return { success: false, error: 'Not in extension context' }; 320 + return { success: true, data: null }; 321 + }, 322 + 323 + setKey: async (key: string, value: unknown) => { 324 + const extId = getExtensionId(); 325 + if (!extId) return { success: false, error: 'Not in extension context' }; 326 + return { success: true }; 327 + }, 328 + }, 329 + }; 330 + 331 + // Set up local keyboard shortcut handler 332 + document.addEventListener('keydown', (e) => { 333 + // Build shortcut string from event 334 + const parts: string[] = []; 335 + if (e.altKey) parts.push('Alt'); 336 + if (e.ctrlKey) parts.push('Control'); 337 + if (e.metaKey) parts.push('Command'); 338 + if (e.shiftKey) parts.push('Shift'); 339 + 340 + // Add the key 341 + if (e.key.length === 1) { 342 + parts.push(e.key.toUpperCase()); 343 + } else { 344 + parts.push(e.key); 345 + } 346 + 347 + const shortcut = parts.join('+'); 348 + 349 + // Check for registered handlers (try various formats) 350 + const variations = [ 351 + shortcut, 352 + shortcut.replace('Alt', 'Option'), 353 + shortcut.replace('Control', 'CommandOrControl'), 354 + shortcut.replace('Command', 'CommandOrControl'), 355 + ]; 356 + 357 + for (const variant of variations) { 358 + const handler = localShortcutHandlers.get(variant); 359 + if (handler) { 360 + e.preventDefault(); 361 + handler(); 362 + break; 363 + } 364 + } 365 + }); 366 + 367 + // Handle ESC key 368 + document.addEventListener('keyup', (e) => { 369 + if (e.key === 'Escape') { 370 + const currentWindow = getCurrentWebviewWindow(); 371 + currentWindow.close(); 372 + } 373 + }); 374 + 375 + // Expose API globally 376 + (window as any).app = api; 377 + 378 + // Log initialization 379 + console.log('[tauri:preload] Initialized for:', sourceAddress); 380 + 381 + export default api;
+363
notes/core-api.md
··· 1 + # Peek Core API Reference 2 + 3 + The `window.app` API is automatically injected into all `peek://` pages. This API provides access to window management, data storage, messaging, and shortcuts. 4 + 5 + ## Context Detection 6 + 7 + ```javascript 8 + // Check if running in peek:// context 9 + if (window.app) { 10 + // API available 11 + } 12 + 13 + // Source address of current page 14 + const source = window.location.toString(); 15 + 16 + // Context types: 17 + // - peek://app/... Core application pages 18 + // - peek://ext/{id}/... Extension pages 19 + ``` 20 + 21 + ## Window Management 22 + 23 + ### `window.app.window.open(url, options)` 24 + 25 + Open a new window. 26 + 27 + ```javascript 28 + const result = await window.app.window.open('peek://app/settings/settings.html', { 29 + key: 'settings', // Reuse window with same key 30 + width: 900, // Window width 31 + height: 650, // Window height 32 + x: 100, // X position 33 + y: 100, // Y position 34 + title: 'Settings', // Window title 35 + modal: true, // Modal behavior 36 + transparent: false, // Transparent background 37 + decorations: true, // Window decorations 38 + alwaysOnTop: false, // Stay on top 39 + visible: true, // Initially visible 40 + resizable: true, // Allow resize 41 + keepLive: false // Keep window alive when closed 42 + }); 43 + // Returns: { success: true, id: 'window_label' } 44 + ``` 45 + 46 + ### `window.app.window.close(id?)` 47 + 48 + Close a window. If no id, closes current window. 49 + 50 + ```javascript 51 + await window.app.window.close(); // Close current 52 + await window.app.window.close('settings'); // Close by id 53 + await window.app.window.close({ id: 'settings' }); // Object form 54 + ``` 55 + 56 + ### `window.app.window.hide(id?)` / `window.app.window.show(id?)` 57 + 58 + Toggle window visibility. 59 + 60 + ```javascript 61 + await window.app.window.hide('main'); 62 + await window.app.window.show('main'); 63 + ``` 64 + 65 + ### `window.app.window.focus(id?)` 66 + 67 + Bring window to front and focus it. 68 + 69 + ```javascript 70 + await window.app.window.focus('settings'); 71 + ``` 72 + 73 + ### `window.app.window.list()` 74 + 75 + List all open windows. 76 + 77 + ```javascript 78 + const result = await window.app.window.list(); 79 + // Returns: { 80 + // success: true, 81 + // data: [{ 82 + // id: 'main', 83 + // label: 'main', 84 + // url: 'peek://app/background.html', 85 + // source: 'peek://app/background.html', 86 + // visible: false, 87 + // focused: false 88 + // }, ...] 89 + // } 90 + ``` 91 + 92 + ### `window.app.window.exists(id)` 93 + 94 + Check if a window exists. 95 + 96 + ```javascript 97 + const result = await window.app.window.exists('settings'); 98 + // Returns: { success: true, data: true } 99 + ``` 100 + 101 + ## Datastore 102 + 103 + All datastore methods return `{ success: boolean, data?: any, error?: string }`. 104 + 105 + ### Addresses 106 + 107 + ```javascript 108 + // Add a new address 109 + const result = await window.app.datastore.addAddress('https://example.com', { 110 + title: 'Example', 111 + favicon: 'https://example.com/favicon.ico' 112 + }); 113 + // Returns: { success: true, data: { id: 'addr_123', ... } } 114 + 115 + // Get address by ID 116 + const addr = await window.app.datastore.getAddress('addr_123'); 117 + 118 + // Update address 119 + await window.app.datastore.updateAddress('addr_123', { 120 + title: 'New Title' 121 + }); 122 + 123 + // Query addresses 124 + const results = await window.app.datastore.queryAddresses({ 125 + uri: 'example.com', // Partial match 126 + limit: 10, 127 + offset: 0 128 + }); 129 + ``` 130 + 131 + ### Visits 132 + 133 + ```javascript 134 + // Add a visit 135 + await window.app.datastore.addVisit('addr_123', { 136 + referrer: 'addr_456' 137 + }); 138 + 139 + // Query visits 140 + const visits = await window.app.datastore.queryVisits({ 141 + addressId: 'addr_123', 142 + limit: 50 143 + }); 144 + ``` 145 + 146 + ### Tags 147 + 148 + ```javascript 149 + // Get or create a tag 150 + const tag = await window.app.datastore.getOrCreateTag('important'); 151 + // Returns: { success: true, data: { id: 'tag_123', name: 'important' } } 152 + 153 + // Tag an address 154 + await window.app.datastore.tagAddress('addr_123', 'tag_123'); 155 + 156 + // Untag an address 157 + await window.app.datastore.untagAddress('addr_123', 'tag_123'); 158 + 159 + // Get tags for an address 160 + const tags = await window.app.datastore.getAddressTags('addr_123'); 161 + ``` 162 + 163 + ### Generic Table Access 164 + 165 + ```javascript 166 + // Get all rows from a table 167 + const table = await window.app.datastore.getTable('extensions'); 168 + // Returns: { success: true, data: { row_id: { ... }, ... } } 169 + 170 + // Set a row 171 + await window.app.datastore.setRow('extensions', 'my-ext', { 172 + name: 'My Extension', 173 + enabled: true 174 + }); 175 + ``` 176 + 177 + ### Statistics 178 + 179 + ```javascript 180 + const stats = await window.app.datastore.getStats(); 181 + // Returns: { success: true, data: { 182 + // addresses: 150, 183 + // visits: 1200, 184 + // tags: 25 185 + // }} 186 + ``` 187 + 188 + ## PubSub Messaging 189 + 190 + Cross-window communication via publish/subscribe. 191 + 192 + ### Scopes 193 + 194 + ```javascript 195 + window.app.scopes = { 196 + SYSTEM: 1, // System messages 197 + SELF: 2, // Same source only 198 + GLOBAL: 3 // All windows 199 + }; 200 + ``` 201 + 202 + ### `window.app.publish(topic, message, scope)` 203 + 204 + Publish a message. 205 + 206 + ```javascript 207 + window.app.publish('settings:changed', { theme: 'dark' }, window.app.scopes.GLOBAL); 208 + ``` 209 + 210 + ### `window.app.subscribe(topic, callback, scope)` 211 + 212 + Subscribe to messages. 213 + 214 + ```javascript 215 + window.app.subscribe('settings:changed', (msg) => { 216 + console.log('Settings changed:', msg.data); 217 + }, window.app.scopes.GLOBAL); 218 + ``` 219 + 220 + ## Keyboard Shortcuts 221 + 222 + ### `window.app.shortcuts.register(shortcut, callback, options)` 223 + 224 + Register a keyboard shortcut. 225 + 226 + ```javascript 227 + // Local shortcut (only when app focused) 228 + window.app.shortcuts.register('Command+K', () => { 229 + console.log('Command+K pressed'); 230 + }); 231 + 232 + // Global shortcut (works even when app not focused) 233 + window.app.shortcuts.register('Option+Space', () => { 234 + console.log('Global shortcut triggered'); 235 + }, { global: true }); 236 + ``` 237 + 238 + **Shortcut format:** 239 + - Modifiers: `Command`, `Control`, `Alt`, `Option`, `Shift`, `CommandOrControl` 240 + - Keys: `A-Z`, `0-9`, `F1-F12`, `Space`, `Enter`, `Escape`, `ArrowUp`, etc. 241 + - Examples: `Command+Shift+P`, `Alt+1`, `Option+ArrowDown` 242 + 243 + ### `window.app.shortcuts.unregister(shortcut, options)` 244 + 245 + Unregister a shortcut. 246 + 247 + ```javascript 248 + window.app.shortcuts.unregister('Command+K'); 249 + window.app.shortcuts.unregister('Option+Space', { global: true }); 250 + ``` 251 + 252 + ## Commands (Command Palette) 253 + 254 + Register commands that appear in the command palette. 255 + 256 + ### `window.app.commands.register(command)` 257 + 258 + ```javascript 259 + window.app.commands.register({ 260 + name: 'my-extension:do-thing', 261 + description: 'Do the thing', 262 + execute: () => { 263 + console.log('Doing the thing'); 264 + } 265 + }); 266 + ``` 267 + 268 + ### `window.app.commands.unregister(name)` 269 + 270 + ```javascript 271 + window.app.commands.unregister('my-extension:do-thing'); 272 + ``` 273 + 274 + ### `window.app.commands.getAll()` 275 + 276 + ```javascript 277 + const commands = await window.app.commands.getAll(); 278 + ``` 279 + 280 + ## Escape Handling 281 + 282 + Handle the ESC key to prevent window from closing. 283 + 284 + ```javascript 285 + window.app.escape.onEscape(() => { 286 + if (hasUnsavedChanges) { 287 + showConfirmDialog(); 288 + return { handled: true }; // Prevent close 289 + } 290 + return { handled: false }; // Allow close 291 + }); 292 + ``` 293 + 294 + ## Logging 295 + 296 + Log messages to the backend console. 297 + 298 + ```javascript 299 + window.app.log('Something happened', { detail: 'value' }); 300 + // Output in terminal: [peek://app/mypage.html] Something happened { detail: 'value' } 301 + ``` 302 + 303 + ## Debug Mode 304 + 305 + ```javascript 306 + if (window.app.debug) { 307 + console.log('Debug mode enabled'); 308 + } 309 + 310 + // Debug levels 311 + window.app.debugLevels = { BASIC: 1, FIRST_RUN: 2 }; 312 + window.app.debugLevel; // Current level 313 + ``` 314 + 315 + ## Extensions API 316 + 317 + Only available in core app pages (`peek://app/...`). 318 + 319 + ```javascript 320 + // Check permission 321 + if (window.app.extensions._hasPermission()) { 322 + const exts = await window.app.extensions.list(); 323 + } 324 + ``` 325 + 326 + ## Settings API 327 + 328 + Only available in extension pages (`peek://ext/{id}/...`). 329 + 330 + ```javascript 331 + // Get all settings 332 + const settings = await window.app.settings.get(); 333 + 334 + // Set all settings 335 + await window.app.settings.set({ theme: 'dark' }); 336 + 337 + // Get/set individual keys 338 + const theme = await window.app.settings.getKey('theme'); 339 + await window.app.settings.setKey('theme', 'light'); 340 + ``` 341 + 342 + ## Response Format 343 + 344 + All async API methods return a consistent response format: 345 + 346 + ```javascript 347 + { 348 + success: true, // Operation succeeded 349 + data: any, // Result data (if applicable) 350 + error: string // Error message (if success: false) 351 + } 352 + ``` 353 + 354 + Always check `success` before using `data`: 355 + 356 + ```javascript 357 + const result = await window.app.datastore.getAddress(id); 358 + if (result.success) { 359 + console.log(result.data); 360 + } else { 361 + console.error(result.error); 362 + } 363 + ```
+5 -1
package.json
··· 39 39 "test:smoke": "npx playwright test tests/smoke.spec.ts", 40 40 "test:persistence": "npx playwright test tests/smoke.spec.ts --grep 'Data Persistence' --timeout=120000", 41 41 "test:headed": "npx playwright test --headed", 42 - "test:debug": "npx playwright test --debug" 42 + "test:debug": "npx playwright test --debug", 43 + "tauri:dev": "cd backend/tauri/src-tauri && cargo run", 44 + "tauri:build": "cd backend/tauri/src-tauri && cargo tauri build", 45 + "tauri:check": "cd backend/tauri/src-tauri && cargo check", 46 + "tauri:test": "cd backend/tauri/src-tauri && cargo test --test smoke -- --nocapture" 43 47 }, 44 48 "dependencies": { 45 49 "better-sqlite3": "^12.5.0",
+38
scripts/tauri-run.sh
··· 1 + #!/bin/bash 2 + # Run Tauri backend in background and capture output 3 + # Usage: ./scripts/tauri-run.sh [seconds] [--visible] 4 + # seconds: how long to run before killing (default: 10, 0 = run forever) 5 + # --visible: show windows (default is headless/hidden) 6 + 7 + cd "$(dirname "$0")/../backend/tauri/src-tauri" 8 + 9 + DURATION=${1:-10} 10 + HEADLESS=1 11 + 12 + # Check for --visible flag 13 + for arg in "$@"; do 14 + if [ "$arg" = "--visible" ]; then 15 + HEADLESS="" 16 + fi 17 + done 18 + 19 + if [ "$DURATION" = "0" ]; then 20 + echo "Running Tauri (Ctrl+C to stop)..." 21 + HEADLESS=$HEADLESS cargo run 2>&1 22 + else 23 + MODE="headless" 24 + [ -z "$HEADLESS" ] && MODE="visible" 25 + echo "Running Tauri ($MODE) for $DURATION seconds..." 26 + LOGFILE=$(mktemp) 27 + # Run in background, capture all output to file 28 + HEADLESS=$HEADLESS cargo run > "$LOGFILE" 2>&1 & 29 + PID=$! 30 + sleep "$DURATION" 31 + kill $PID 2>/dev/null 32 + wait $PID 2>/dev/null 33 + echo "=== Output ===" 34 + cat "$LOGFILE" 35 + rm "$LOGFILE" 36 + echo "" 37 + echo "=== Stopped after $DURATION seconds ===" 38 + fi