fix(gate): stop injecting the redundant Altro option on select gates

buildSelectRequest injected an `altroOption` ({id:"other", opens:freetext}) into the
select descriptor, but SelectWidget/MultiselectWidget never route option.opens — so it
rendered as an inert "Altro — specifica…" button next to the working reserved
"Other — specify" control (which now owns free-text since the reviewer-gate-ux fix).
Remove the injection, the now-unused altroOption()/ALTRO_LABEL, and the unused
allow_other flag from both builders (no frontend consumer). Free-text stays offered on
every gate via the reserved "Other" control. The ArtifactGateWidget opens/LinkageHost
linkage (reject-with-reason capability) is intentionally kept — it is a separate,
tested feature, not the injected duplicate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 19:10:01 +02:00
co-authored by Claude Opus 4.8
parent 25ab37d0fe
commit b043293baf
4 changed files with 14 additions and 31 deletions
@@ -33,20 +33,17 @@ test("select F1 matches golden shape", () => {
assert.equal(result.type, "ui_request");
assert.equal(result.schema_version, 1);
assert.deepEqual(result.reserved, g.reserved); // back/exit/other framework hatches
assert.deepEqual(
result.options.filter((o) => o.id !== "other"),
g.options.filter((o) => o.id !== "other"),
);
assert.deepEqual(result.options, g.options);
});
test("select always carries the Altro escape hatch with freetext linkage", () => {
test("select no longer injects an Altro option (free-text is the reserved 'Other' control)", () => {
const result = buildSelectRequest({
id: "u", phase: "F1", title: "x",
options: [{ id: "a", label: "A" }],
});
const other = result.options.find((o) => o.id === "other");
assert.ok(other, "Altro option must always be present");
assert.equal(other.opens.widget, "freetext");
assert.equal(result.options.find((o) => o.id === "other"), undefined, "no injected Altro option");
assert.ok(!("allow_other" in result), "the unused allow_other flag is gone");
assert.deepEqual(result.reserved, ["back", "exit", "other"], "Altro still offered via the reserved control");
});
test("select surfaces a recommended marker when given", () => {
@@ -70,6 +67,7 @@ test("multiselect F4 matches golden + carries content + allowEmpty", () => {
const g = golden("multiselect_F4.json");
assert.equal(result.widget, "multiselect");
assert.equal(result.allow_empty, false);
assert.ok(!("allow_other" in result), "the unused allow_other flag is gone");
assert.ok("content" in result);
assert.deepEqual(result.reserved, g.reserved);
});
@@ -6,7 +6,6 @@
"widget": "multiselect",
"title": "Tabelle",
"allow_empty": false,
"allow_other": true,
"options": [],
"selected": [],
"content": { "candidates": [] },
@@ -6,12 +6,10 @@
"widget": "select",
"title": "Disambigua 'ablazione'",
"intro": null,
"allow_other": true,
"recommended": null,
"options": [
{ "id": "o1", "label": "procedura" },
{ "id": "o2", "label": "patologia" },
{ "id": "other", "label": "Altro — specifica…", "opens": { "widget": "freetext", "title": "Specifica…" } }
{ "id": "o2", "label": "patologia" }
],
"reserved": ["back", "exit", "other"]
}
+7 -19
View File
@@ -15,7 +15,6 @@
const SCHEMA_VERSION = 1;
const RESERVED = ["back", "exit", "other"];
const ALTRO_LABEL = "Altro — specifica…";
const VALID_ACTION_KINDS = new Set(["confirm", "approve_reject", "view_only"]);
const VALID_INFO_LEVELS = new Set(["info", "warning", "error"]);
@@ -33,21 +32,12 @@ function requireArray(value, name, ctx) {
return value;
}
// An "other" option that opens a freetext child widget. Always present on
// blocking pick widgets so the reviewer is never trapped in the listed options.
function altroOption() {
return {
id: "other",
label: ALTRO_LABEL,
opens: { widget: "freetext", title: "Specifica…" },
};
}
// Build a `select` ui_request (single-pick). Reserved hatches (Altro/Back/Exit)
// are injected; Altro carries the freetext linkage (§4.2). `recommended` is the
// id of the option to highlight as "(consigliato)".
// Build a `select` ui_request (single-pick). The framework escape hatches
// (Back/Exit/Other) come from `reserved` (rendered by ReservedControls, which owns
// the free-text linkage on "Other"). `recommended` is the id of the option to
// highlight as "(consigliato)".
function buildSelectRequest({
id, phase, title, options, intro = null, allowOther = true, recommended = null,
id, phase, title, options, intro = null, recommended = null,
}) {
requireString(title, "title", "select");
const opts = requireArray(options, "options", "select");
@@ -59,9 +49,8 @@ function buildSelectRequest({
widget: "select",
title,
intro,
allow_other: allowOther,
recommended,
options: allowOther ? [...opts, altroOption()] : [...opts],
options: [...opts],
reserved: RESERVED,
};
return out;
@@ -71,7 +60,7 @@ function buildSelectRequest({
// context (e.g. schema candidates); `selected` the pre-checked option ids;
// `allowEmpty:false` with zero options is a broken widget and throws.
function buildMultiselectRequest({
id, phase, title, options, content = null, selected = [], allowEmpty = false, allowOther = true,
id, phase, title, options, content = null, selected = [], allowEmpty = false,
}) {
requireString(title, "title", "multiselect");
const opts = requireArray(options, "options", "multiselect");
@@ -88,7 +77,6 @@ function buildMultiselectRequest({
widget: "multiselect",
title,
allow_empty: allowEmpty,
allow_other: allowOther,
options: [...opts],
selected: [...selected],
content,