findings.md
wox.core/setting/store.go.SettingValue.syncable (default true).wox.db via Gorm models WoxSetting (Key, Value) and PluginSetting (PluginID, Key, Value).WoxSettingStore serializes primitives to strings and complex types to JSON; PlatformValue stores a JSON struct of per-OS values.SettingValue.Set writes via SettingStore.Set; no sync hooks are currently invoked.SettingValue contains a syncable flag (currently unused), and should be the canonical sync eligibility signal.wox.core; need new secure storage for DEK.wox.core yet.SettingValue to avoid syncing internal metadata.Oplog table includes SyncedToCloud and LogOplog exists in WoxSettingStore, but no current callers.wox.core/cloudsync with base URL + auth/device providers; headers include X-Device-Id, X-App-Version, X-Platform, X-Trace-Id.${woxDataDirectory}/device_id when no explicit path is provided.WOX_CLOUD_SYNC_URL and WOX_CLOUD_SYNC_TOKEN./setting/wox (read) and /setting/wox/update (write) in wox.core/ui/router.go./setting/plugin/update uses APIImpl.SaveSetting and PluginSettingStore.PostSettingUpdate in wox.core/ui/manager.go applies side effects (tray, hotkeys, lang, autostart, MCP server).APIImpl.OnSettingChanged in wox.core/plugin/api.go./setting/wox + /setting/wox/update via WoxApi; settings are reloaded after update.wox.db lives under user data.| Decision | Rationale |
|---|---|
| Include plugin settings in sync scope | User requirement |
| Backend is official paid Wox cloud service | Product direction |
| Auth via user login (token-based) | Aligns with account system |
| Conflict resolution uses LWW | User requirement |
| Auto sync triggers after save | User requirement |
| Payload encryption uses AES-256-GCM with per-user key | Authenticated encryption and cross-device decrypt |
| Persist per-user encryption key after first login in OS keychain | Enables automatic sync without re-auth each time |
| All SettingValue are syncable | User requirement |
| Per-plugin sync exclusion list stored as a synced setting | User requirement |
| Remote pull via periodic polling + startup/manual triggers | Simpler without server push |
| Push batching uses debounce + max batch + exponential backoff | Avoids frequent network calls |
| Recovery code KDF uses Argon2id | User choice |
| First-time sync uses cloud snapshot when available | User requirement |
| Platform-specific settings sync full PlatformValue JSON | User requirement |
| Sync eligibility uses SettingValue.syncable (default true) | User requirement |
| Issue | Resolution |
|---|---|
session-catchup.py required python3 instead of python | Retried with python3 |
wox.core/setting/store.gowox.core/setting/value.gowox.core/setting/wox_setting.gowox.core/setting/manager.gowox.core/setting/backup_restore.gowox.core/ui/router.gowox.core/ui/manager.gowox.core/ui/dto/setting_dto.gowox.core/database/database.go (WoxSetting, PluginSetting, Oplog)wox.core/plugin/api.go (SaveSetting)wox.ui.flutter/wox/lib/api/wox_api.dartwox.ui.flutter/wox/lib/controllers/wox_setting_controller.dartwox.ui.flutter/wox/lib/entity/wox_setting.dart/v1Authorization: Bearer <access_token>X-Device-Id, X-App-Version, X-Platform, X-Trace-Id (optional)server_ts (ms since epoch) and LWW uses server time.AES-256-GCMentity_type + ":" + plugin_id + ":" + key + ":" + op (UTF-8; plugin_id empty for wox settings).key_version (int)nonce (base64, 12 bytes)ciphertext (base64, includes GCM tag)/sync/key/init
{device_id, device_name, kdf: {alg, salt, iter, mem_kib, parallelism, hash_len, version}, encrypted_dek, key_version}{key_version, created_at}/sync/key/fetch
{device_id}{key_version, kdf: {alg, salt, iter, mem_kib, parallelism, hash_len, version}, encrypted_dek}/sync/key/reset/prepare
{reset_token, expires_at} (explicit confirmation flow)/sync/key/reset
{reset_token, confirm: true}{reset_at}(entity_type, plugin_id, key) unique.upsert | deletePOST /sync/push
{
"device_id": "...",
"changes": [
{
"change_id": "uuid",
"entity_type": "wox_setting|plugin_setting",
"plugin_id": "optional",
"key": "setting_key",
"op": "upsert|delete",
"client_ts": 1234567890,
"value": { "key_version": 1, "nonce": "...", "ciphertext": "..." }
}
]
}
{
"server_ts": 1234567999,
"applied": [{"change_id":"...","status":"ok|ignored","server_ts":123}],
"next_cursor": "..."
}
server_ts.POST /sync/pull
{device_id, cursor, limit}{
"records": [
{
"entity_type": "wox_setting|plugin_setting",
"plugin_id": "optional",
"key": "setting_key",
"op": "upsert|delete",
"server_ts": 1234567999,
"client_ts": 1234567000,
"value": { "key_version": 1, "nonce": "...", "ciphertext": "..." }
}
],
"next_cursor": "...",
"has_more": false
}
POST /sync/snapshot
{device_id}/sync/pull with full dataset (paged)./devices/register
{device_id, device_name, platform}{ok: true}alg: argon2idversion: 19mem_kib: 65536 (64 MiB)iter: 3parallelism: 2hash_len: 32salt_len: 16 bytes (server generates, base64)