fix: keep raw config edits scoped to config view (#1673) (thanks @Glucksberg)

This commit is contained in:
Peter Steinberger
2026-01-25 02:42:31 +00:00
parent 2e3b14187b
commit c3e777e3e1
4 changed files with 57 additions and 1 deletions

View File

@@ -21,6 +21,7 @@ Docs: https://docs.clawd.bot
- BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.
- Web UI: hide internal `message_id` hints in chat bubbles.
- Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
- Web UI: keep raw config edits from toggling channel save state; enable save/apply on raw changes only. (#1673) Thanks @Glucksberg.
- Heartbeat: normalize target identifiers for consistent routing.
- TUI: reload history after gateway reconnect to restore session state. (#1663)
- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)

View File

@@ -48,15 +48,29 @@ describe("models-config", () => {
const previous = process.env.COPILOT_GITHUB_TOKEN;
const previousGh = process.env.GH_TOKEN;
const previousGithub = process.env.GITHUB_TOKEN;
const previousVenice = process.env.VENICE_API_KEY;
const previousKimiCode = process.env.KIMICODE_API_KEY;
const previousKimiCodeAlt = process.env.KIMI_CODE_API_KEY;
const previousMinimax = process.env.MINIMAX_API_KEY;
const previousMoonshot = process.env.MOONSHOT_API_KEY;
const previousSynthetic = process.env.SYNTHETIC_API_KEY;
const previousAwsAccessKey = process.env.AWS_ACCESS_KEY_ID;
const previousAwsSecretKey = process.env.AWS_SECRET_ACCESS_KEY;
const previousAwsProfile = process.env.AWS_PROFILE;
const previousAwsBearer = process.env.AWS_BEARER_TOKEN;
delete process.env.COPILOT_GITHUB_TOKEN;
delete process.env.GH_TOKEN;
delete process.env.GITHUB_TOKEN;
delete process.env.VENICE_API_KEY;
delete process.env.KIMICODE_API_KEY;
delete process.env.KIMI_CODE_API_KEY;
delete process.env.MINIMAX_API_KEY;
delete process.env.MOONSHOT_API_KEY;
delete process.env.SYNTHETIC_API_KEY;
delete process.env.AWS_ACCESS_KEY_ID;
delete process.env.AWS_SECRET_ACCESS_KEY;
delete process.env.AWS_PROFILE;
delete process.env.AWS_BEARER_TOKEN;
try {
vi.resetModules();
@@ -79,12 +93,26 @@ describe("models-config", () => {
else process.env.GH_TOKEN = previousGh;
if (previousGithub === undefined) delete process.env.GITHUB_TOKEN;
else process.env.GITHUB_TOKEN = previousGithub;
if (previousVenice === undefined) delete process.env.VENICE_API_KEY;
else process.env.VENICE_API_KEY = previousVenice;
if (previousKimiCode === undefined) delete process.env.KIMICODE_API_KEY;
else process.env.KIMICODE_API_KEY = previousKimiCode;
if (previousKimiCodeAlt === undefined) delete process.env.KIMI_CODE_API_KEY;
else process.env.KIMI_CODE_API_KEY = previousKimiCodeAlt;
if (previousMinimax === undefined) delete process.env.MINIMAX_API_KEY;
else process.env.MINIMAX_API_KEY = previousMinimax;
if (previousMoonshot === undefined) delete process.env.MOONSHOT_API_KEY;
else process.env.MOONSHOT_API_KEY = previousMoonshot;
if (previousSynthetic === undefined) delete process.env.SYNTHETIC_API_KEY;
else process.env.SYNTHETIC_API_KEY = previousSynthetic;
if (previousAwsAccessKey === undefined) delete process.env.AWS_ACCESS_KEY_ID;
else process.env.AWS_ACCESS_KEY_ID = previousAwsAccessKey;
if (previousAwsSecretKey === undefined) delete process.env.AWS_SECRET_ACCESS_KEY;
else process.env.AWS_SECRET_ACCESS_KEY = previousAwsSecretKey;
if (previousAwsProfile === undefined) delete process.env.AWS_PROFILE;
else process.env.AWS_PROFILE = previousAwsProfile;
if (previousAwsBearer === undefined) delete process.env.AWS_BEARER_TOKEN;
else process.env.AWS_BEARER_TOKEN = previousAwsBearer;
}
});
});

View File

@@ -512,7 +512,6 @@ export function renderApp(state: AppViewState) {
activeSubsection: state.configActiveSubsection,
onRawChange: (next) => {
state.configRaw = next;
state.configFormDirty = true;
},
onFormModeChange: (mode) => (state.configFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value),

View File

@@ -96,6 +96,34 @@ describe("config view", () => {
expect(applyButton?.disabled).toBe(true);
});
it("enables save and apply when raw changes", () => {
const container = document.createElement("div");
render(
renderConfig({
...baseProps(),
formMode: "raw",
raw: "{\n gateway: { mode: \"local\" }\n}\n",
originalRaw: "{\n}\n",
}),
container,
);
const saveButton = Array.from(
container.querySelectorAll("button"),
).find((btn) => btn.textContent?.trim() === "Save") as
| HTMLButtonElement
| undefined;
const applyButton = Array.from(
container.querySelectorAll("button"),
).find((btn) => btn.textContent?.trim() === "Apply") as
| HTMLButtonElement
| undefined;
expect(saveButton).not.toBeUndefined();
expect(applyButton).not.toBeUndefined();
expect(saveButton?.disabled).toBe(false);
expect(applyButton?.disabled).toBe(false);
});
it("switches mode via the sidebar toggle", () => {
const container = document.createElement("div");
const onFormModeChange = vi.fn();