refactor: retire the communal workspace concept#522
Conversation
`default` becomes an ordinary workspace. It is not renamed, not deleted, and its objects and public URLs are untouched — `storage.uploads.sh` is an R2 custom domain, so those links never consulted the workspace record and are unaffected by anything here. The concept was already dead in practice. No signup path joins `default`: there is no user-create hook and the only membership insert outside tests is the local demo. Production has one member in it (the operator), and PR #353 removed the last behavioral exemptions. What remained was a name and a handful of branches. Removed: - `packages/workspace` entirely — it held nothing but `COMMUNAL_WORKSPACE` and `isCommunalWorkspace`, so retiring the concept deletes the package. - The `communal` flag on `/me/workspaces` AND the web fallback that re-derived it from the slug. Both had to go together: with only the server half removed, the client would have kept inferring the old behavior indefinitely. - The auto-open skip in `resolveDefaultWorkspace`. - The member-cap exemption. `default` carries no plan and no `selfServe`, so it still resolves to unlimited on its own; a plan stamped on it now applies, which is pinned by a new test. - The invite-email special case ("You've been given access to uploads.sh"). - The `#479` content-hash gate. Workspace membership is the trust boundary uniformly: members can already read each other's files, so inheriting metadata from a readable file discloses nothing new. Kept: `default` stays in `RESERVED_WORKSPACE_NAMES`, with the justification rewritten. The old comment argued from a reaper exemption that no longer exists; the real reason is that years of public `storage.uploads.sh/default/…` URLs are embedded in merged PRs, so the slug must never be reclaimable. Also in this change: - `uploads config init` with no flags no longer seeds `UPLOADS_WORKSPACE=default`. That entry outranked the workspace encoded in the token, pinning every later login to `default` regardless of what the token was minted for. This path had no test coverage; it does now. - Workspace-name prefill (#506). `GET /v1/tokens` returns `suggestedWorkspace` derived from the caller's GitHub login, but only for an account with no workspace yet. Consumed by the device-approval page, `/account/workspaces/new` (superseding the weaker email heuristic), and the `uploads login` TTY prompt as a bracketed default. It is a hint, never a decision: nothing is created and no name is claimed. Every failure — no linked GitHub account, a login that can't become a valid slug, a reserved or blocklisted name, one already taken — resolves to no suggestion, which is an empty field, which is the prior behavior. Auto-provisioning was rejected: it would have to invent an answer for each of those cases and would claim a slug on every signup. Two user-visible changes, both affecting only accounts that are members of `default`: the app may now open `default` rather than another workspace, and invites to it name it like any other workspace. Verification: pnpm test 3140/3140 (242 files), pnpm typecheck clean, changed files format-clean, /device and /account/workspaces/new render with no console errors. Closes #505. Closes #506.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-api | 566a131 | Commit Preview URL Branch Preview URL |
Jul 24 2026, 08:10 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-auth | 247a706 | Commit Preview URL Branch Preview URL |
Jul 24 2026, 07:48 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-web | 247a706 | Commit Preview URL Branch Preview URL |
Jul 24 2026, 07:48 PM |
|
CodeRabbit (@coderabbitai) review |
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (2)
🚫 Excluded labels (none allowed) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe change removes communal-workspace special cases across API, web, email, and billing paths. It adds GitHub-derived workspace suggestions to API, web, and CLI onboarding, and stops bare config initialization from seeding a workspace override. ChangesWorkspace model and communal-workspace retirement
Workspace suggestions and onboarding
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant WebOrCLI
participant TokensAPI
participant GitHubCache
User->>WebOrCLI: Start workspace onboarding
WebOrCLI->>TokensAPI: Request suggested workspace
TokensAPI->>GitHubCache: Resolve linked GitHub login
GitHubCache-->>TokensAPI: Return login or no result
TokensAPI-->>WebOrCLI: Return optional suggestion
WebOrCLI-->>User: Prefill or show bracketed default
User->>WebOrCLI: Accept or override workspace name
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/workspace-suggestion.ts`:
- Around line 62-63: Update the workspace suggestion logic around
loadWorkspaceRecord so it checks raw KV/name-key occupancy rather than treating
a null record as available, thereby excluding soft-deleted, tombstoned, and
purged names that still reserve keys. Reuse or expose a dedicated availability
helper consistent with the workspace.ts contract, and add a regression case
covering deleted/tombstoned names.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a875be99-3826-4e28-b75f-ec632cc5647f
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (35)
.changeset/quiet-lions-retire.mdapps/api/package.jsonapps/api/src/content-hash.tsapps/api/src/routes/internal-billing.test.tsapps/api/src/routes/internal-billing.tsapps/api/src/routes/me.test.tsapps/api/src/routes/me.tsapps/api/src/routes/tokens.test.tsapps/api/src/routes/tokens.tsapps/api/src/slug-policy.tsapps/api/src/workspace-suggestion.test.tsapps/api/src/workspace-suggestion.tsapps/api/test/content-hash.test.tsapps/web/package.jsonapps/web/src/lib/api-client.test.tsapps/web/src/lib/api-client.tsapps/web/src/lib/workspace-file-row.test.tsapps/web/src/lib/workspaces-nav.test.tsapps/web/src/lib/workspaces-nav.tsapps/web/src/pages/account/workspaces/new.astroapps/web/src/pages/device.astrodocs/admin-tokens.mdpackages/email/package.jsonpackages/email/src/invites.tspackages/email/test/invites.test.tspackages/uploads/src/client.tspackages/uploads/src/commands/config.tspackages/uploads/src/commands/login.tspackages/uploads/test/commands-login.test.tspackages/uploads/test/config-init.test.tspackages/workspace/README.mdpackages/workspace/package.jsonpackages/workspace/src/communal.tspackages/workspace/src/index.tspackages/workspace/tsconfig.json
💤 Files with no reviewable changes (9)
- packages/workspace/README.md
- packages/workspace/package.json
- packages/workspace/src/communal.ts
- packages/workspace/tsconfig.json
- apps/web/package.json
- apps/web/src/lib/workspace-file-row.test.ts
- packages/workspace/src/index.ts
- apps/api/package.json
- packages/email/package.json
The #506 prefill checked availability with `loadWorkspaceRecord`, which deliberately collapses soft-deleted records and purged tombstones to "not found" so every auth and serving path 404s uniformly. Those records still hold the `ws:<name>` KV key, and `POST /v1/workspaces` gates on a raw read — so the prefill could offer a name that creation immediately rejected with `workspace_name_taken`. Adds `isWorkspaceNameTaken` in workspace.ts as the single answer to "can this name be registered", and points both creation and the suggestion at it. The two were previously asking the same question through different lookups with different semantics, which is the exact drift this PR is otherwise removing. Regression coverage for both hidden cases: a soft-deleted record and a purged tombstone each yield no suggestion. Caught by CodeRabbit on #522. Verification: pnpm test 3142/3142, pnpm typecheck clean, changed files format-clean.
Two user-visible CLI behaviors from this PR were undocumented: - `uploads login` and the create-workspace form now offer a name derived from the user's GitHub login. enrollment.md described the prompt without it. - `uploads config init` with no flags no longer seeds UPLOADS_WORKSPACE. No doc claimed it did, but the CLI reference is the place that should say what bare init writes, and why not seeding a workspace is deliberate. AGENTS.md requires skills/uploads-cli to stay in sync with CLI behavior, so both land there; enrollment.md gets the onboarding-facing half. Swept the rest and found nothing stale: no doc referenced the deleted packages/workspace (it was never in README's package table), content-hash inheritance is not documented anywhere (the communal gate was internal), and no doc asserts the auto-open or invite-copy behavior that changed.
…ore (#523) #522 deleted packages/workspace but left it in .changeset/config.json's ignore list. Changesets validates that every ignored entry resolves to a real package, so `changeset version` now fails config parsing before it does any work — blocking the release PR and every npm publish behind it. Removing the stale entry restores it. `changeset status` confirms the pending @buildinternet/uploads minor is intact, so nothing was lost; the release PR regenerates on the next run. Checked the remaining five ignore entries against the actual workspace list — all resolve.
Retires the "communal workspace" concept.
defaultbecomes an ordinary workspace — not renamed, not deleted, objects and public URLs untouched.Closes #505. Closes #506.
Why this is safe
storage.uploads.shis an R2 custom domain, not a Worker route. Existingstorage.uploads.sh/default/…URLs are served straight off the bucket and never consult the workspace record, the org, or any membership — so nothing here can break a link that's already been pasted into a PR or Slack.The concept was also already dead in practice. No signup path joins
default: there's no user-create hook, and the only membership insert outside tests is the local demo. #353 removed the last behavioral exemptions. What remained was a name and a handful of branches.Removed
packages/workspaceentirely. It held nothing butCOMMUNAL_WORKSPACEandisCommunalWorkspace, so retiring the concept deletes the package.communalflag on/me/workspacesand the web fallback that re-derived it from the slug. These had to land together — with only the server half removed,mapMyWorkspacewould have read the missing field as absent and kept inferring the old behavior from the name indefinitely.resolveDefaultWorkspace.defaultcarries no plan and noselfServe, so it still resolves to unlimited on its own — verified, and pinned by a new test. A plan stamped on it now applies, which is the behavior change; that's pinned too.Kept
defaultstays inRESERVED_WORKSPACE_NAMES. The old comment justified it via a reaper exemption that no longer exists; the wording is rewritten around the reason that's actually true — years of publicstorage.uploads.sh/default/…URLs are embedded in merged PRs, so the slug must never become reclaimable if the workspace were ever hard-deleted.This is now the only thing that treats
defaultdifferently, and it's a squatting guard on a name that served public traffic, not a claim that the workspace is special.The inheritance rule (#479)
Dropped the gate. Workspace membership is the trust boundary, uniformly.
The carve-out existed because
defaultwas the communal tenant, where "same workspace" said nothing about whether two uploaders were related. With that retired, every workspace is one whose members are there deliberately — and since members can already read each other's files, inheriting metadata from a file the caller could simply open discloses nothing new.Considered and rejected: gating on member count (would newly break inheritance in the 3-person team workspace where it works today, and adds a lookup to the upload path) and gating on uploader identity (attribution is null for legacy and enrollment tokens, so it would silently stop working for exactly the tokens
defaultuses).Also included
uploads config initwith no flags no longer seedsUPLOADS_WORKSPACE=default. That entry outranked the workspace encoded in the token, pinning every lateruploads logintodefaultregardless of what the token was minted for. This path had no test coverage; it does now.Workspace-name prefill (#506).
GET /v1/tokensreturnssuggestedWorkspace, derived from the caller's GitHub login — but only for an account with no workspace yet, so it costs nothing for everyone else. Consumed by three surfaces: the device-approval page,/account/workspaces/new(superseding the weaker email-local-part heuristic), and theuploads loginTTY prompt as a bracketed default.It is a hint, never a decision — nothing is created and no name is claimed. Every failure resolves to no suggestion, which is an empty field, which is the prior behavior:
admin,new,api, …)validateSlugThat tolerance is the whole argument for prefill over auto-provisioning: a feature allowed to produce no answer never has to produce a wrong one. Auto-provisioning would need a suffix policy, a silent fallback ladder, and a decision on free-slot accounting — and would claim a slug on every signup whether or not the account was ever used.
The availability check runs against a server-derived candidate only, never a client-supplied string, so this can't be used as a name-enumeration oracle: you learn at most whether your own login is taken, which is no more than trying to create it would tell you.
User-visible changes
Both affect only accounts that are members of
default— one account in production:defaultrather than another workspace (role order decides; nothing is skipped).defaultname it like any other workspace.Verification
pnpm test— 3140/3140 (242 files)pnpm typecheck— clean/deviceand/account/workspaces/newrender in the dev server with no console errorsZero residual references to
isCommunalWorkspace,COMMUNAL_WORKSPACE, or@uploads/workspaceanywhere inapps/orpackages/.CLI changes ship as a minor via changeset.
Summary by CodeRabbit
uploads config initno longer overrides token-defined workspaces unless one is explicitly specified.defaultworkspace now follows standard workspace behavior for membership limits, metadata, navigation, and invitations.