fix: dedupe a repeated name in a group, and name every unmet requirement

Addresses Copilot's round-13 review on #2035. Two suppressed comments, both
real, both in the required-group work from round 7 rather than in the recent
preview rework.

1. `{a,a}` is ONE requirement named twice, but `requiredGroups` stored it as
   a two-name group, so everything downstream read it as "either of these
   will do". The TUI was left with a form it could not submit: its
   `length === 1` test marked `a` optional while ResourceTestModal's guard
   still refused a blank. The web panel offered the useless hint
   "Any one of: a, a". Names are now deduplicated within the expression --
   expansion still emits both occurrences (`x://{a,a}` -> `x://1,1`), which
   RFC 6570 requires.

2. The per-field hint used `find`, so a variable in several shared groups
   advertised only the first. With `{a,b}{b,c}{a,c}`, filling `b` satisfies
   the first two and every visible hint then looks met while Read Resource
   stays disabled on the unmet `{a,c}` -- a requirement with nothing on
   screen pointing at it.

   Fields now hint every shared group they sit in. That alone is not enough,
   because no per-field hint can say WHICH group is still outstanding, so
   the form states it directly: "Still needed: a or c", built from the same
   `unmetRequiredGroups` the submit gate uses, so the message and the button
   cannot disagree. `hasRequiredValues` is no longer called here -- the
   panel derives both from the one list.

npm run ci passes.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-17 00:33:52 -04:00
commit 1c595e96ee

View file

@ -352,6 +352,24 @@ describe("requiredGroups / hasRequiredValues", () => {
});
});
describe("a name repeated inside one expression", () => {
// `{a,a}` is one requirement named twice, not two names either of which
// would do. Undeduplicated it read as a shared group everywhere downstream:
// the TUI's `length === 1` test marked `a` optional while its submit guard
// still refused a blank, and the web panel offered "Any one of: a, a".
it("collapses to a single-name required group", () => {
expect(requiredGroups("x://{a,a}")).toEqual([["a"]]);
});
it("still expands both occurrences, which RFC 6570 requires", () => {
expect(expandUriTemplate("x://{a,a}", { a: "1" })).toBe("x://1,1");
});
it("leaves genuinely distinct names alone", () => {
expect(requiredGroups("x://{a,b}")).toEqual([["a", "b"]]);
});
});
describe("expression independence", () => {
it("does not rewrite a later query expression on the own-expansion path", () => {
expect(