fix: restore reveal-to-edit raw config flow

This commit is contained in:
Peter Steinberger
2026-03-26 23:44:17 +00:00
parent d25c4fd6c5
commit f0c1057f68
3 changed files with 112 additions and 15 deletions

View File

@@ -1,7 +1,7 @@
import { render } from "lit";
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ThemeMode, ThemeName } from "../theme.ts";
import { renderConfig } from "./config.ts";
import { renderConfig, type ConfigProps } from "./config.ts";
describe("config view", () => {
const baseProps = () => ({
@@ -59,6 +59,49 @@ describe("config view", () => {
};
}
function renderConfigView(overrides: Partial<ConfigProps> = {}): {
container: HTMLElement;
props: ConfigProps;
} {
const container = document.createElement("div");
const props = {
...baseProps(),
...overrides,
};
const rerender = () =>
render(
renderConfig({
...props,
onRequestUpdate: rerender,
}),
container,
);
rerender();
return { container, props };
}
function normalizedText(container: HTMLElement): string {
return container.textContent?.replace(/\s+/g, " ").trim() ?? "";
}
function resetRawRevealState() {
const { container } = renderConfigView({
formMode: "raw",
raw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
originalRaw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
formValue: {
openai: {
apiKey: "supersecret",
},
},
});
container.querySelector<HTMLButtonElement>(".config-raw-toggle.active")?.click();
}
beforeEach(() => {
resetRawRevealState();
});
it("allows save when form is unsafe", () => {
const container = document.createElement("div");
render(
@@ -242,7 +285,6 @@ describe("config view", () => {
expect(tabs).toContain("Settings");
expect(tabs).toContain("Agents");
expect(tabs).toContain("Gateway");
expect(tabs).toContain("Appearance");
});
it("clears the active search query", () => {
@@ -262,4 +304,52 @@ describe("config view", () => {
clearButton?.click();
expect(onSearchChange).toHaveBeenCalledWith("");
});
it("keeps sensitive raw config hidden until reveal", () => {
const { container } = renderConfigView({
formMode: "raw",
raw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
originalRaw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
formValue: {
openai: {
apiKey: "supersecret",
},
},
});
const text = normalizedText(container);
expect(text).toContain("1 secret redacted");
expect(text).toContain("Use the reveal button above to edit the raw config.");
expect(text).not.toContain("supersecret");
expect(container.querySelector("textarea")).toBeNull();
});
it("reveals sensitive raw config before editing", () => {
const onRawChange = vi.fn();
const { container } = renderConfigView({
formMode: "raw",
raw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
originalRaw: '{\n "openai": { "apiKey": "supersecret" }\n}\n',
formValue: {
openai: {
apiKey: "supersecret",
},
},
onRawChange,
});
const revealButton = container.querySelector<HTMLButtonElement>(".config-raw-toggle");
expect(revealButton).toBeTruthy();
revealButton?.click();
const textarea = container.querySelector<HTMLTextAreaElement>("textarea");
expect(textarea).not.toBeNull();
expect(textarea?.value).toContain("supersecret");
if (!textarea) {
return;
}
textarea.value = textarea.value.replace("supersecret", "updatedsecret");
textarea.dispatchEvent(new Event("input", { bubbles: true }));
expect(onRawChange).toHaveBeenCalledWith(textarea.value);
});
});

View File

@@ -1117,18 +1117,24 @@ export function renderConfig(props: ConfigProps) {
: nothing
}
</span>
<textarea
class="${blurred ? "config-raw-redacted" : ""}"
placeholder=${blurred ? REDACTED_PLACEHOLDER : "Raw config (JSON/JSON5)"}
.value=${blurred ? "" : props.raw}
?readonly=${blurred}
@input=${(e: Event) => {
if (blurred) {
return;
}
props.onRawChange((e.target as HTMLTextAreaElement).value);
}}
></textarea>
${
blurred
? html`
<div class="callout info" style="margin-top: 12px">
${sensitiveCount} sensitive value${sensitiveCount === 1 ? "" : "s"} hidden. Use the
reveal button above to edit the raw config.
</div>
`
: html`
<textarea
placeholder="Raw config (JSON/JSON5)"
.value=${props.raw}
@input=${(e: Event) => {
props.onRawChange((e.target as HTMLTextAreaElement).value);
}}
></textarea>
`
}
</div>
`;
})()