feat(canvas): surface current org name/slug/UUID in Settings panel #1621
Reference in New Issue
Block a user
Delete Branch "feat/canvas-org-info-tab"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes a chronic UX gap where users (and AI agents) must use browser
devtools to call
/cp/auth/meor/cp/orgsjust to read theirorg_idUUID. Adds an Organization tab to the existingSettingsPaneldrawer with copy-buttoned rows for the current org'sname, slug, and UUID, plus a list of other orgs the user belongs to.
Why this surface
SettingsPanelis already discoverable via the gear icon +Cmd+,becomes the 4th — no new top-level UI affordance
/orgsEndpoint choice
/cp/orgsnot/cp/auth/me—Sessionfrom/cp/auth/mereturnsonly
{user_id, org_id, email}. To renderslug+namewe needthe orgs list anyway. We do call
fetchSession()in parallel to getthe authoritative
org_idfor matching (host-slug match is thefallback when the session probe fails).
Files changed
canvas/src/components/settings/OrgInfoTab.tsx(new)canvas/src/components/settings/__tests__/OrgInfoTab.test.tsx(new)canvas/src/components/settings/SettingsPanel.tsx(modified)canvas/src/components/settings/index.ts(modified)Rendered UI (textual mock — current-org card)
Tests (8, all passing locally —
vitest run OrgInfoTab.test.tsx)role=status+aria-live=politesession.org_id(name + slug + UUID rendered)navigator.clipboardfetchSessionrejects/cp/orgs5xx (does not crash the panel)SettingsPanel.test.tsx(14 tests) also still passes — unchanged.Open question for CTO
Multi-org users: currently the tab shows the current org (matched
by
session.org_id) prominently and lists the others below. Twoalternatives that might be preferred:
subdomain (active org-switcher; closer to a nav surface).
The PR ships option 0 (read-only, show all). Happy to switch to (1)
or (2) on review.
Test plan
Cmd+,, clickOrganization tab — current org card shows with correct UUID
/cp/orgs(devtools network override) — errorbanner renders instead of crashing the panel
Closes a chronic UX gap where users (and AI agents) had to call /cp/auth/me or /cp/orgs from browser devtools to read their org_id UUID. Adds an "Organization" tab to the existing SettingsPanel drawer with copy-buttoned rows for the current org's name, slug, and UUID, plus a list of other orgs the user belongs to. Data path: - fetchSession() → /cp/auth/me supplies the authoritative org_id - api.get('/cp/orgs') supplies the slug + name (Session lacks both) - Falls back to host-slug match (via getTenantSlug) if the session probe fails but /cp/orgs succeeds — keeps the panel usable on a transient CP 401. Surface choice: same right-anchored drawer as the existing 3 tabs (Secrets / Workspace Tokens / Org API Keys). One additional tab keeps the Settings affordance discoverable and avoids a new top-level UI surface. Read-only — creation/switching lives at /orgs. Files: + canvas/src/components/settings/OrgInfoTab.tsx (181 LoC) + canvas/src/components/settings/__tests__/OrgInfoTab.test.tsx (8 tests) ~ canvas/src/components/settings/SettingsPanel.tsx (+8 LoC, wires tab) ~ canvas/src/components/settings/index.ts (+1 LoC, barrel export) Tests (8 cases, all passing locally): - loading state has role=status + aria-live=polite - matches current org by session.org_id (name + slug + UUID all rendered) - Copy UUID button writes the UUID to navigator.clipboard - Copy Slug button writes the slug - host-slug fallback when fetchSession rejects - other-orgs list rendered when user has >1 org - error banner on /cp/orgs 5xx (does not crash the panel) - no-match recovery hint renders without crashingcore-qa five-axis review (PR#1621 @
6eadd86b)Correctness: 8 tests covering loading / session-org match / copy-UUID / copy-slug / host-slug fallback / multi-org list / 5xx banner / no-match recovery — comprehensive. No obvious gap; status display + recovery hint both pinned. Minor: no test asserts the cancelled-effect guard (unmount before resolve), but the cancelled flag is correctly implemented in the source effect.
Edge cases: When URL subdomain matches no org in /cp/orgs, currentOrg falls through to null and renders the recovery hint with /orgs link — graceful, no crash. The wrapped {orgs: []} response shape is defensively unwrapped via
Array.isArray(body) ? body : body.orgs ?? [](mirrors orgs/page.tsx). Empty array + null session both pinned by the no-match test.A11y: Copy buttons carry explicit
aria-label={Copy ${label}}(UUID/Slug). UUID code element uses font-mono with select-all break-all. Loading state has role="status" + aria-live="polite". Focus-visible ring wired. Good baseline.Clipboard API:
navigator.clipboard.writeTextwrapped in try/catch with a comment acknowledging jsdom + old Safari sync-throw — defensive. Canvas is always HTTPS in prod, and the catch silently no-ops; the code element hasselect-allso users retain a triple-click fallback. No-op fallback is acceptable.Test isolation:
vi.useRealTimers()per-test, mock resets in beforeEach,cleanup()in afterEach, navigator.clipboard stubbed viavi.stubGlobal. No state leakage between tests.Verdict: APPROVED. Surgical (181+207 LoC), well-tested, A11y-correct, defensive on response-shape and clipboard failure modes.
core-devops five-axis review (PR#1621 @
6eadd86b)Tiny-PR / scope: 181 (component) + 207 (test) + 8 (panel) + 1 (barrel) LoC — surgical. No drive-by refactor. Closes a real UX gap (task #379) without touching SettingsPanel beyond a 4th tab trigger/content pair. Tab key
org-infois consistent with sibling tabs.Integration with PR#234 (#380): OrgInfoTab consumes the existing
/cp/orgslist shape (Org[] with id/slug/name/status) andfetchSession()forsession.org_idonly. It does NOT depend on the auth-me org_slug/org_name fields that PR#234 adds — so this PR is forward-compatible and ships independently. When #234 lands, OrgInfoTab keeps working via the same org_id match. Good decoupling.Network calls: Two fetches in
Promise.all(fetchSession + /cp/orgs) inside auseEffectwith empty deps array — fires ONCE on tab-mount, not on every tab-switch. Rt-mount only happens when the user re-opens the panel/tab. No rate-limit concern.fetchSession().catch(() => null)swallows the session failure independently so /cp/orgs can still drive the host-slug fallback path.Observability:
setError(e.message)on /cp/orgs throw — surfaces the API error to the user (good perfeedback_surface_actionable_failure_reason_to_user). Noconsole.error/log spam on 5xx. The "best-effort" clipboard catch is silent (no console) — acceptable, jsdom-only path.Multi-org read-only: Other-orgs list is plain
OrgIdentityCardwith no onClick — deliberate per brief. Switching lives at/orgs. The recovery hint already links there. No missed feature; intent is documented in the component header.Defensive shape-handling:
OrgsResponse = Org[] | { orgs?: Org[] }matchesorgs/page.tsxprecedent. Cancelled-effect guard prevents setState-after-unmount React warnings.Verdict: APPROVED. Ships independently of PR#234, zero CP/runtime touch, no observability spam.
/sop-tier-recheck
/qa-recheck
/security-recheck
6eadd86b39to7d630646bf