Skip to content

feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement#978

Merged
raivieiraadriano92 merged 18 commits into
raivieiraadriano92/thu-555-settings-workspace-members-add-remove-pendingfrom
raivieiraadriano92/thu-556-settings-workspace-permissions-manage_members-change_roles
Jun 16, 2026
Merged

feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement#978
raivieiraadriano92 merged 18 commits into
raivieiraadriano92/thu-555-settings-workspace-members-add-remove-pendingfrom
raivieiraadriano92/thu-556-settings-workspace-permissions-manage_members-change_roles

Conversation

@raivieiraadriano92

@raivieiraadriano92 raivieiraadriano92 commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ships PR 8 of the Workspaces v1 plan — the Permissions page under Settings → Workspace, plus end-to-end FE/BE enforcement of those permissions for add/remove × agents / skills / models / mcp_servers (8 keys). Soft-delete (PATCH deleted_at) is correctly classified as a remove. Stacked on top of #974 (THU-555, Members page); base will move to main once #974 merges.

Spec called for an 11-row grid; mid-task the surface was narrowed to the 4 resource CRUD pairs (the spec's other keys stay in the enum, stored-only — see Out of scope below).

What this PR does

  • Shared (shared/workspaces.ts) — single source of truth for workspacePermissionKeys, WorkspacePermissionKey/Role types, isWorkspacePermissionKey narrowing, and permissionAllows(userRole, requiredRole) so FE + BE agree on satisfiability.
  • Frontend
    • DAL: setWorkspacePermissionRequiredRole (UPSERT — works without the seeded row), useWorkspacePermissionsQuery reactive hook.
    • Permissions page at /w/<id>/settings/workspace/permissions — card+table with 8 rows (Add/Remove × Agents/Skills/Models/MCPs); role selector ("Admin" / "Everyone"). Loads, mutates, persists via PowerSync — no separate save button.
    • RequireWorkspaceAdmin route guard (sibling to RequireWorkspacePermission): admin-only, personal-workspace + E2EE gated. Branch order: personal → e2ee → loading → !admin → render.
    • Sidebar: new "Permissions" entry (Lock icon) between Members and Models, gated on admin + non-personal + non-E2EE.
    • Affordance gating across 4 resource surfaces — every Add/Edit/Remove/Toggle that PATCHes the row now consults useWorkspacePermission:
      • Models page: header +, empty-state Add, row Switch, row Edit (Pen), row Delete (Trash), ModificationIndicator reset.
      • MCP Servers page: header +, empty-state Add, row Switch, row Trash (Popover).
      • Agents page: header Add Custom Agent; AgentRow Switch + Trash threaded via canEditAgents / canRemoveAgents props.
      • Skills: SkillsList Create button; LibraryRow Switch + Edit/Delete menu items; SkillDetail Switch + Edit/Delete menu items; empty-panel "Create your first skill".
    • Chat-composer bypass gating — the chat input has its own write paths into skills / agents / models pickers; gated symmetrically:
      • ChatSkillsBar "Pin a skill" trigger hidden when !add_skills; pinned chips still render but SuggestionChip hides Reorder + Unpin.
      • ChatModelPicker "Add Models" footer suppressed when !add_models.
      • Header agent selector "Add Agent" footer also gated on add_agents (in addition to allowCustomAgents).
  • Backend
    • DAL helpers: getUserRoleInWorkspace(tx, workspaceId, userId), getRequiredRoleForPermission(tx, workspaceId, key).
    • createWorkspaceScopedHandler extended with optional addPermissionKey / removePermissionKey / softDeleteColumn. New opIntent helper classifies PUT/PATCH(no deleted_at) as "add" and DELETE/PATCH(deleted_at set) as "remove" — gates each on the corresponding key, rejects with permanent / INSUFFICIENT_PERMISSION otherwise.
    • Registry entries for agents, skills, models, mcp_servers now pass all three options (deleted_at is the soft-delete column for all four).

Notable decisions

  • Permissions page itself is hardcoded admin-only (RequireWorkspaceAdmin) rather than gated on the change_permissions key — making it configurable creates a self-escalation foot-gun (a member granted change_permissions=member could promote themselves further). The row stays in the enum for forward-compat.
  • 'member' = "Everyone" in the UI — the underlying enum stays 'admin' | 'member', but the role picker shows "Admin" / "Everyone" since every workspace member satisfies a member requirement (admins satisfy it too).
  • Soft-delete classified as a remove at the upload-handler layer, not via per-resource hacks. The four resource tables all soft-delete via PATCH deleted_at, so softDeleteColumn: 'deleted_at' on each registry entry routes that PATCH to removePermissionKey instead of addPermissionKey. Regular edits + restores stay on the add key.
  • Edit gates on add_* (not a separate edit_* key) — toggling enabled, renaming a model, editing a skill all PATCH the row and gate on the resource's add key. Avoids exploding the permission surface.
  • Shared permissionAllows predicate in shared/workspaces.ts — same satisfiability check on FE (useWorkspacePermission) and BE (createWorkspaceScopedHandler). Adding 'admin' always satisfies; 'member' is satisfied by both roles.
  • useWorkspacePermission as a DI seam on each page-level component (ChatSkillsBar, ChatModelPicker, Header, AgentsSettingsPage, McpServersPage, ModelsPage) — matches the existing isStandalone?: () => boolean pattern, lets component-level tests assert "denied → affordance hidden" without seeding the DB.
  • Pin/reorder/unpin from the chat composer route through add_skills since they PATCH the shared skills row (pinned_order). Future work could move pinning to a per-user store; for now the BE enforcement covers it.

Out of scope (stored-only keys)

The enum has 12 keys; only 8 are enforced today. The rest:

  • manage_members — legacy; still backs the Members sidebar gate + route guard for the existing add/remove members flows. Effectively superseded by invite_users / remove_users once those are wired.
  • join_workspace — no user-initiated "join" operation exists yet; reserved for a future invite-link / accept-invite flow.
  • invite_users, change_roles, remove_users — Members page already gates on manage_members + change_roles directly (membership upload handlers hardcode isWorkspaceAdmin). These keys are stored but not consulted.
  • change_general_settings — workspaces upload handler hardcodes isWorkspaceAdmin for renames/slug/icon. Stored but not consulted.
  • change_permissions — Permissions page itself is hardcoded admin-only via RequireWorkspaceAdmin (see Notable decisions).
  • delete_workspace — BE rejects all workspace DELETEs (WORKSPACE_DELETE_DISABLED). Stored but inert.

Wiring enforcement for any of these is a one-line registry change (workspace-scoped tables) or a getRequiredRoleForPermission + permissionAllows call (bespoke handlers). See the project memory note in this repo for the migration pattern.

Caveats

  • Permissions for personal workspace are hidden entirely — Decision 25. Personal workspaces have no permission rows, no Permissions sidebar entry, and direct URL nav redirects.
  • E2EE-enabled servers redirect away from the Permissions page (same as Members) — configuring member-management permissions is meaningless when nothing can be shared.
  • Defensive UPSERT in setWorkspacePermissionRequiredRole — works on workspaces that pre-date THU-552's seed-defaults hook. If a row is somehow missing, the first write creates it.

Test plan

  • Permissions page: admin in a shared workspace sees 8 rows defaulted to Admin; flipping add_agents/add_skills/add_models/add_mcp_servers to Everyone immediately reflects on the corresponding resource page; member can hit the URL → redirected.
  • Sidebar: Permissions entry visible to admin only on shared workspaces; hidden on personal and on E2EE-enabled servers.
  • Agents page: as member without add_agents, "Add Custom Agent" hidden, row Switch disabled, Trash hidden. Setting add_agents=member makes them appear; row PATCH lands on BE. Header agent-selector footer also follows.
  • Skills page: same matrix — Create hidden, row Switch disabled, Edit/Delete menu items hidden, empty-panel "Create your first skill" hidden.
  • Models page: header +, empty-state Add, row Switch, Edit (Pen), Delete (Trash), ModificationIndicator reset all gated.
  • MCP servers page: same matrix — header +, empty-state Add, row Switch + Trash gated.
  • Chat composer bypasses: + Pin a skill hidden when !add_skills; pinned chips still render but Reorder/Unpin are absent from the chip dropdown. Add Models footer hidden in the model picker. Add Agent footer hidden in the header's agent selector.
  • BE soft-delete for each resource: PATCH {deleted_at: <iso>} from a member without remove permission is rejected with INSUFFICIENT_PERMISSION; granting the remove key allows it.
  • BE edit PATCH (no deleted_at) for each resource: rejected on !add_* even if the remove key is granted.
  • bun check (FE) + bun test (FE + BE) green.

Spec: https://github.com/thunderbird/thunderbolt-spec/pull/12
Addendum: Decision 10, Decision 11, Decision 25.

What's in the PR

17 commits, ordered for review:

Commit Subject
1338e4a5 setWorkspacePermissionRequiredRole DAL
09c600fd RequireWorkspaceAdmin route guard
6bd9b4dc Consolidate workspace permission keys into shared/workspaces.ts
f23b18d4 Permissions page (11 keys, labels only)
c845030a Permissions sidebar entry
f54e70b7 Mount Permissions route
796ebf66 Refine Permissions page copy and layout
0311c4fb Scope Permissions to agents/skills/models/mcps
eccd544c Use Lock icon for Permissions sidebar entry
aa9fde05 permissionAllows + BE permission lookup helpers
9f1f6afa Permission-key gating in workspace-scoped handler
29f620b2 Enforce add_agents / remove_agents permissions
a9d368c2 Treat soft-delete PATCH as remove for permission gating
ab2c8a2a Enforce add_skills / remove_skills permissions
bfb504cb Enforce add_models / remove_models permissions
26119f09 Enforce add_mcp_servers / remove_mcp_servers permissions
967f8e96 Gate edit + chat bypasses, add permission gating tests

Note

High Risk
Changes authorization on membership/pending-invite uploads and resource writes, and broadens pending-invite sync visibility to all members—mistakes could allow privilege escalation or block legitimate admins.

Overview
Workspace permissions are centralized in shared/workspaces.ts (keys, roles, permissionAllows) and wired through FE useWorkspacePermission / BE callerSatisfiesPermission, defaulting missing policy rows to admin-only.

Backend upload auth moves membership and pending-invite writes from “workspace admin” to invite_users / change_roles / remove_users, with guards against admin escalation via invites or membership PUT upserts, last-admin protection on PUT, safer promote-on-insert (no role downgrade, delete pending by workspace+email), email validation, and server-truth invited_by_user_id. Agents, skills, models, and MCP servers use optional add_* / remove_* keys; PATCH setting deleted_at counts as remove.

Sync: workspace_pending_memberships syncs to all members (not an admin-only bucket) so permitted non-admins can see invites; writes stay handler-enforced.

Frontend adds the Permissions page (admin-only route), setWorkspacePermissionRequiredRole, and hides add/edit/remove affordances on settings pages and chat composer paths when the user lacks the matching key. Smaller fixes include Members route without manage_members guard, AppErrorBoundary, mode-picker stale validation, logout double-click guard, and clearAllKeys(serverId) after registry clear.

Reviewed by Cursor Bugbot for commit 24cc392. Bugbot is set up for automated code reviews on this repo. Configure here.

@raivieiraadriano92 raivieiraadriano92 self-assigned this Jun 12, 2026
@github-actions

Copy link
Copy Markdown

Semgrep Security Scan

No security issues found.

@raivieiraadriano92 raivieiraadriano92 changed the title Raivieiraadriano92/thu 556 settings workspace permissions manage members change roles feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement Jun 12, 2026
@raivieiraadriano92 raivieiraadriano92 marked this pull request as ready for review June 12, 2026 10:50
@github-actions

Copy link
Copy Markdown

PR Metrics

Metric Value
Lines changed (prod code) +898 / -202
JS bundle size (gzipped) 🟡 733.1 KB → 746.9 KB (+13.7 KB, +1.9%)
Test coverage 🟡 77.63% → 76.85% (-0.8%)
Performance (preview) Preview not ready — Render deploy may have timed out
Accessibility
Best Practices
SEO

Updated Fri, 12 Jun 2026 10:50:45 GMT · run #1874

Comment thread src/components/chat/chat-skills-bar.tsx
* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
@raivieiraadriano92 raivieiraadriano92 merged commit 752cd0f into raivieiraadriano92/thu-555-settings-workspace-members-add-remove-pending Jun 16, 2026
5 of 9 checks passed
@raivieiraadriano92 raivieiraadriano92 deleted the raivieiraadriano92/thu-556-settings-workspace-permissions-manage_members-change_roles branch June 16, 2026 20:09
raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
…pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
…plicate) (#971)

* fix(THU-554): align PageHeader actions with content padding

* feat(THU-554): allow personal workspace rename via upload handler

* feat(THU-554): updateWorkspaceName DAL, default name, createdAt stamps

* feat(THU-554): useActiveWorkspaceMembership hook

* feat(THU-554): split settings sidebar into Account Settings and Workspace groups

* feat(THU-554): Workspace Settings page with autosave rename

* feat(THU-554): add slug + icon columns to workspaces

* feat(THU-554): workspace slug input with auto-derive from name

* feat(THU-554): workspace icon picker (emoji or image upload)

* feat(THU-554): render workspace icon in the sidebar selector

* feat(THU-554): share WorkspaceFormFields between settings and create modal

* feat(THU-554): duplicate workspace action

* feat(THU-555): Settings → Workspace → Members (add / remove / role / pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
* feat(THU-552): promote pending membership to active on email match

* feat(THU-552): createSharedWorkspace DAL function

* feat(THU-552): useCanCreateWorkspace hook

* feat(THU-552): EmailChipInput component

* feat(THU-552): CreateWorkspaceModal component

* feat(THU-552): Create workspace button in sidebar selector footer

* feat(THU-552): seed defaults into newly-created shared workspace

* fix(THU-552): scope cleanupRemovedDefaults to a workspace

* feat(THU-552): split create + invite into two modals, single Create CTA

* feat(THU-552): workspace selector design polish

* fix(THU-552): rename SCREAMING_SNAKE constants to camelCase

* feat(THU-554): Settings → Workspace → General (rename, slug, icon, duplicate) (#971)

* fix(THU-554): align PageHeader actions with content padding

* feat(THU-554): allow personal workspace rename via upload handler

* feat(THU-554): updateWorkspaceName DAL, default name, createdAt stamps

* feat(THU-554): useActiveWorkspaceMembership hook

* feat(THU-554): split settings sidebar into Account Settings and Workspace groups

* feat(THU-554): Workspace Settings page with autosave rename

* feat(THU-554): add slug + icon columns to workspaces

* feat(THU-554): workspace slug input with auto-derive from name

* feat(THU-554): workspace icon picker (emoji or image upload)

* feat(THU-554): render workspace icon in the sidebar selector

* feat(THU-554): share WorkspaceFormFields between settings and create modal

* feat(THU-554): duplicate workspace action

* feat(THU-555): Settings → Workspace → Members (add / remove / role / pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
…pace (#961)

* feat(THU-551): reactive active-workspace + URL helpers

* feat(THU-551): useWorkspacesQuery hook + DAL test

* feat(THU-551): WorkspaceMembershipGate + live membership query

* feat(THU-551): mount workspace routes under /w/:workspaceId prefix

* feat(THU-551): workspace selector in sidebar header

* feat(THU-551): thread workspace prefix through navigation call sites

* feat(THU-552): create workspace modal — name + invite by email (#965)

* feat(THU-552): promote pending membership to active on email match

* feat(THU-552): createSharedWorkspace DAL function

* feat(THU-552): useCanCreateWorkspace hook

* feat(THU-552): EmailChipInput component

* feat(THU-552): CreateWorkspaceModal component

* feat(THU-552): Create workspace button in sidebar selector footer

* feat(THU-552): seed defaults into newly-created shared workspace

* fix(THU-552): scope cleanupRemovedDefaults to a workspace

* feat(THU-552): split create + invite into two modals, single Create CTA

* feat(THU-552): workspace selector design polish

* fix(THU-552): rename SCREAMING_SNAKE constants to camelCase

* feat(THU-554): Settings → Workspace → General (rename, slug, icon, duplicate) (#971)

* fix(THU-554): align PageHeader actions with content padding

* feat(THU-554): allow personal workspace rename via upload handler

* feat(THU-554): updateWorkspaceName DAL, default name, createdAt stamps

* feat(THU-554): useActiveWorkspaceMembership hook

* feat(THU-554): split settings sidebar into Account Settings and Workspace groups

* feat(THU-554): Workspace Settings page with autosave rename

* feat(THU-554): add slug + icon columns to workspaces

* feat(THU-554): workspace slug input with auto-derive from name

* feat(THU-554): workspace icon picker (emoji or image upload)

* feat(THU-554): render workspace icon in the sidebar selector

* feat(THU-554): share WorkspaceFormFields between settings and create modal

* feat(THU-554): duplicate workspace action

* feat(THU-555): Settings → Workspace → Members (add / remove / role / pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
…vity (#958)

* feat(THU-553): polish mode-picker UI — layout, selection state, and connectivity

* fix(THU-553): DI for mode-picker, drop mock.module('@/lib/http') leakage

* feat(THU-551): workspace selector in sidebar + URL-based active workspace (#961)

* feat(THU-551): reactive active-workspace + URL helpers

* feat(THU-551): useWorkspacesQuery hook + DAL test

* feat(THU-551): WorkspaceMembershipGate + live membership query

* feat(THU-551): mount workspace routes under /w/:workspaceId prefix

* feat(THU-551): workspace selector in sidebar header

* feat(THU-551): thread workspace prefix through navigation call sites

* feat(THU-552): create workspace modal — name + invite by email (#965)

* feat(THU-552): promote pending membership to active on email match

* feat(THU-552): createSharedWorkspace DAL function

* feat(THU-552): useCanCreateWorkspace hook

* feat(THU-552): EmailChipInput component

* feat(THU-552): CreateWorkspaceModal component

* feat(THU-552): Create workspace button in sidebar selector footer

* feat(THU-552): seed defaults into newly-created shared workspace

* fix(THU-552): scope cleanupRemovedDefaults to a workspace

* feat(THU-552): split create + invite into two modals, single Create CTA

* feat(THU-552): workspace selector design polish

* fix(THU-552): rename SCREAMING_SNAKE constants to camelCase

* feat(THU-554): Settings → Workspace → General (rename, slug, icon, duplicate) (#971)

* fix(THU-554): align PageHeader actions with content padding

* feat(THU-554): allow personal workspace rename via upload handler

* feat(THU-554): updateWorkspaceName DAL, default name, createdAt stamps

* feat(THU-554): useActiveWorkspaceMembership hook

* feat(THU-554): split settings sidebar into Account Settings and Workspace groups

* feat(THU-554): Workspace Settings page with autosave rename

* feat(THU-554): add slug + icon columns to workspaces

* feat(THU-554): workspace slug input with auto-derive from name

* feat(THU-554): workspace icon picker (emoji or image upload)

* feat(THU-554): render workspace icon in the sidebar selector

* feat(THU-554): share WorkspaceFormFields between settings and create modal

* feat(THU-554): duplicate workspace action

* feat(THU-555): Settings → Workspace → Members (add / remove / role / pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 24cc392. Configure here.

const wouldChangeRole = newRole !== null && existing.role !== newRole
if (
(wouldChangeRole || targetsAdminRole) &&
!(await callerSatisfiesPermission(tx, existing.workspaceId, ctx.userId, 'change_roles'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admin PUT wrongly needs change_roles

Medium Severity

For workspace_memberships PUTs that omit workspace_id and only target an existing row by op.id, validation treats any role: 'admin' payload as needing change_roles, even when the member is already an admin and the role does not change. The insert-or-update path correctly uses wouldMintNewAdmin only for new rows; this branch still uses targetsAdminRole alone, so callers with invite_users but not change_roles get INSUFFICIENT_PERMISSION on harmless re-upserts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 24cc392. Configure here.

isResolved && (requiredRole === 'member' ? userRole === 'admin' || userRole === 'member' : userRole === 'admin')
isResolved &&
!!membership &&
(requiredRole === 'member' ? userRole === 'admin' || userRole === 'member' : userRole === 'admin')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FE duplicates permissionAllows logic

Low Severity

This PR adds shared permissionAllows in shared/workspaces.ts so frontend and backend use the same satisfiability rule, but useWorkspacePermission reimplements that predicate inline instead of calling permissionAllows. Future changes to role rules can update the shared helper while the hook stays wrong, hiding denied actions or showing ones the backend rejects.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 24cc392. Configure here.

raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
* feat(THU-578): scope all FE DAL methods to active workspaceId

* fix(THU-578): seed trust domain in eval CLI + scope model-profile upsert by workspace

* fix(THU-578): retry hydration when workspaceId resolves after WorkspaceGate

* fix(THU-578): evict stale chat session on workspace switch

* fix(THU-578): scope prompt join in getTriggerPromptForThread to active workspace

* fix(THU-578): fix random test failures from workspaceId resolution races

* feat(THU-553): mode picker UI — layout, selection state, and connectivity (#958)

* feat(THU-553): polish mode-picker UI — layout, selection state, and connectivity

* fix(THU-553): DI for mode-picker, drop mock.module('@/lib/http') leakage

* feat(THU-551): workspace selector in sidebar + URL-based active workspace (#961)

* feat(THU-551): reactive active-workspace + URL helpers

* feat(THU-551): useWorkspacesQuery hook + DAL test

* feat(THU-551): WorkspaceMembershipGate + live membership query

* feat(THU-551): mount workspace routes under /w/:workspaceId prefix

* feat(THU-551): workspace selector in sidebar header

* feat(THU-551): thread workspace prefix through navigation call sites

* feat(THU-552): create workspace modal — name + invite by email (#965)

* feat(THU-552): promote pending membership to active on email match

* feat(THU-552): createSharedWorkspace DAL function

* feat(THU-552): useCanCreateWorkspace hook

* feat(THU-552): EmailChipInput component

* feat(THU-552): CreateWorkspaceModal component

* feat(THU-552): Create workspace button in sidebar selector footer

* feat(THU-552): seed defaults into newly-created shared workspace

* fix(THU-552): scope cleanupRemovedDefaults to a workspace

* feat(THU-552): split create + invite into two modals, single Create CTA

* feat(THU-552): workspace selector design polish

* fix(THU-552): rename SCREAMING_SNAKE constants to camelCase

* feat(THU-554): Settings → Workspace → General (rename, slug, icon, duplicate) (#971)

* fix(THU-554): align PageHeader actions with content padding

* feat(THU-554): allow personal workspace rename via upload handler

* feat(THU-554): updateWorkspaceName DAL, default name, createdAt stamps

* feat(THU-554): useActiveWorkspaceMembership hook

* feat(THU-554): split settings sidebar into Account Settings and Workspace groups

* feat(THU-554): Workspace Settings page with autosave rename

* feat(THU-554): add slug + icon columns to workspaces

* feat(THU-554): workspace slug input with auto-derive from name

* feat(THU-554): workspace icon picker (emoji or image upload)

* feat(THU-554): render workspace icon in the sidebar selector

* feat(THU-554): share WorkspaceFormFields between settings and create modal

* feat(THU-554): duplicate workspace action

* feat(THU-555): Settings → Workspace → Members (add / remove / role / pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
* feat(THU-550): BE workspaces tables + post-create bootstrap hook

* feat(THU-550): PowerSync upload handler factory + workspace handlers

* feat(THU-550): workspace_id on scoped tables + sync rules + ws handler

* feat(THU-550): encrypt workspaces.name

* feat(THU-550): shared deterministic personal-workspace id helpers

* feat(THU-550): BE — FE-owned personal workspace creation

* feat(THU-550): FE — workspace-aware DAL + post-auth bootstrap + gate

* fix(THU-550): log permanently rejected upload ops

* fix(THU-550): pin workspace_id in PATCH/DELETE WHERE clause

* fix(THU-550): scope reconcile-defaults lookups and updates by workspace_id

* fix(THU-550): force personal workspace name + shared workspace admin bootstrap

* fix(THU-550): throw bootstrap error + update stale 400 upload test assertions

* fix(THU-550): add workspaceId to model profile defaults + fix getSystemTinfoilClient cloud URL

* fix(THU-550): restore BE test isolation broken by upload handler transactions

* ci(THU-550): install root deps before BE type-check (shared/ uses uuid)

* feat(THU-578): scope all FE DAL methods to active workspaceId (#951)

* feat(THU-578): scope all FE DAL methods to active workspaceId

* fix(THU-578): seed trust domain in eval CLI + scope model-profile upsert by workspace

* fix(THU-578): retry hydration when workspaceId resolves after WorkspaceGate

* fix(THU-578): evict stale chat session on workspace switch

* fix(THU-578): scope prompt join in getTriggerPromptForThread to active workspace

* fix(THU-578): fix random test failures from workspaceId resolution races

* feat(THU-553): mode picker UI — layout, selection state, and connectivity (#958)

* feat(THU-553): polish mode-picker UI — layout, selection state, and connectivity

* fix(THU-553): DI for mode-picker, drop mock.module('@/lib/http') leakage

* feat(THU-551): workspace selector in sidebar + URL-based active workspace (#961)

* feat(THU-551): reactive active-workspace + URL helpers

* feat(THU-551): useWorkspacesQuery hook + DAL test

* feat(THU-551): WorkspaceMembershipGate + live membership query

* feat(THU-551): mount workspace routes under /w/:workspaceId prefix

* feat(THU-551): workspace selector in sidebar header

* feat(THU-551): thread workspace prefix through navigation call sites

* feat(THU-552): create workspace modal — name + invite by email (#965)

* feat(THU-552): promote pending membership to active on email match

* feat(THU-552): createSharedWorkspace DAL function

* feat(THU-552): useCanCreateWorkspace hook

* feat(THU-552): EmailChipInput component

* feat(THU-552): CreateWorkspaceModal component

* feat(THU-552): Create workspace button in sidebar selector footer

* feat(THU-552): seed defaults into newly-created shared workspace

* fix(THU-552): scope cleanupRemovedDefaults to a workspace

* feat(THU-552): split create + invite into two modals, single Create CTA

* feat(THU-552): workspace selector design polish

* fix(THU-552): rename SCREAMING_SNAKE constants to camelCase

* feat(THU-554): Settings → Workspace → General (rename, slug, icon, duplicate) (#971)

* fix(THU-554): align PageHeader actions with content padding

* feat(THU-554): allow personal workspace rename via upload handler

* feat(THU-554): updateWorkspaceName DAL, default name, createdAt stamps

* feat(THU-554): useActiveWorkspaceMembership hook

* feat(THU-554): split settings sidebar into Account Settings and Workspace groups

* feat(THU-554): Workspace Settings page with autosave rename

* feat(THU-554): add slug + icon columns to workspaces

* feat(THU-554): workspace slug input with auto-derive from name

* feat(THU-554): workspace icon picker (emoji or image upload)

* feat(THU-554): render workspace icon in the sidebar selector

* feat(THU-554): share WorkspaceFormFields between settings and create modal

* feat(THU-554): duplicate workspace action

* feat(THU-555): Settings → Workspace → Members (add / remove / role / pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
raivieiraadriano92 added a commit that referenced this pull request Jun 16, 2026
* feat(THU-549): serverId + extended /v1/config payload

* feat(THU-549): trust-domain registry + namespaced auth/device/keys

* feat(THU-549): boot env vars + trust-domain decision tree

* feat(THU-549): scope DB filename by trust domain + lifecycle broadcast

* feat(THU-549): logout = unconditional wipe

* feat(THU-549): scope cloud URL by active server

* refactor(THU-549): collapse registry actions into activateServer/activateStandalone

* feat(THU-549): persist per-server session on ServerEntry + wire mirror

* refactor(THU-549): drop unused db-reopened event + subscribeDbLifecycle

* refactor(THU-549): extract shared sync-worker-name format

* fix(THU-549): pin SERVER_ID for e2e backends

* fix(THU-549): address PR review — timeout, Tauri OPFS, logout loading state, test SERVER_ID, auth-token let

* fix(THU-549): wipe IDB databases on logout + fix SSO redirect race + delete E2EE keys IDB

* fix(THU-549): use getActiveCloudUrl in getSystemTinfoilClient

* fix(THU-550): fix BE/FE tests broken by THU-549 trust-domain rebase

* feat(THU-550): Workspaces data layer (#944)

* feat(THU-550): BE workspaces tables + post-create bootstrap hook

* feat(THU-550): PowerSync upload handler factory + workspace handlers

* feat(THU-550): workspace_id on scoped tables + sync rules + ws handler

* feat(THU-550): encrypt workspaces.name

* feat(THU-550): shared deterministic personal-workspace id helpers

* feat(THU-550): BE — FE-owned personal workspace creation

* feat(THU-550): FE — workspace-aware DAL + post-auth bootstrap + gate

* fix(THU-550): log permanently rejected upload ops

* fix(THU-550): pin workspace_id in PATCH/DELETE WHERE clause

* fix(THU-550): scope reconcile-defaults lookups and updates by workspace_id

* fix(THU-550): force personal workspace name + shared workspace admin bootstrap

* fix(THU-550): throw bootstrap error + update stale 400 upload test assertions

* fix(THU-550): add workspaceId to model profile defaults + fix getSystemTinfoilClient cloud URL

* fix(THU-550): restore BE test isolation broken by upload handler transactions

* ci(THU-550): install root deps before BE type-check (shared/ uses uuid)

* feat(THU-578): scope all FE DAL methods to active workspaceId (#951)

* feat(THU-578): scope all FE DAL methods to active workspaceId

* fix(THU-578): seed trust domain in eval CLI + scope model-profile upsert by workspace

* fix(THU-578): retry hydration when workspaceId resolves after WorkspaceGate

* fix(THU-578): evict stale chat session on workspace switch

* fix(THU-578): scope prompt join in getTriggerPromptForThread to active workspace

* fix(THU-578): fix random test failures from workspaceId resolution races

* feat(THU-553): mode picker UI — layout, selection state, and connectivity (#958)

* feat(THU-553): polish mode-picker UI — layout, selection state, and connectivity

* fix(THU-553): DI for mode-picker, drop mock.module('@/lib/http') leakage

* feat(THU-551): workspace selector in sidebar + URL-based active workspace (#961)

* feat(THU-551): reactive active-workspace + URL helpers

* feat(THU-551): useWorkspacesQuery hook + DAL test

* feat(THU-551): WorkspaceMembershipGate + live membership query

* feat(THU-551): mount workspace routes under /w/:workspaceId prefix

* feat(THU-551): workspace selector in sidebar header

* feat(THU-551): thread workspace prefix through navigation call sites

* feat(THU-552): create workspace modal — name + invite by email (#965)

* feat(THU-552): promote pending membership to active on email match

* feat(THU-552): createSharedWorkspace DAL function

* feat(THU-552): useCanCreateWorkspace hook

* feat(THU-552): EmailChipInput component

* feat(THU-552): CreateWorkspaceModal component

* feat(THU-552): Create workspace button in sidebar selector footer

* feat(THU-552): seed defaults into newly-created shared workspace

* fix(THU-552): scope cleanupRemovedDefaults to a workspace

* feat(THU-552): split create + invite into two modals, single Create CTA

* feat(THU-552): workspace selector design polish

* fix(THU-552): rename SCREAMING_SNAKE constants to camelCase

* feat(THU-554): Settings → Workspace → General (rename, slug, icon, duplicate) (#971)

* fix(THU-554): align PageHeader actions with content padding

* feat(THU-554): allow personal workspace rename via upload handler

* feat(THU-554): updateWorkspaceName DAL, default name, createdAt stamps

* feat(THU-554): useActiveWorkspaceMembership hook

* feat(THU-554): split settings sidebar into Account Settings and Workspace groups

* feat(THU-554): Workspace Settings page with autosave rename

* feat(THU-554): add slug + icon columns to workspaces

* feat(THU-554): workspace slug input with auto-derive from name

* feat(THU-554): workspace icon picker (emoji or image upload)

* feat(THU-554): render workspace icon in the sidebar selector

* feat(THU-554): share WorkspaceFormFields between settings and create modal

* feat(THU-554): duplicate workspace action

* feat(THU-555): Settings → Workspace → Members (add / remove / role / pending) (#974)

* feat(THU-555): add user_name/user_email to workspace_memberships

* feat(THU-555): enrich membership upload handler with user display info

* feat(THU-555): propagate user name/email changes to memberships

* feat(THU-555): mirror userName/userEmail on FE workspace_memberships

* feat(THU-555): reactive members + pending queries

* feat(THU-555): membership mutation DAL

* feat(THU-555): useWorkspacePermission hook

* feat(THU-555): RequireWorkspacePermission route wrapper

* feat(THU-555): Members page scaffold + route

* feat(THU-555): Members sidebar entry gated by manage_members

* feat(THU-555): render members table with role dropdown

* feat(THU-555): wire Remove member with confirmation

* feat(THU-555): use globe icon for General sidebar entry

* fix(THU-555): rename SCREAMING_SNAKE constant to camelCase

* feat(THU-593): gate member management on E2EE-enabled servers

* fix(chat-store): dedupe concurrent hydrateChatStore calls

* feat(THU-556): Settings → Workspace → Permissions page + resource-CRUD enforcement (#978)

* feat(THU-556): setWorkspacePermissionRequiredRole DAL

* feat(THU-556): RequireWorkspaceAdmin route guard

* refactor(THU-556): consolidate workspace permission keys into shared/workspaces.ts

* feat(THU-556): Permissions page (11 keys, labels only)

* feat(THU-556): Permissions sidebar entry

* feat(THU-556): mount Permissions route

* feat(THU-556): refine Permissions page copy and layout

* feat(THU-556): scope Permissions to agents/skills/models/mcps

* feat(THU-556): use Lock icon for Permissions sidebar entry

* feat(THU-556): permissionAllows + BE permission lookup helpers

* feat(THU-556): permission-key gating in workspace-scoped handler

* feat(THU-556): enforce add_agents/remove_agents permissions

* feat(THU-556): treat soft-delete PATCH as remove for permission gating

* feat(THU-556): enforce add_skills/remove_skills permissions

* feat(THU-556): enforce add_models/remove_models permissions

* feat(THU-556): enforce add_mcp_servers/remove_mcp_servers permissions

* feat(THU-556): gate edit + chat bypasses, add permission gating tests

* fix(workspaces): address review comments across stacked PRs (#988)

* fix(THU-549): clear auth token after signOut so server can revoke session

* fix(THU-549): clear registry before db-closing so reloaded tabs land at ModePicker

* fix(THU-549): guard LogoutModal handleLogout against rapid double-click

* fix(THU-549): friendlier ZodError for unset/invalid SERVER_ID

* fix(THU-549): remove Cloud URL dev-settings input (use ModePicker flow instead)

* fix(THU-552): preserve existing membership role on promote-on-insert

* fix(THU-552): delete pending row by (workspace_id, email) after promote-on-insert

* fix(THU-552): skip onCreated when create-workspace modal dismissed mid-flight

* fix(THU-554): preserve slug/icon on PUT when payload omits them

* fix(THU-554): reflect remote workspace updates into settings form

* fix(THU-554): append random suffix to duplicate workspace slug

* fix(THU-555): resolve workspace permission state when user has no membership

* fix(THU-555): gate member actions on granular permission keys

* fix(THU-551): expose URL workspace id immediately + collapse chat-id on workspace switch

* fix(THU-550): wrap BrowserRouter in ErrorBoundary so bootstrap throws surface a screen

* chore: strip PR-ref noise from review-followup comments

* fix(THU-578): reset isReady before chat session eviction on workspace switch

* fix(THU-553): dedupe ModePicker validation between blur and Continue

* fix(THU-552): force ctx.userId for invitedByUserId in pending memberships

* fix(THU-552): reject malformed emails on pending membership writes

* fix(THU-578): persist selectedAgent against session.workspaceId not thread

* fix(THU-556): hide chat-skills-bar when no edit perm and no pinned chips

* docs: refresh workspace-memberships handler docstring for permission-key model

* test(THU-578): seed workspace context in LinkPreviewWidget fallback tests

* fix(THU-553): reset isValidating on SET_URL to unstick Continue after stale blur

* fix(THU-553): reset stage to picker on stale-URL bail-out in handleContinue

* fix(THU-549): pass captured serverId to handleFullWipe after registry clear

* fix(THU-555): align pending-row gates + block admin escalation via invite

* refactor(THU-555): use isResolved for membership loading distinction

* refactor(THU-555): assign openRef in render body, drop useEffect

* fix(THU-555): widen duplicate-slug suffix to 8 chars for collision resistance

* docs(THU-555): refresh stale comments referencing removed manage_members guard

* fix(THU-555): mirror serverUrl ref synchronously in mode-picker onChange

* fix(THU-555): sync pending memberships to all workspace members

* fix(THU-555): require change_roles for membership PUT that changes existing role

* fix(THU-549): keep getAuthToken resolvable through signOut after registry clear

* fix(THU-555): add last-admin guard to membership PUT apply path

* fix(THU-555): gate pending-row Admin option on change_roles
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