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

@ -317,7 +317,12 @@ export function requiredGroups(uriTemplate: string): string[][] {
// time on a condition nothing can satisfy, and the "any one of" message
// built from it would name no fields.
if (part.names.length === 0) continue;
groups.push(part.names);
// Deduplicated within the expression: `{a,a}` is one requirement named
// twice, not two names either of which would do. Left as-is 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 the useless hint "Any one of: a, a".
groups.push([...new Set(part.names)]);
}
return groups;
}