Skip to content

feat(workspaces): add Mode B workspace switcher - #36

Merged
man4ish merged 1 commit into
mainfrom
feature/mode-b-phase2-workspace-switcher
Aug 12, 2026
Merged

feat(workspaces): add Mode B workspace switcher#36
man4ish merged 1 commit into
mainfrom
feature/mode-b-phase2-workspace-switcher

Conversation

@man4ish

@man4ish man4ish commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Mode B Phase 2 — Workspace Switcher

Adds the top-bar workspace switcher: a user can move between their personal workspace and any team they belong to, entirely through the existing auth-service authority.

What this does

  • Workspace switching through the existing auth-service authority. The new POST /auth/switch-team route in routes_auth_proxy.py is a pure relay (_proxy_to_auth), identical in shape to every other route in this file. Control-center never inspects or trusts team_id itself.
  • Server-side team identity resolution. team_id is resolved on the auth-service side from the presented token's own sub claim, with org-membership and team-membership independently re-checked on every switch. Control-center only relays the request/response.
  • Personal workspace support via team_id: null. Treated as a real, meaningful state end-to-end (not a sentinel), including in the new switchTeam(teamId: number | null) client function.
  • Token persistence → session validation → reload, in that order. switchTeam() persists the reissued access token, then calls validateSession() on it, and only resolves successfully once that validation succeeds. TeamSwitcher.tsx calls window.location.reload() only after switchTeam() resolves.
  • No client-supplied X-Team-Id / X-Workspace-Id mechanism. The only place client intent is expressed is the team_id field in the /auth/switch-team request body; there is a dedicated regression test asserting neither header is ever sent.
  • Team/org membership isolation remains server-authoritative. TeamSwitcher.tsx reads orgId/userId/teamId only from the authenticated SessionUser and uses listTeams() purely for display filtering — the server's own membership re-check on /auth/switch-team is the only authority on whether a switch is allowed.
  • Integration with the existing Teams UI/session model. SessionUser gains teamId/teamRole (nullable, mirrors the existing orgId/orgRoles shape). TeamSwitcher is wired into TopAppBar next to the existing (placeholder) OrgSelector.

Rebase note

This branch had zero commits unique from its fork point, so bringing it onto current main (PR #35, Team Management v0.8.0 Step 5) was a fast-forward, not a replay — the implementation diff is byte-for-byte identical before and after. The only adjustment needed was mechanical fixture updates required by interfaces the two branches both extended independently:

No functional change to PR #35's Step 5 implementation.

Verification

  • Backend: 957 passed, 0 failed
  • Coverage: 99.80% (≥98% gate)
  • Frontend/Vitest: 37 files, 464 passed, 0 failed
  • TypeScript: 0 errors
  • Security/isolation checks: passed

Backend tests were run with PYTHONPATH pointed at this worktree's own backend/src to avoid a known editable-install shadowing issue where control_center can resolve to a different checkout's src/ instead of the one under test.

🤖 Generated with Claude Code

Mode B Phase 2: adds a top-bar TeamSwitcher that lets a user move
between their personal workspace (team_id: null) and any team they
belong to, backed by the existing auth-service POST /auth/switch-team.

- backend: routes_auth_proxy.py gains a pure relay for
  /auth/switch-team (same _proxy_to_auth pattern as every other route
  in this file; this service never inspects or trusts team_id).
- frontend: auth.ts adds SessionUser.teamId/teamRole and switchTeam(),
  which persists the reissued access token and re-validates the
  session before the caller may reload -- never a locally-computed
  team_id, no X-Team-Id/X-Workspace-Id header.
- frontend: new TeamSwitcher.tsx/test.tsx, wired into TopAppBar.

Rebased onto main (PR #35, Team Management v0.8.0 Step 5); two test
fixtures (TeamSwitcher.test.tsx's Team mocks, TeamMembersPanel.test.tsx's
SessionUser mock) updated to match the interfaces both branches now
share -- no functional changes to Step 5's own implementation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@man4ish
man4ish merged commit e069a84 into main Aug 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant