fix(control-ui): support raw edits from editable config

This commit is contained in:
clawsweeper
2026-05-26 02:24:32 +00:00
parent 24eba3a7e6
commit befbe16362
3 changed files with 64 additions and 4 deletions

View File

@@ -56,6 +56,7 @@ import {
saveConfig,
stageDefaultAgentConfigEntry,
stageConfigPreset,
updateConfigRawValue,
updateConfigFormValue,
removeConfigFormValue,
} from "./controllers/config.ts";
@@ -1163,7 +1164,7 @@ export function renderApp(state: AppViewState) {
formValue: state.configForm,
originalValue: state.configFormOriginal,
onRawChange: (next: string) => {
state.configRaw = next;
updateConfigRawValue(state, next);
},
onRequestUpdate: requestHostUpdate,
onFormPatch: (path: Array<string | number>, value: unknown) =>

View File

@@ -11,6 +11,7 @@ import {
stageDefaultAgentConfigEntry,
stageConfigPreset,
updateConfigFormValue,
updateConfigRawValue,
type ConfigState,
} from "./config.ts";
@@ -198,6 +199,54 @@ describe("applyConfigSnapshot", () => {
expect(state.configFormMode).toBe("raw");
expect(state.configRaw).toBe('{\n "gateway": {\n "mode": "local"\n }\n}\n');
});
it("does not clobber raw edits while dirty", () => {
const state = createState();
state.configFormMode = "raw";
applyConfigSnapshot(state, {
hash: "hash-original",
config: { gateway: { mode: "local" } },
valid: true,
issues: [],
raw: '{\n "gateway": { "mode": "local" }\n}\n',
});
updateConfigRawValue(state, '{\n "gateway": { "mode": "remote" }\n}\n');
applyConfigSnapshot(state, {
hash: "hash-refreshed",
config: { gateway: { mode: "external" } },
valid: true,
issues: [],
raw: '{\n "gateway": { "mode": "external" }\n}\n',
});
expect(state.configSnapshot?.hash).toBe("hash-refreshed");
expect(state.configDraftBaseHash).toBe("hash-original");
expect(state.configRaw).toBe('{\n "gateway": { "mode": "remote" }\n}\n');
});
});
describe("updateConfigRawValue", () => {
it("tracks raw edits as pending changes", () => {
const state = createState();
applyConfigSnapshot(state, {
hash: "hash-original",
config: { gateway: { mode: "local" } },
valid: true,
issues: [],
raw: '{\n "gateway": { "mode": "local" }\n}\n',
});
updateConfigRawValue(state, '{\n "gateway": { "mode": "remote" }\n}\n');
expect(state.configFormDirty).toBe(true);
expect(state.configDraftBaseHash).toBe("hash-original");
updateConfigRawValue(state, '{\n "gateway": { "mode": "local" }\n}\n');
expect(state.configFormDirty).toBe(false);
expect(state.configDraftBaseHash).toBe("hash-original");
});
});
describe("loadConfig", () => {

View File

@@ -123,11 +123,11 @@ export function applyConfigSnapshot(
: editableConfig
? serializeConfigForm(editableConfig)
: state.configRaw;
if (!preservePendingChanges || state.configFormMode === "raw") {
if (!preservePendingChanges) {
state.configRaw = rawFromSnapshot;
} else if (state.configForm) {
} else if (state.configFormMode !== "raw" && state.configForm) {
state.configRaw = serializeConfigForm(state.configForm);
} else {
} else if (state.configFormMode !== "raw") {
state.configRaw = rawFromSnapshot;
}
state.configValid = typeof snapshot.valid === "boolean" ? snapshot.valid : null;
@@ -390,6 +390,16 @@ export function updateConfigFormValue(
});
}
export function updateConfigRawValue(state: ConfigState, value: string) {
state.configRaw = value;
state.configFormDirty = value !== state.configRawOriginal;
if (state.configFormDirty) {
state.configDraftBaseHash = state.configDraftBaseHash ?? state.configSnapshot?.hash ?? null;
} else {
state.configDraftBaseHash = state.configSnapshot?.hash ?? null;
}
}
export function stageConfigPreset(state: ConfigState, patch: Record<string, unknown>) {
const snapshotConfig = resolveEditableSnapshotConfig(state.configSnapshot);
const baseSource = state.configForm ?? snapshotConfig;