Access tab: team dropdown for team-subject bindings #361

Closed
opened 2026-09-12 00:48:55 +00:00 by crueber · 3 comments
Owner

Survey: crueber/walhub#349 candidate 4.

Evidence

  • team:/ subjects are fully supported in access.json bindings (internal/identity/access.go:41-57 validSubject; Resolve expands teams) and DeleteTeam strips bindings (orgs.go:666).
  • But web/src/pages/Access.jsx:154-172 is a free-text subject input (placeholder 'user:jane@example.com', label 'subject (user:email or team:org/slug)'). Nothing fetches the org's teams; a user who does not know the exact team:org/slug spelling cannot discover it. SDK already exposes client.orgs.teams.list (web/sdk/src/orgs.js:73-77).

Design

  • On org-owned repos, fetch the owner org's team list and offer a subject picker (user-email input + team dropdown composing team:/), keeping free-text as fallback. Validate spelling client-side before add.

Acceptance criteria

  • Binding a team from the Access tab is discoverable without typing the exact subject string.
  • Free-text entry still works (non-org owners, user subjects); invalid spellings rejected with a friendly note.
  • node --test coverage for the picker logic per web test rules.
Survey: crueber/walhub#349 candidate 4. ## Evidence - team:<org>/<slug> subjects are fully supported in access.json bindings (internal/identity/access.go:41-57 validSubject; Resolve expands teams) and DeleteTeam strips bindings (orgs.go:666). - But web/src/pages/Access.jsx:154-172 is a free-text subject input (placeholder 'user:jane@example.com', label 'subject (user:email or team:org/slug)'). Nothing fetches the org's teams; a user who does not know the exact team:org/slug spelling cannot discover it. SDK already exposes client.orgs.teams.list (web/sdk/src/orgs.js:73-77). ## Design - On org-owned repos, fetch the owner org's team list and offer a subject picker (user-email input + team dropdown composing team:<org>/<slug>), keeping free-text as fallback. Validate spelling client-side before add. ## Acceptance criteria - [ ] Binding a team from the Access tab is discoverable without typing the exact subject string. - [ ] Free-text entry still works (non-org owners, user subjects); invalid spellings rejected with a friendly note. - [ ] node --test coverage for the picker logic per web test rules.
crueber added this to the v1 milestone 2026-09-12 00:48:55 +00:00
Author
Owner

PR #368 (branch fix/issue-361) implements the team-subject picker: team dropdown on org-owned repos composing team:org/slug, free-text fallback kept, client-side spelling validation with a friendly note, headless tests (web/test/unit/access-subject.test.js), vite build clean, no backend change, law-12 decision in docs/features/01_identity_permissions.md. Ready for review — not merging.

PR #368 (branch fix/issue-361) implements the team-subject picker: team dropdown on org-owned repos composing team:org/slug, free-text fallback kept, client-side spelling validation with a friendly note, headless tests (web/test/unit/access-subject.test.js), vite build clean, no backend change, law-12 decision in docs/features/01_identity_permissions.md. Ready for review — not merging.
Author
Owner

Review of PR #368 (fix/issue-361), verified in scratch worktree at f27c6eb (incl. one review fix, pushed). No browser used — node tests + code reasoning only, per review scope.

FINDINGS (file:line on the PR branch):

  1. Fetch scope — FOUND + FIXED (small). Access.jsx:42-45 fetched the roster for every non-empty owner, so user-owned repos wasted one GET /orgs//teams -> 404 before degrading. Fix pushed (f27c6eb): headless shouldFetchTeams() in web/src/lib/access.js (email/empty owners skip — an identity.ValidOrg slug can never contain '@', so no false negatives), wired into the useData fetcher, unit + wiring-guard tests added, doc decision updated (law 12). Non-org owners now make zero requests; legacy-namespace 404 / 403 / empty still degrade to text-only.
  2. Subject spelling — VERIFIED. ACCESS_ORG_RE/ACCESS_SLUG_RE sources byte-match identity orgRe/slugRe (internal/identity/identity.go:111-112); first-slash split matches strings.Cut in internal/identity/access.go:50; client lowercases prefix+org+slug, which is exactly the spelling the server accepts (server validSubject is lowercase-only and normalizeAccess does not re-case team subjects, so the client normalization is load-bearing and correct). Server stays authoritative (PUT revalidates via normalizeAccess; client never grants).
  3. Free-text fallback — VERIFIED. Input, label, and placeholder intact (Access.jsx:193-202); empty/invalid now surfaces a friendly note with examples instead of the old silent no-op (test pins absence of 'if (!sub) return;').
  4. No lock-in — VERIFIED. pickTeam composes into the subject field, which stays editable; typing clears the dropdown selection (Access.jsx:200,110-113); reselecting placeholder leaves field as source of truth.
  5. Server authoritative — VERIFIED (see 2). Client email check is deliberately looser than mail.ParseAddress, documented as typo-catching only.
  6. Team-list privacy — VERIFIED, no leak. Teams-list GET is auth-gated server-side (http.go:626-630, anon blocked without anonymousRead); the Access tab itself requires triage+; 403 degrades to []. Picker is read-only (no teams.create call site).
  7. #278 bounds — VERIFIED unaffected. Picker is a native (no absolute positioning, no new panel class, pinned by test); base grid is single-column with min-w-0, so the 390px viewport stacks. #278 concerns fixed-width absolute popovers (clone w-96, tray) — none introduced.
  8. Deps/doc/tests — VERIFIED. No new deps (runtime still solid-js + @solidjs/router + marked + dompurify, law 1); law-12 decision in docs/features/01_identity_permissions.md accurate (updated for the skip-fetch); web/test/unit/access-subject.test.js (13 tests) pins helpers + wiring.
  9. TESTS (scratch worktree): access-subject 13/13 pass; full node suite 716 tests — 713 pass, 3 fail, all 3 the pre-existing live-server smoke tests (SPA shell / hashed assets / repos.js — need a non-setup server; local :8080 answers 503 setup-only, same environmental cause disclosed in the PR). vite + esbuild builds clean (only the pre-existing chunk-size warning).

    MERGE RECOMMENDATION: ready to merge.

Review of PR #368 (fix/issue-361), verified in scratch worktree at f27c6eb (incl. one review fix, pushed). No browser used — node tests + code reasoning only, per review scope. FINDINGS (file:line on the PR branch): 1. Fetch scope — FOUND + FIXED (small). Access.jsx:42-45 fetched the roster for every non-empty owner, so user-owned repos wasted one GET /orgs/<email>/teams -> 404 before degrading. Fix pushed (f27c6eb): headless shouldFetchTeams() in web/src/lib/access.js (email/empty owners skip — an identity.ValidOrg slug can never contain '@', so no false negatives), wired into the useData fetcher, unit + wiring-guard tests added, doc decision updated (law 12). Non-org owners now make zero requests; legacy-namespace 404 / 403 / empty still degrade to text-only. 2. Subject spelling — VERIFIED. ACCESS_ORG_RE/ACCESS_SLUG_RE sources byte-match identity orgRe/slugRe (internal/identity/identity.go:111-112); first-slash split matches strings.Cut in internal/identity/access.go:50; client lowercases prefix+org+slug, which is exactly the spelling the server accepts (server validSubject is lowercase-only and normalizeAccess does not re-case team subjects, so the client normalization is load-bearing and correct). Server stays authoritative (PUT revalidates via normalizeAccess; client never grants). 3. Free-text fallback — VERIFIED. Input, label, and placeholder intact (Access.jsx:193-202); empty/invalid now surfaces a friendly note with examples instead of the old silent no-op (test pins absence of 'if (!sub) return;'). 4. No lock-in — VERIFIED. pickTeam composes into the subject field, which stays editable; typing clears the dropdown selection (Access.jsx:200,110-113); reselecting placeholder leaves field as source of truth. 5. Server authoritative — VERIFIED (see 2). Client email check is deliberately looser than mail.ParseAddress, documented as typo-catching only. 6. Team-list privacy — VERIFIED, no leak. Teams-list GET is auth-gated server-side (http.go:626-630, anon blocked without anonymousRead); the Access tab itself requires triage+; 403 degrades to []. Picker is read-only (no teams.create call site). 7. #278 bounds — VERIFIED unaffected. Picker is a native <select> (no absolute positioning, no new panel class, pinned by test); base grid is single-column with min-w-0, so the 390px viewport stacks. #278 concerns fixed-width absolute popovers (clone w-96, tray) — none introduced. 8. Deps/doc/tests — VERIFIED. No new deps (runtime still solid-js + @solidjs/router + marked + dompurify, law 1); law-12 decision in docs/features/01_identity_permissions.md accurate (updated for the skip-fetch); web/test/unit/access-subject.test.js (13 tests) pins helpers + wiring. TESTS (scratch worktree): access-subject 13/13 pass; full node suite 716 tests — 713 pass, 3 fail, all 3 the pre-existing live-server smoke tests (SPA shell / hashed assets / repos.js — need a non-setup server; local :8080 answers 503 setup-only, same environmental cause disclosed in the PR). vite + esbuild builds clean (only the pre-existing chunk-size warning). MERGE RECOMMENDATION: ready to merge.
Author
Owner

Fixed by PR #368 (review clean + one fetch-scope fix by reviewer: teams list only for org owners; spelling/privacy/#278 verified), merged. Closing.

Fixed by PR #368 (review clean + one fetch-scope fix by reviewer: teams list only for org owners; spelling/privacy/#278 verified), merged. Closing.
Sign in to join this conversation.
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
crueber/walhub#361
No description provided.