fix: address Copilot review round 41 — const does not demand a property, required-only branches

- `const` constrains a property that is PRESENT; it neither requires the
  property nor acts as a default. It is now supplied automatically only where
  the schema also requires the field — in `collectSchemaDefaults`,
  `applySchemaConstants` and the TUI's initial values alike — so an optional
  `dryRun: { const: true }` no longer turns a valid `{}` call into one that
  asks the server to do something. A value that IS present is still corrected
  to the constant, whatever supplied it, and the field stays read-only.
- Offerability is judged on the base+member merge rather than the raw member,
  so `anyOf: [{ required: ["email"] }, { required: ["phone"] }]` over
  root-declared properties is offered. It was declined, which left the gate
  checking the base alone and accepting `{}` — which that schema rejects.
- The TUI's branch sections keep the schema's own `required` for the seeding
  decision while still rendering every branch field non-required, which is what
  keeps a static form submittable.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-26 16:08:24 -04:00
commit b0b252a5c0

View file

@ -555,6 +555,7 @@ describe("schemaToForm", () => {
{
type: "object",
properties: { v: { type: "string", const: "a", default: "b" } },
required: ["v"],
},
"const_tool",
);
@ -565,6 +566,26 @@ describe("schemaToForm", () => {
});
});
it("leaves an optional const unfilled", () => {
// `const` constrains a present value; it does not require the property
// or act as a default, so an optional one must stay omittable.
const form = schemaToForm(
{
type: "object",
properties: { dryRun: { type: "boolean", const: true } },
},
"optional_const",
);
const field = form.sections[0]!.fields[0] as {
initialValue?: unknown;
options?: unknown[];
};
expect(field.initialValue).toBeUndefined();
// The single option is still offered to a user who wants it.
expect(field.options).toEqual([{ label: "true", value: "true" }]);
expect(decodeFormValues({ type: "object" }, {})).toEqual({});
});
it("renders a branch's specialization of a root property in its section", () => {
const form = schemaToForm(
{