Skip to content

feat: workspaces v1#1012

Open
raivieiraadriano92 wants to merge 19 commits into
mainfrom
workspaces
Open

feat: workspaces v1#1012
raivieiraadriano92 wants to merge 19 commits into
mainfrom
workspaces

Conversation

@raivieiraadriano92

@raivieiraadriano92 raivieiraadriano92 commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Lands Workspaces v1 — multi-user support for enterprise deployments where members share AI configuration (models, modes, prompts, MCP servers, agents, skills) inside a workspace while keeping chats, messages, and tasks private per user.

What's in

  • Foundations (THU-549) — serverId + extended /v1/config, trust-domain registry, per-server scoped SQLite, namespaced auth/device/keys, logout = wipe.
  • Data layer (THU-550, THU-578) — workspaces schema, sync rules, scoped upload handlers, FE DAL threaded through workspaceId, post-auth bootstrap + gate.
  • Mode picker (THU-553) — server vs standalone selector at boot.
  • Sidebar + routing (THU-551) — workspace selector in sidebar, /w/:workspaceId routing for shared workspaces (personal stays at unprefixed URLs).
  • Create modal (THU-552) — name + invite by email, with promote-on-insert.
  • Settings → Workspace
    • General (THU-554) — rename, slug, icon, duplicate; personal rename allowed.
    • Members (THU-555) — add / remove / role / pending invites.
    • Permissions (THU-556) — 8 granular keys for agents / skills / models / mcps (add + remove), enforced FE and BE via createWorkspaceScopedHandler + useWorkspacePermission.
  • User-private scope (THU-603) — resources can opt into per-user visibility inside a workspace.
  • Data migration (THU-622) — BE backfills (not truncates) and FE attach-migrates legacy localStorage / IDB / SQLite into the new per-server DB so pre-Workspaces clients upgrade seamlessly.
  • Models api_key (THU-579) — reverts THU-505; key lives on the synced models row again, copied over from the old models_secrets table.

Out of scope (post-v1)

Standalone UX polish, anon entry button, workspace delete, multi-server UI, surfacing rejected sync ops (THU-557, THU-592, THU-598, THU-599, THU-590).


Note

High Risk
Large one-shot schema migration on published PowerSync tables (PK swaps and backfills) plus changed upload semantics affect every synced client; SERVER_ID is a breaking deploy requirement that resets client trust domains if mis-set.

Overview
Introduces Workspaces v1 on the backend: a consolidated migration adds workspace identity tables, backfills a personal workspace per user, threads workspace_id (and optional scope) through PowerSync data tables, reshapes composite PKs/FKs, and restores models.api_key.

Deployment identity and policySERVER_ID is now required (validated UUID); GET /v1/config exposes serverId, anonymous-user allowance, workspace-creation flags, and allowUserScopedResources. Env examples and CI install root deps so shared/workspaces.ts can resolve uuid during backend type-check.

PowerSync uploads — the monolithic applyOperation DAL is replaced by applyUploadBatch and per-table handlers (workspace-scoped vs user-scoped). Successful uploads return { success, rejected[] } with per-op codes (e.g. ROW_NOT_FOUND, UNKNOWN_TABLE) instead of failing the whole batch with HTTP 400; transient failures use 503.

Auth lifecycle — on user create, promotePendingMemberships turns email invites into memberships; on profile update, syncMembershipDisplayInfo keeps denormalized member display fields in sync. New workspace DAL powers upload enforcement and tests cover these flows.

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

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

Copy link
Copy Markdown

Semgrep Security Scan

No security issues found.

Comment thread src/crypto/key-storage.ts Fixed
Comment thread src/crypto/key-storage.ts Fixed
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

PR Metrics

Metric Value
Lines changed (prod code) +12491 / -1541
JS bundle size (gzipped) 🔴 769.2 KB → 839.6 KB (+70.4 KB, +9.2%)
Test coverage 🟢 78.35% → 78.88% (+0.5%)
Performance (preview) Preview not ready — Render deploy may have timed out
Accessibility
Best Practices
SEO

Updated Mon, 29 Jun 2026 19:38:26 GMT · run #2108

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Preview environment deployed 🚀

Service URL
Marketing / blog / docs https://thunderbolt-pr-1012.preview.thunderbolt.io
App https://app-pr-1012.preview.thunderbolt.io
API https://api-pr-1012.preview.thunderbolt.io
Keycloak https://auth-pr-1012.preview.thunderbolt.io
PowerSync https://powersync-pr-1012.preview.thunderbolt.io

Stack: preview-pr-1012 · Commit: 49ec550b65434308df23d10bb0e928cee1ebdecb

Auto-destroys on PR close/merge. Login via the bundled Keycloak realm — demo@thunderbolt.io / demo by default.

* 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
* chore(THU-603): add scope column to workspace resource schemas

* feat(THU-603): split sync rules by resource scope

* feat(THU-603): scope-aware upload handler + config flag

* feat(THU-603): thread scope through FE config and DAL

* feat(THU-603): add scope picker primitive and gate hook

* feat(THU-603): expose scope picker in resource UIs

* feat(THU-603): accept user scope in workspace upload handler

* fix(THU-603): skip default tasks when seeding new workspaces

* fix(THU-603): honor composite PK in upload-handler row lookup

* fix: stabilize logout-modal and skills-view flaky tests

* refactor: split settings sidebar into Account / Extensions / Workspace

* feat: open Workspace > General to members; read-only for non-admins

* fix: reset useConfigStore between test suites to prevent leak

* fix(THU-603): enforce allowUserScopedResources + INVALID_SCOPE on PUT and PATCH
* feat(THU-622): backfill workspace_id in BE 0020/0021 instead of truncating

* feat(THU-622): unblock pre-Workspaces clients during rollout

* feat(THU-622): FE pre-Workspaces attach migration + boot wiring

* feat(THU-579): add api_key column to models schema (revert THU-505)

* refactor(THU-579): read api_key directly from models, drop models_secrets

* feat(THU-579): copy api_key from legacy models_secrets without clobbering

* feat(THU-622): redirect to / after first-time migration

* fix(THU-579): stamp api_key via Drizzle so PowerSync uploads it

* fix(THU-622): rewrite pre-Workspaces local DB migration via separate wa-sqlite engine

* chore: merge BE migrations

* fix(THU-622): gate destructive migration steps on data-completion flag

* docs(THU-622): move pre-workspaces-attach README to docs/architecture

* fix(THU-622): warn on dropped legacy rows during table copy

* docs(THU-622): mark PowerSync internal-schema coupling in ps_crud import

* fix(THU-622): assert non-null user_id before backfill in 0021 migration

* refactor(THU-622): rename ranAttach to ranMigration on migration result

* fix(THU-622): defer pre-Workspaces telemetry until after PostHog init

* fix(THU-622): preserve legacy localStorage and IDB keys for rollback

* fix(THU-622): device-global migration flag, ps_crud wipe, drop tripwire

* fix(THU-622): post-merge test and DAL fallout
@raivieiraadriano92 raivieiraadriano92 changed the title Workspaces feat: Workspaces v1 Jun 28, 2026
@raivieiraadriano92 raivieiraadriano92 marked this pull request as ready for review June 28, 2026 12:34
Comment thread backend/src/powersync/upload-handlers/workspaces.ts
Comment thread backend/drizzle/0021_brave_quasar.sql
Comment thread backend/src/powersync/upload-handlers/workspace-scoped.ts
@raivieiraadriano92 raivieiraadriano92 changed the title feat: Workspaces v1 feat: workspaces v1 Jun 28, 2026
Comment thread backend/drizzle/0021_brave_quasar.sql

@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 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e6210f8. Configure here.

Comment thread backend/src/powersync/upload-handlers/user-scoped.ts
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.

2 participants