#552: live name-collision preview + variant-D copy fixes (v0.6.208) #561

Merged
Cordy merged 15 commits from fix-552c into main 2026-09-16 02:33:54 +00:00
Owner

The two items left on #552.

1 — Live collision preview (mockup variant E, panel 3)

New group, Rename group and Add account all take a name that must not already be in use. Until now each let you fill in the whole dialog before the server refused with a 409. Now the clash shows while typing: danger border, the reason beneath the field, primary button disabled.

No new endpoint and no debounced request. All three dialogs open from tabs that have already loaded every name they need — Accounts holds the local list and the directory seen-list, Groups holds app-owned and directory groups. ncLists is filled by the two loaders; the check is a synchronous lookup. /candidates was left alone: it feeds the member picker with account names, which is a different list from group names.

Four distinct messages, because the reason differs: a directory-group clash is about share ambiguity, a directory-account clash is the v0.6.204 security case (shared identity), the two same-kind duplicates are plain duplicates.

Deliberately one-sided — there is no green "available" tick. The lists are paginated and, with no IdP admin client, only the names seen in sign-in tokens so far. The check can honestly say taken; it can never say free. A tick would be an assurance Cairn cannot make. Silence means nothing is known against the name; the server 409 remains the authority.

A bug TDD caught before it shipped: Rename opens pre-filled with the group's own current name, which is of course already in the app-group list — so the naive version opened accusing the group of colliding with itself, Rename disabled, breaking the exact flow it was meant to help. nameCollision takes a self exemption, the same line the server draws (groups.go only calls dirNameTaken when the name actually changed). Three tests cover it.

2 — Variant D review

Empty states were already right (luEmpty, accDirEmpty, guEmpty). Two real divergences found and fixed:

  • Filtering the Groups tab rendered "No accounts match the filter" over a table of groups — accNoMatch was being reused. New guNoMatch key.
  • The notice shown when Cairn cannot list directory groups explained what it could not see, but never said what to configure to fix it — which is the whole point of variant D ("say what is known, what is not, and what to configure to get the rest"). It now names the IdP admin client and mentions that member counts are unknown.

One divergence I did not implement, for your call: variant D also puts a per-row "seen in sign-in tokens" note in the actions column of every directory group. The notebox below the table already states that once, for the whole table — repeating a global condition on every row reads as noise rather than information. Say the word if you want it anyway.

Also skipped: the mockup's notebox warn styling. Purely cosmetic, and the plain notebox already reads as a notice.

Verification

  • Red witnessed on the runner before any implementation: index.html has no // --- pure:name-collision --- block, with the workflow refusing to proceed if the test had passed or failed for the wrong reason.
  • 15 tests in web/test/name-collision.test.js, all passing; full web suite green; node --check clean.
  • Nine splices, every one count-asserted — two runs failed loudly on a wrong anchor and wrote nothing, which is the guard working.
  • Desktop invariant: the one new CSS rule is outside any @media block, asserted.

Visual pass is yours — sessions clear on redeploy so I cannot sign in.

The two items left on #552. ## 1 — Live collision preview (mockup variant E, panel 3) New group, Rename group and Add account all take a name that must not already be in use. Until now each let you fill in the whole dialog before the server refused with a 409. Now the clash shows while typing: danger border, the reason beneath the field, primary button disabled. **No new endpoint and no debounced request.** All three dialogs open from tabs that have *already* loaded every name they need — Accounts holds the local list and the directory seen-list, Groups holds app-owned and directory groups. `ncLists` is filled by the two loaders; the check is a synchronous lookup. `/candidates` was left alone: it feeds the member picker with *account* names, which is a different list from group names. Four distinct messages, because the reason differs: a directory-group clash is about share ambiguity, a directory-account clash is the v0.6.204 security case (shared identity), the two same-kind duplicates are plain duplicates. **Deliberately one-sided — there is no green "available" tick.** The lists are paginated and, with no IdP admin client, only the names seen in sign-in tokens so far. The check can honestly say *taken*; it can never say *free*. A tick would be an assurance Cairn cannot make. Silence means nothing is known against the name; the server 409 remains the authority. **A bug TDD caught before it shipped:** Rename opens pre-filled with the group's own current name, which is of course already in the app-group list — so the naive version opened accusing the group of colliding with itself, Rename disabled, breaking the exact flow it was meant to help. `nameCollision` takes a `self` exemption, the same line the server draws (`groups.go` only calls `dirNameTaken` when the name actually changed). Three tests cover it. ## 2 — Variant D review Empty states were already right (`luEmpty`, `accDirEmpty`, `guEmpty`). Two real divergences found and fixed: - Filtering the **Groups** tab rendered "No accounts match the filter" over a table of groups — `accNoMatch` was being reused. New `guNoMatch` key. - The notice shown when Cairn cannot list directory groups explained what it could not see, but never said **what to configure to fix it** — which is the whole point of variant D ("say what is known, what is not, and what to configure to get the rest"). It now names the IdP admin client and mentions that member counts are unknown. **One divergence I did not implement**, for your call: variant D also puts a per-row "seen in sign-in tokens" note in the actions column of every directory group. The notebox below the table already states that once, for the whole table — repeating a global condition on every row reads as noise rather than information. Say the word if you want it anyway. Also skipped: the mockup's `notebox warn` styling. Purely cosmetic, and the plain notebox already reads as a notice. ## Verification - **Red witnessed** on the runner before any implementation: `index.html has no // --- pure:name-collision --- block`, with the workflow refusing to proceed if the test had passed or failed for the wrong reason. - 15 tests in `web/test/name-collision.test.js`, all passing; full web suite green; `node --check` clean. - Nine splices, every one count-asserted — two runs failed loudly on a wrong anchor and wrote nothing, which is the guard working. - Desktop invariant: the one new CSS rule is outside any `@media` block, asserted. Visual pass is yours — sessions clear on redeploy so I cannot sign in.
Cordy scheduled this pull request to auto merge when all checks succeed 2026-09-16 02:27:12 +00:00
ci: literal i18n keys so the webcheck guard can verify them
Some checks failed
ci / test-and-build (pull_request) Failing after 50s
a8534bfb4c
Cordy merged commit 5e0af3d784 into main 2026-09-16 02:33:54 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#561
No description provided.