Skip to content

Port upstream 0.67.0: portable preferences export and import - #654

Closed
Finesssee wants to merge 3 commits into
port/upstream-0.67.0from
port/micro-0.67.0-portable-preferences
Closed

Finesssee wants to merge 3 commits into
port/upstream-0.67.0from
port/micro-0.67.0-portable-preferences

Conversation

@Finesssee

Copy link
Copy Markdown
Collaborator

Summary

Adds portable preferences: export and import of display, refresh, notification, provider and float-bar choices as a versioned JSON document ({"version": 1, "preferences": {...}}), from Settings > Advanced and from the CLI (codexbar config preferences export|import --file <path>).

  • Explicit allowlist of 34 keys. Anything else (API keys, cookies, token accounts, folders, SSH hosts, proxy, update, consent and side-effect toggles) is rejected on import and never exported. A test classifies every Settings field as allowed or excluded, so a new field defaults to excluded until someone decides.
  • The whole file is validated before anything is saved; a rejected import changes nothing. Errors name the key, never echo the value. Files over 256 KiB are rejected. null restores a key to its default.
  • Desktop import runs the same live updates as update_settings (locale, provider cache, float bar, tray, codexbar:settings-updated, refresh). CLI import writes settings.json only; the CLI prints that a running app must be restarted (there is no settings-file watcher).
  • New shell commands export_preferences / import_preferences reuse the existing settings-window dialog capabilities (dialog:allow-open / dialog:allow-save); no capability file change was needed.

Upstream reference

Ported / Deferred

Ported: versioned document, allowlist, validation, CLI --file export/import, Settings > Advanced section with save/open dialogs, en-US locale keys, docs.

Deferred or not applicable:

  • cost_reporting_period and preferred_currency keys: these settings do not exist in this base (they arrive with the reporting-period and currency items). Add them to the allowlist when those land.
  • merge_tray_icons: excluded, nothing in the app reads it.
  • macOS-only mechanics (UserDefaults, CloudKit suppression, pending-import queue, DistributedNotification): skipped.
  • Non-English locale strings fall back to en-US.

Validation

Run on the pinned 1.98.0 toolchain, E-cores only.

  • cargo +1.98.0 fmt --all: clean
  • cargo +1.98.0 clippy --workspace --all-targets -- -D warnings: clean
  • cargo +1.98.0 test -p codexbar preferences: 12 passed (11 document tests, 1 CLI parse test)
  • cargo +1.98.0 test -p codexbar (full): 2172 passed, 0 failed, 1 ignored
  • cargo +1.98.0 test -p codexbar-desktop-tauri: 461 passed, 1 failed. The failure is commands::tests::bootstrap_payload_exposes_every_provider_variant (catalog 79 vs 78 active providers). It concerns the provider catalog and reads machine settings (a deprecated provider that is already enabled stays in the catalog); this change touches no provider code. Not verified against a clean base.
  • pnpm exec vitest run (full): 68 files, 408 tests passed (6 new in PreferencesTransferSection.test.tsx)
  • pnpm run lint: no new warnings; pnpm run build: ok

Affected areas

  • Rust backend / CLI (rust/src/settings/preferences_document.rs, rust/src/cli/config.rs, rust/src/locale*)
  • Tauri shell (commands/preferences_transfer.rs, main.rs)
  • Frontend Settings > Advanced (PreferencesTransferSection.tsx, lib/tauri.ts, i18n/keys.ts)
  • Docs (docs/CONFIGURATION.md)
  • Tray icon / float bar rendering (refreshed on import, no rendering code changed)

No file crosses 1000 lines (settings.rs gains 2 lines; locale.rs was already over 1000 and gains 6).

UI proof

Pending: coordinator will capture CUA proof on a fresh build (Settings > Advanced > Portable preferences: export, then import with a modified file and confirm the toggles and language update live).

@coderabbitai

coderabbitai Bot commented Sep 29, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Thermo-nuclear review

Head reviewed: c9fc004. Overall the structure is good: the allowlist/validator table plus a compile-checked EXCLUDED_KEYS classification test is the right shape (a new Settings field defaults to excluded), all logic lives in one 364-line module with tests in a sibling file, no file crosses 1000 lines, no new dependency, and the capabilities already cover dialog:allow-open (main + settings) and dialog:allow-save (settings). The findings below are the remaining gaps.

Valid findings (fixing in a follow-up commit)

  1. config preferences export requires --file. Upstream (CLIConfigPreferences.swift @ v0.67.0: "export defaults to stdout") and the audit spec both make --file optional for export and write to stdout. The last commit over-corrected "like upstream". Fix: Option<PathBuf>, stdout when absent.
  2. import_preferences (shell) unconditionally prunes the provider cache, invalidates in-flight refreshes and spawns a full do_refresh_providers, even for a display-only file. update_settings only does this when the enabled provider set changes. Importing a theme/scale file should not refetch every provider. Fix: gate on the enabled-provider set actually changing.
  3. Nit: percent checks is_number() and then as_f64(); as_f64() already returns None for non-numbers. Delete the redundant guard.

Noted, not changing

  • CLI and shell both run load -> apply_to -> save. Three lines each; the shell needs previous_language/previous_enabled in between, so a shared helper would not delete anything.
  • The shell's post-save side effects mirror update_settings, which is itself inline and patch-driven. There is no shared "after settings saved" helper to reuse for a whole-document import.
  • The audit spec asks for JSON status output on CLI import; upstream only does that under its global --format json, which the local config subcommands do not have (they print Config: ... text). Not adding a new output-format flag in this PR.
  • The section lives in Settings -> Advanced rather than General as the audit text suggested; the audit said final placement is decided in review, and the docs already say Advanced.
  • write_file is not atomic (truncate-then-write). A failed export can leave a partial file at a user-chosen path; acceptable for an export that does not touch settings.json.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Thermo-nuclear review follow-up

Fixes for the findings above landed (commit "Address thermo review"). Reviewed by Codex gpt-6-luna (xhigh); verified and validated by Claude.

Fixed:

  1. config preferences export: --file is optional; JSON goes to stdout when omitted (parse test added, docs updated).
  2. Shell import_preferences: provider cache prune, refresh invalidation and full refresh now run only when the enabled-provider set changes (unit test for the set comparison).
  3. percent validator: dropped the redundant is_number() guard.
  4. Additional (Codex): export no longer fails when loaded settings hold provider IDs unknown to this build; those provider-scoped entries are omitted from the export. Import stays strict. Test added.

Left as noted above: shared load/apply/save helper, JSON status output on CLI import, Advanced tab placement, non-atomic export write.

Commands run (Rust 1.98.0, slot-1): cargo fmt --all -- --check; clippy --all-targets -- -D warnings on rust and apps/desktop-tauri/src-tauri; tests preferences (13), cli::config (5), preferences_transfer (1). No frontend change; no UI-visible change.

@Finesssee

Finesssee commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator Author

CUA proof

Build commit: f74ac0f72013f45c0710ff38c76b1ea531153161 (head of port/micro-0.67.0-portable-preferences), debug desktop build, Windows 11, driven with the cua-driver CLI in background mode only (UIA invoke/select, window screenshots; no foreground, no global input). Windows were kept on a second monitor.

Proof-only patches (throwaway, reverted, never committed): workspace Cargo.toml [patch.crates-io] dirs = { path = ".../proof-shim/dirs" } so home/config/data dirs resolve under CODEXBAR_PROOF_HOME (isolated profile, empty provider homes, dummy keys, enabled_providers: []). No source patch, no mock (the surface reads no provider data).

Commands: bash launch.sh trayPanel -> tray panel "Settings..." -> Settings window (label settings) -> Advanced tab -> Export / Import preferences. Native dialogs: Save/Open from the isolated profile's Desktop; file name cannot be typed in the background, so the default name codexbar-preferences.json and pre-placed import fixtures were used.

# Assertion Result
0 No real email/account from the user's machine visible PASS
1 Advanced tab shows "Portable preferences", caption, "Export preferences" and "Import preferences"; theme dark under auto PASS
2 Export: Save dialog default name codexbar-preferences.json; status "Preferences exported."; file is {"version":1,"preferences":{...}} with refresh_interval_secs: 300, no provider_configs/http_proxy/api_key PASS
3 Import valid file: "Preferences imported."; General shows refresh "15 minutes"; Notifications shows Show Notifications off, thresholds 55/80 PASS (show_as_used not checked in the UI; settings.json is DPAPI-encrypted after save)
4 Import file with provider_configs: red "Invalid or non-portable preference: provider_configs"; General still 15 minutes, theme Auto PASS
5 Buttons enabled after each action; cancelling the Open dialog shows no message PASS

Note (proof-harness, not a PR defect): with CODEXBAR_PROOF_MODE=settings:advanced Settings renders inside the main window, where dialog:allow-save is not granted (capability usage-spend-save-dialog is limited to window settings), so Export shows a "dialog.save not allowed on window main" error there. The real detached settings window (opened from the tray panel) works, as shown above.

Screenshots (local, not committed), %LOCALAPPDATA%\Win-CodexBar\port-audit\proof\654\shots\: 41-settings-advanced.png (assertions 0, 1), 43-save-as.png, 47-after-export.png (2), 49-after-import.png, 50-general.png, 51-notifications.png (3), 55-invalid.png (4), 56-open3.png, 57-after-cancel.png (5), 32-after-export-click.png (harness note).

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Adversarial validation (lane-B review)

Head validated: f74ac0f7 ("Address thermo review"). Spec: 0.67.0.md PR 5 (item 2, portable preferences) + upstream PreferencesDocument.swift / CLIConfigPreferences.swift at v0.67.0 (tag-pinned reads).

Verdict: no blocking defects. The implementation matches the spec and the PR body's claims.

Checks performed (code review at head + local re-run):

  • Rust validation suite: cargo +1.98.0 fmt --all -- --check clean; cargo +1.98.0 clippy --workspace --all-targets -- -D warnings clean; cargo +1.98.0 test -p codexbar --lib: 2173 passed, 0 failed (includes the 12 document tests + CLI parse test); cargo +1.98.0 test -p codexbar-desktop-tauri: 462 passed, 1 failed — the failure is bootstrap_payload_exposes_every_provider_variant (catalog 79 vs 78), the documented Isolate bootstrap payload test from real settings #684 machine-state baseline, untouched by this diff.
  • Frontend: pnpm test (vitest) 68 files / 408 passed; pnpm run check-locale OK (885 keys); pnpm run build clean.
  • Spec conformance reviewed from the source: versioned envelope with strict unknown-key/version/type/range rejection before any write; null restores documented defaults; missing keys leave the receiving machine unchanged (test applying_keeps_everything_else_on_the_receiving_machine pins provider_configs tokens + proxy password surviving an import); export-only-allowlist with the whole-field classifier test (every serialized Settings field must be classified, else the test fails — so a new field defaults to excluded, matching the PR body); 256 KiB bound; secrets never echoed in errors (errors_never_echo_values).
  • CLI matches upstream's --file shape: export writes the file (stdout without it), import requires --file.
  • Desktop import reuses the settings-window dialog capabilities and replays the same live updates as update_settings (locale event, provider prune+refresh when the enabled set changed, float bar, tray, settings-updated).

Integrator note (merge glue required): the classifier test requires every Settings field to be in ALLOWED_PREFERENCES or EXCLUDED_KEYS. This branch's base predates #609/#648/#700, so after those merge, glue on the integration branch must classify:

@Finesssee

Copy link
Copy Markdown
Collaborator Author

UI proof (browser-use)

Combined build of integrate/v0.70.0-ports at ddd85594 (all 40 port PRs and the follow-ups). Debug desktop build in proof mode, isolated config/data dirs, driven by browser-use over the WebView2 DevTools protocol with DOM events only (no mouse or keyboard input, no focus changes). All data is synthetic: local mock servers for L1, a seeded usage snapshot plus synthetic local logs for L2.

Follow-up 8f4ed03 classifies the settings added since this PR's base (portable: cost_reporting_period, preferred_currency_code, switcher_shortcuts; excluded: machine-local fields).

Scenario Check Result
L1 extras (preferences, keys, toggles) #654 Advanced: 'Portable preferences' section with Export/Import buttons and the no-secrets caption PASS
L1 extras (preferences, keys, toggles) #654 export: 'Preferences exported.' and the file is written PASS
L1 extras (preferences, keys, toggles) #654 export: no secrets / machine-local fields (provider_configs, stay_awake, bucket zone, keys, cookies) PASS
L1 extras (preferences, keys, toggles) #654 export (combined, 8f4ed03 classification): cost_reporting_period + preferred_currency_code present, switcher_shortcuts skipped while empty PASS
L1 extras (preferences, keys, toggles) #654 import: 'Preferences imported.'; threshold 61 and switcher override applied PASS
L1 extras (preferences, keys, toggles) #654 import of a reserved switcher key (ctrl+r) is rejected whole: alert shown, threshold stays 61 PASS
L1 extras (preferences, keys, toggles) #654 import with a machine-local key (stay_awake_enabled) is rejected whole; nothing changes PASS
L1 editor reset re-export #654 re-export keeps the imported threshold 61 PASS

Every surface also passed the privacy check (no email-like text, account e-mail nodes or profile paths in the DOM) and theme auto rendered dark on the tray flyout, float bar and settings windows.

Validation at ddd85594: cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test -p codexbar (3567 passed, 0 failed) and cargo test -p codexbar-desktop-tauri (606 passed, 0 failed). Screenshots were captured for each scenario and kept with the local proof kit.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Shipped in v0.70.0: this PR's head is included in main via #735 (merge commit 9d0a37a). Closing as integrated.

@Finesssee Finesssee closed this Oct 3, 2026
junglesub-bot Bot pushed a commit to junglesub/Win-CodexBar that referenced this pull request Oct 4, 2026
…rt and import

Conflict in rust/src/settings.rs: the release added the optional_details
module next to the new preferences_document module; both module
declarations and re-exports are kept.

The document classifier test needs every Settings field classified; the
fields that reached the release after this PR's base are classified in the
follow-up commit, together with the import side effects they need.
junglesub-bot Bot pushed a commit to junglesub/Win-CodexBar that referenced this pull request Oct 4, 2026
The preferences document test requires every Settings field to be either
portable or excluded. Fields that reached the release after nesszer#654's base:

- Portable: cost_reporting_period and preferred_currency_code (nesszer#654 asks
  for both once they land), switcher_shortcuts (nesszer#700 deferred it to this
  document; upstream 0.70.0 exports switcherShortcuts). Validators accept
  only stored forms: canonical periods, AUTO or a supported code, and
  overrides that pass the switcher shortcut rules.
- Excluded: stay_awake_enabled (side-effect toggle, nesszer#659),
  cost_usage_bucket_time_zone (pinned to the machine zone on first launch,
  no settings control), and menu_bar_color_pace, the stacked tray provider
  picks and credential_expiry_notifications_enabled, which upstream 0.70.0
  also keeps out of its portable keys.

null on a field omitted when empty (switcher_shortcuts) now clears it
instead of failing. A desktop import that changes the reporting period
resets the local cost cache, as update_settings does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant