feat(settings): data-safe editors, deep-linked search, Data/About/Cost pages
- EditorModal loads once per path (a parent re-render no longer reloads the file over the user's typing), refuses Save after a failed load, opens a truncated (>256 KB) file read-only, and asks before overwriting a file that changed on disk or could not be re-read. - Settings/Hooks editors unset only keys from the snapshot taken at Edit, warn when the file changed underneath, keep a renamed key's value, and refuse a new key named like a redacted one (env) or a read-only hook event, which used to replace the secret/hooks. - Project scope shows its resolved folder and is disabled inside the plugin cache. Account-profile, pricing-row and pet deletes confirm; two-click actions disarm after 3 s; unsaved drafts ask before closing. - Search indexes every Claude Code / Codex sub-tab, opens it via ?tab= and scrolls to + highlights the row; the active config tab is in the URL. - General splits into General / Data / About; Pricing moves to a Cost group with one shared load; config tabs group as Content / Extensions / Config with arrow-key roving; Promise.allSettled per tab. - Data: browser-preferences export/import (allowlisted keys), per-section reset, housekeeping dry-run first, clear-all behind a typed phrase. Server: purge days must be a whole number >= 1 (INVALID_PURGE_DAYS) and dry_run returns exactly what the real run would delete. - Alerts can be edited; webhooks/alerts/remote-source failures toast; copies go through copyText; modals get dialog roles and useDialog. - Sagi: Ctrl/Cmd+B only when shown and not typing, default spot clear of row controls, arrow keys move it, reduced motion honoured; the dead /run launch link goes to the workspace.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @file Tests for the browser-preferences backup: only allowlisted keys leave
|
||||
* (never the access token), an import writes only allowlisted keys, and a file
|
||||
* that is not one of ours is refused.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { exportPrefs, importPrefs, PREFS_FORMAT } from "../prefsBackup";
|
||||
|
||||
beforeEach(() => localStorage.clear());
|
||||
|
||||
describe("prefsBackup", () => {
|
||||
it("exports only preference keys, never the access token", () => {
|
||||
localStorage.setItem("theme", "dark");
|
||||
localStorage.setItem("agenthub.pet.size", "2");
|
||||
localStorage.setItem("agent-monitor-notifications", "{}");
|
||||
localStorage.setItem("dashboard_token", "secret");
|
||||
localStorage.setItem("sidebar-connection-stats", "{}");
|
||||
const out = exportPrefs();
|
||||
expect(out.format).toBe(PREFS_FORMAT);
|
||||
expect(Object.keys(out.prefs).sort()).toEqual([
|
||||
"agent-monitor-notifications",
|
||||
"agenthub.pet.size",
|
||||
"theme",
|
||||
]);
|
||||
});
|
||||
|
||||
it("imports allowlisted keys only and refuses a foreign file", () => {
|
||||
const n = importPrefs(
|
||||
JSON.stringify({
|
||||
format: PREFS_FORMAT,
|
||||
version: 1,
|
||||
prefs: { theme: "light", dashboard_token: "evil", "agenthub.x": 5 },
|
||||
})
|
||||
);
|
||||
expect(n).toBe(1);
|
||||
expect(localStorage.getItem("theme")).toBe("light");
|
||||
expect(localStorage.getItem("dashboard_token")).toBeNull();
|
||||
expect(() => importPrefs(JSON.stringify({ format: "other", prefs: {} }))).toThrow();
|
||||
expect(() => importPrefs("not json")).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user