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:
@@ -33,20 +33,17 @@ test("select F1 matches golden shape", () => {
|
|||||||
assert.equal(result.type, "ui_request");
|
assert.equal(result.type, "ui_request");
|
||||||
assert.equal(result.schema_version, 1);
|
assert.equal(result.schema_version, 1);
|
||||||
assert.deepEqual(result.reserved, g.reserved); // back/exit/other framework hatches
|
assert.deepEqual(result.reserved, g.reserved); // back/exit/other framework hatches
|
||||||
assert.deepEqual(
|
assert.deepEqual(result.options, g.options);
|
||||||
result.options.filter((o) => o.id !== "other"),
|
|
||||||
g.options.filter((o) => o.id !== "other"),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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({
|
const result = buildSelectRequest({
|
||||||
id: "u", phase: "F1", title: "x",
|
id: "u", phase: "F1", title: "x",
|
||||||
options: [{ id: "a", label: "A" }],
|
options: [{ id: "a", label: "A" }],
|
||||||
});
|
});
|
||||||
const other = result.options.find((o) => o.id === "other");
|
assert.equal(result.options.find((o) => o.id === "other"), undefined, "no injected Altro option");
|
||||||
assert.ok(other, "Altro option must always be present");
|
assert.ok(!("allow_other" in result), "the unused allow_other flag is gone");
|
||||||
assert.equal(other.opens.widget, "freetext");
|
assert.deepEqual(result.reserved, ["back", "exit", "other"], "Altro still offered via the reserved control");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("select surfaces a recommended marker when given", () => {
|
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");
|
const g = golden("multiselect_F4.json");
|
||||||
assert.equal(result.widget, "multiselect");
|
assert.equal(result.widget, "multiselect");
|
||||||
assert.equal(result.allow_empty, false);
|
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.ok("content" in result);
|
||||||
assert.deepEqual(result.reserved, g.reserved);
|
assert.deepEqual(result.reserved, g.reserved);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
"widget": "multiselect",
|
"widget": "multiselect",
|
||||||
"title": "Tabelle",
|
"title": "Tabelle",
|
||||||
"allow_empty": false,
|
"allow_empty": false,
|
||||||
"allow_other": true,
|
|
||||||
"options": [],
|
"options": [],
|
||||||
"selected": [],
|
"selected": [],
|
||||||
"content": { "candidates": [] },
|
"content": { "candidates": [] },
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
"widget": "select",
|
"widget": "select",
|
||||||
"title": "Disambigua 'ablazione'",
|
"title": "Disambigua 'ablazione'",
|
||||||
"intro": null,
|
"intro": null,
|
||||||
"allow_other": true,
|
|
||||||
"recommended": null,
|
"recommended": null,
|
||||||
"options": [
|
"options": [
|
||||||
{ "id": "o1", "label": "procedura" },
|
{ "id": "o1", "label": "procedura" },
|
||||||
{ "id": "o2", "label": "patologia" },
|
{ "id": "o2", "label": "patologia" }
|
||||||
{ "id": "other", "label": "Altro — specifica…", "opens": { "widget": "freetext", "title": "Specifica…" } }
|
|
||||||
],
|
],
|
||||||
"reserved": ["back", "exit", "other"]
|
"reserved": ["back", "exit", "other"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
const SCHEMA_VERSION = 1;
|
const SCHEMA_VERSION = 1;
|
||||||
const RESERVED = ["back", "exit", "other"];
|
const RESERVED = ["back", "exit", "other"];
|
||||||
|
|
||||||
const ALTRO_LABEL = "Altro — specifica…";
|
|
||||||
const VALID_ACTION_KINDS = new Set(["confirm", "approve_reject", "view_only"]);
|
const VALID_ACTION_KINDS = new Set(["confirm", "approve_reject", "view_only"]);
|
||||||
const VALID_INFO_LEVELS = new Set(["info", "warning", "error"]);
|
const VALID_INFO_LEVELS = new Set(["info", "warning", "error"]);
|
||||||
|
|
||||||
@@ -33,21 +32,12 @@ function requireArray(value, name, ctx) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// An "other" option that opens a freetext child widget. Always present on
|
// Build a `select` ui_request (single-pick). The framework escape hatches
|
||||||
// blocking pick widgets so the reviewer is never trapped in the listed options.
|
// (Back/Exit/Other) come from `reserved` (rendered by ReservedControls, which owns
|
||||||
function altroOption() {
|
// the free-text linkage on "Other"). `recommended` is the id of the option to
|
||||||
return {
|
// highlight as "(consigliato)".
|
||||||
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)".
|
|
||||||
function buildSelectRequest({
|
function buildSelectRequest({
|
||||||
id, phase, title, options, intro = null, allowOther = true, recommended = null,
|
id, phase, title, options, intro = null, recommended = null,
|
||||||
}) {
|
}) {
|
||||||
requireString(title, "title", "select");
|
requireString(title, "title", "select");
|
||||||
const opts = requireArray(options, "options", "select");
|
const opts = requireArray(options, "options", "select");
|
||||||
@@ -59,9 +49,8 @@ function buildSelectRequest({
|
|||||||
widget: "select",
|
widget: "select",
|
||||||
title,
|
title,
|
||||||
intro,
|
intro,
|
||||||
allow_other: allowOther,
|
|
||||||
recommended,
|
recommended,
|
||||||
options: allowOther ? [...opts, altroOption()] : [...opts],
|
options: [...opts],
|
||||||
reserved: RESERVED,
|
reserved: RESERVED,
|
||||||
};
|
};
|
||||||
return out;
|
return out;
|
||||||
@@ -71,7 +60,7 @@ function buildSelectRequest({
|
|||||||
// context (e.g. schema candidates); `selected` the pre-checked option ids;
|
// context (e.g. schema candidates); `selected` the pre-checked option ids;
|
||||||
// `allowEmpty:false` with zero options is a broken widget and throws.
|
// `allowEmpty:false` with zero options is a broken widget and throws.
|
||||||
function buildMultiselectRequest({
|
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");
|
requireString(title, "title", "multiselect");
|
||||||
const opts = requireArray(options, "options", "multiselect");
|
const opts = requireArray(options, "options", "multiselect");
|
||||||
@@ -88,7 +77,6 @@ function buildMultiselectRequest({
|
|||||||
widget: "multiselect",
|
widget: "multiselect",
|
||||||
title,
|
title,
|
||||||
allow_empty: allowEmpty,
|
allow_empty: allowEmpty,
|
||||||
allow_other: allowOther,
|
|
||||||
options: [...opts],
|
options: [...opts],
|
||||||
selected: [...selected],
|
selected: [...selected],
|
||||||
content,
|
content,
|
||||||
|
|||||||
Reference in New Issue
Block a user