diff --git a/ui/src/config-editor/store.test.ts b/ui/src/config-editor/store.test.ts index 6d5ef4f9..bb8493b8 100644 --- a/ui/src/config-editor/store.test.ts +++ b/ui/src/config-editor/store.test.ts @@ -99,6 +99,22 @@ describe("ConfigEditorStore", () => { expect(store.serializeSelectedFile()).toBe("motd=hello\n"); }); + it("preserves unresolved numeric runtime placeholders without validation errors", () => { + const store = loadedStore( + "motd=hello\nserver-port=__DRUID_PORT_MAIN__\n", + fileSchema([ + baseField({ key: "motd", type: "string", min: undefined, max: undefined }), + baseField({ key: "server-port" }), + ]), + ); + + expect(store.snapshot().fields["server-port"]!.issues).toEqual([]); + store.setDisplayValue("motd", "updated"); + expect(store.serializeSelectedFile()).toBe( + "motd=updated\nserver-port=__DRUID_PORT_MAIN__\n", + ); + }); + it("redacts secret values from change summaries and immutable snapshots", () => { const store = loadedStore( "password=old-secret\n", diff --git a/ui/src/config-editor/store.ts b/ui/src/config-editor/store.ts index 9c037eec..c10d4292 100644 --- a/ui/src/config-editor/store.ts +++ b/ui/src/config-editor/store.ts @@ -193,6 +193,13 @@ export class ConfigEditorStore { ): { value: ConfigValue | undefined; issues: ValidationIssue[] } { const raw = this.adapter.get(document, field.key); if (raw === undefined) return { value: undefined, issues: [] }; + if ( + typeof raw === "string" && + (field.type === "integer" || field.type === "number") && + /^__DRUID_[A-Z0-9_]+__$/.test(raw) + ) { + return { value: undefined, issues: [] }; + } try { const value = coerceFieldValue(field, raw); return { value, issues: validateField(field, value) };