feat(canvas): surface current org name/slug/UUID in Settings panel #1621

Merged
core-devops merged 1 commits from feat/canvas-org-info-tab into main 2026-05-20 23:27:59 +00:00
Member

Summary

Closes a chronic UX gap where users (and AI agents) must use browser
devtools to call /cp/auth/me or /cp/orgs just 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.

Why this surface

  • SettingsPanel is already discoverable via the gear icon + Cmd+,
  • 3 existing tabs (Secrets / Workspace Tokens / Org API Keys); this
    becomes the 4th — no new top-level UI affordance
  • Read-only; creation/switching still lives at /orgs

Endpoint choice

  • /cp/orgs not /cp/auth/meSession from /cp/auth/me returns
    only {user_id, org_id, email}. To render slug + name we need
    the orgs list anyway. We do call fetchSession() in parallel to get
    the authoritative org_id for matching (host-slug match is the
    fallback when the session probe fails).
  • Promise.all keeps it to a single hydration roundtrip.

Files changed

File LoC
canvas/src/components/settings/OrgInfoTab.tsx (new) 181
canvas/src/components/settings/__tests__/OrgInfoTab.test.tsx (new) 207 (8 tests)
canvas/src/components/settings/SettingsPanel.tsx (modified) +8
canvas/src/components/settings/index.ts (modified) +1

Rendered UI (textual mock — current-org card)

┌─ Settings ──────────────────────────────────────  × ─┐
│  [Secrets] [Workspace Tokens] [Org API Keys] [Organization*]  │
│                                                       │
│  Current Organization                                 │
│  IDs you can paste into API calls, support tickets…   │
│                                                       │
│  ┌─ Agents Team ─────────────────────── running ─┐    │
│  │ Slug  [agents-team                  ] [Copy]  │    │
│  │ UUID  [2355b568-0799-4cc7-9e7f-806747f9958c]  │    │
│  │                                       [Copy]  │    │
│  └────────────────────────────────────────────────┘    │
│                                                       │
│  YOUR OTHER ORGANIZATIONS (1)                         │
│  ┌─ Skunkworks ──────────────────────── running ─┐    │
│  │ Slug  [skunkworks                   ] [Copy]  │    │
│  │ UUID  [11111111-…-111111111111      ] [Copy]  │    │
│  └────────────────────────────────────────────────┘    │
└───────────────────────────────────────────────────────┘

Tests (8, all passing locally — vitest run OrgInfoTab.test.tsx)

  • loading state has role=status + aria-live=polite
  • matches current org by session.org_id (name + slug + UUID 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 crashing

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. Two
alternatives that might be preferred:

  1. Show only the current org; hide the others (less info, simpler).
  2. Make the "other orgs" entries clickable links to their tenant
    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

  • CI green (vitest + tsc)
  • Open canvas on a tenant subdomain, hit Cmd+,, click
    Organization tab — current org card shows with correct UUID
  • Click Copy UUID — pastes the UUID
  • Sign in as a user belonging to >1 org — secondary list renders
  • Force a 5xx on /cp/orgs (devtools network override) — error
    banner renders instead of crashing the panel
## Summary Closes a chronic UX gap where users (and AI agents) must use browser devtools to call `/cp/auth/me` or `/cp/orgs` just 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. ## Why this surface - `SettingsPanel` is already discoverable via the gear icon + `Cmd+,` - 3 existing tabs (Secrets / Workspace Tokens / Org API Keys); this becomes the 4th — no new top-level UI affordance - Read-only; creation/switching still lives at `/orgs` ## Endpoint choice - `/cp/orgs` not `/cp/auth/me` — `Session` from `/cp/auth/me` returns only `{user_id, org_id, email}`. To render `slug` + `name` we need the orgs list anyway. We do call `fetchSession()` in parallel to get the authoritative `org_id` for matching (host-slug match is the fallback when the session probe fails). - Promise.all keeps it to a single hydration roundtrip. ## Files changed | File | LoC | |---|---| | `canvas/src/components/settings/OrgInfoTab.tsx` (new) | 181 | | `canvas/src/components/settings/__tests__/OrgInfoTab.test.tsx` (new) | 207 (8 tests) | | `canvas/src/components/settings/SettingsPanel.tsx` (modified) | +8 | | `canvas/src/components/settings/index.ts` (modified) | +1 | ## Rendered UI (textual mock — current-org card) ``` ┌─ Settings ────────────────────────────────────── × ─┐ │ [Secrets] [Workspace Tokens] [Org API Keys] [Organization*] │ │ │ │ Current Organization │ │ IDs you can paste into API calls, support tickets… │ │ │ │ ┌─ Agents Team ─────────────────────── running ─┐ │ │ │ Slug [agents-team ] [Copy] │ │ │ │ UUID [2355b568-0799-4cc7-9e7f-806747f9958c] │ │ │ │ [Copy] │ │ │ └────────────────────────────────────────────────┘ │ │ │ │ YOUR OTHER ORGANIZATIONS (1) │ │ ┌─ Skunkworks ──────────────────────── running ─┐ │ │ │ Slug [skunkworks ] [Copy] │ │ │ │ UUID [11111111-…-111111111111 ] [Copy] │ │ │ └────────────────────────────────────────────────┘ │ └───────────────────────────────────────────────────────┘ ``` ## Tests (8, all passing locally — `vitest run OrgInfoTab.test.tsx`) - loading state has `role=status` + `aria-live=polite` - matches current org by `session.org_id` (name + slug + UUID 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 crashing `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. Two alternatives that might be preferred: 1. Show only the current org; hide the others (less info, simpler). 2. Make the "other orgs" entries clickable links to their tenant 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 - [ ] CI green (vitest + tsc) - [ ] Open canvas on a tenant subdomain, hit `Cmd+,`, click Organization tab — current org card shows with correct UUID - [ ] Click Copy UUID — pastes the UUID - [ ] Sign in as a user belonging to >1 org — secondary list renders - [ ] Force a 5xx on `/cp/orgs` (devtools network override) — error banner renders instead of crashing the panel
core-fe added 1 commit 2026-05-20 21:53:26 +00:00
feat(canvas): surface current org name/slug/UUID in Settings panel
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 7s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 20s
E2E API Smoke Test / detect-changes (pull_request) Successful in 8s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Harness Replays / detect-changes (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 4s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 8s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 3s
gate-check-v3 / gate-check (pull_request) Successful in 3s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 4s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m5s
CI / Platform (Go) (pull_request) Successful in 5m13s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 4s
CI / Canvas (Next.js) (pull_request) Successful in 6m42s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
Harness Replays / Harness Replays (pull_request) Successful in 4s
CI / Python Lint & Test (pull_request) Successful in 7m7s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 9s
CI / all-required (pull_request) Successful in 6m32s
E2E Chat / E2E Chat (pull_request) Failing after 5m53s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 11m58s
qa-review / approved (pull_request) Refired via /qa-recheck by unknown
security-review / approved (pull_request) Refired via /security-recheck; security-review failed
6eadd86b39
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 crashing
core-qa approved these changes 2026-05-20 21:59:19 +00:00
core-qa left a comment
Member

core-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.writeText wrapped 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 has select-all so 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 via vi.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-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.writeText` wrapped 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 has `select-all` so 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 via `vi.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 approved these changes 2026-05-20 21:59:36 +00:00
core-devops left a comment
Member

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-info is consistent with sibling tabs.

Integration with PR#234 (#380): OrgInfoTab consumes the existing /cp/orgs list shape (Org[] with id/slug/name/status) and fetchSession() for session.org_id only. 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 a useEffect with 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 per feedback_surface_actionable_failure_reason_to_user). No console.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 OrgIdentityCard with 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[] } matches orgs/page.tsx precedent. Cancelled-effect guard prevents setState-after-unmount React warnings.

Verdict: APPROVED. Ships independently of PR#234, zero CP/runtime touch, no observability spam.

## 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-info` is consistent with sibling tabs. **Integration with PR#234 (#380):** OrgInfoTab consumes the existing `/cp/orgs` list shape (Org[] with id/slug/name/status) and `fetchSession()` for `session.org_id` only. 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 a `useEffect` with 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 per `feedback_surface_actionable_failure_reason_to_user`). No `console.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 `OrgIdentityCard` with 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[] }` matches `orgs/page.tsx` precedent. Cancelled-effect guard prevents setState-after-unmount React warnings. Verdict: APPROVED. Ships independently of PR#234, zero CP/runtime touch, no observability spam.
Member

/sop-tier-recheck

/sop-tier-recheck
Member

/qa-recheck

/qa-recheck
Member

/security-recheck

/security-recheck
core-be force-pushed feat/canvas-org-info-tab from 6eadd86b39 to 7d630646bf 2026-05-20 23:02:55 +00:00 Compare
core-devops merged commit 5455ddefe2 into main 2026-05-20 23:27:59 +00:00
Sign in to join this conversation.
No Reviewers
4 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1621