Skip to content

refactor: retire the communal workspace concept#522

Merged
Zach Dunn (zachdunn) merged 3 commits into
mainfrom
chore/retire-communal-workspace
Jul 24, 2026
Merged

refactor: retire the communal workspace concept#522
Zach Dunn (zachdunn) merged 3 commits into
mainfrom
chore/retire-communal-workspace

Conversation

@zachdunn

@zachdunn Zach Dunn (zachdunn) commented Jul 24, 2026

Copy link
Copy Markdown
Member

Retires the "communal workspace" concept. default becomes an ordinary workspace — not renamed, not deleted, objects and public URLs untouched.

Closes #505. Closes #506.

Why this is safe

storage.uploads.sh is an R2 custom domain, not a Worker route. Existing storage.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/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. These had to land together — with only the server half removed, mapMyWorkspace would have read the missing field as absent and kept inferring the old behavior from the name 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 — verified, and pinned by a new test. A plan stamped on it now applies, which is the behavior change; that's pinned too.
  • The invite-email special case ("You've been given access to uploads.sh").
  • The Content-identical re-uploads inherit the earlier object's derived metadata (server-side) #479 content-hash gate. See below.

Kept

default stays in RESERVED_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 public storage.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 default differently, 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 default was 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 default uses).

Also included

uploads config init with no flags no longer seeds UPLOADS_WORKSPACE=default. That entry outranked the workspace encoded in the token, pinning every later uploads 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, 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 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 resolves to no suggestion, which is an empty field, which is the prior behavior:

Case Result
No linked GitHub account no suggestion
Login lookup fails or is rate-limited no suggestion
1-char login (legal on GitHub, below our 2-char floor) no suggestion
Reserved name (admin, new, api, …) no suggestion
Blocklisted by validateSlug no suggestion
Slug already taken no suggestion

That 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:

  1. The app may now open default rather than another workspace (role order decides; nothing is skipped).
  2. Invites to default name it like any other workspace.

Verification

  • pnpm test3140/3140 (242 files)
  • pnpm typecheck — clean
  • Changed files format-clean
  • /device and /account/workspaces/new render in the dev server with no console errors

Zero residual references to isCommunalWorkspace, COMMUNAL_WORKSPACE, or @uploads/workspace anywhere in apps/ or packages/.

CLI changes ship as a minor via changeset.

Summary by CodeRabbit

  • New Features
    • New workspaces are suggested from the user’s linked GitHub login during CLI and web setup.
    • Users can accept or edit the suggested workspace name.
  • Bug Fixes
    • uploads config init no longer overrides token-defined workspaces unless one is explicitly specified.
    • The default workspace now follows standard workspace behavior for membership limits, metadata, navigation, and invitations.
  • Documentation
    • Updated token-minting guidance to clarify workspace selection behavior.

`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.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@zachdunn

Copy link
Copy Markdown
Member Author

CodeRabbit (@coderabbitai) review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (2)
  • coderabbit:review
  • review
🚫 Excluded labels (none allowed) (1)
  • wip

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81089144-ded6-4140-874d-df995cc5ce4c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The 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.

Changes

Workspace model and communal-workspace retirement

Layer / File(s) Summary
Remove communal workspace behavior
apps/api/src/{content-hash.ts,slug-policy.ts}, apps/api/src/routes/*, apps/api/test/content-hash.test.ts, packages/workspace/src/index.ts
default is handled through normal workspace lookup, inheritance, billing, membership, and API response paths.
Align client and email workspace shapes
apps/web/src/lib/*, packages/email/src/*, apps/*/package.json, packages/email/package.json, docs/admin-tokens.md
Client workspace mappings, default selection, invite messaging, and package dependencies no longer rely on communal-workspace behavior.

Workspace suggestions and onboarding

Layer / File(s) Summary
Generate and expose workspace suggestions
apps/api/src/workspace-suggestion.*, apps/api/src/routes/tokens.*, apps/api/src/slug-policy.ts
GitHub logins are normalized into available, valid workspace suggestions and returned for accounts without workspaces.
Apply suggestions during web and CLI onboarding
apps/web/src/lib/api-client.*, apps/web/src/pages/{account/workspaces/new.astro,device.astro}, packages/uploads/src/{client.ts,commands/login.ts}, packages/uploads/test/commands-login.test.ts
Web forms prefill suggestions without overwriting edits, while interactive CLI login displays and accepts or overrides bracketed defaults.
Preserve token workspace precedence
packages/uploads/src/commands/config.ts, packages/uploads/test/config-init.test.ts, .changeset/quiet-lions-retire.md
Bare config initialization seeds only the API URL; explicit workspace configuration remains supported and documented.

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
Loading

Possibly related PRs

Suggested labels: coderabbit:review

Poem

I’m a rabbit with a workspace name,
GitHub hops turn letters into fame.
No forced default in config tonight,
Bracketed choices make login right.
I thump, accept, or type anew—
A tidy burrow waits for you!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers retiring communal special cases, but it only suggests GitHub-based names and does not auto-provision a workspace as required by #506. Implement first-signup workspace provisioning from GitHub usernames, including conflict handling, or revise the linked issue to match the suggestion-only behavior.
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: retiring the communal workspace concept.
Out of Scope Changes check ✅ Passed The changes stay focused on retiring communal-workspace behavior and adding GitHub-based workspace suggestions, with no unrelated feature work visible.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/retire-communal-workspace

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between bf70ff9 and 247a706.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • .changeset/quiet-lions-retire.md
  • apps/api/package.json
  • apps/api/src/content-hash.ts
  • apps/api/src/routes/internal-billing.test.ts
  • apps/api/src/routes/internal-billing.ts
  • apps/api/src/routes/me.test.ts
  • apps/api/src/routes/me.ts
  • apps/api/src/routes/tokens.test.ts
  • apps/api/src/routes/tokens.ts
  • apps/api/src/slug-policy.ts
  • apps/api/src/workspace-suggestion.test.ts
  • apps/api/src/workspace-suggestion.ts
  • apps/api/test/content-hash.test.ts
  • apps/web/package.json
  • apps/web/src/lib/api-client.test.ts
  • apps/web/src/lib/api-client.ts
  • apps/web/src/lib/workspace-file-row.test.ts
  • apps/web/src/lib/workspaces-nav.test.ts
  • apps/web/src/lib/workspaces-nav.ts
  • apps/web/src/pages/account/workspaces/new.astro
  • apps/web/src/pages/device.astro
  • docs/admin-tokens.md
  • packages/email/package.json
  • packages/email/src/invites.ts
  • packages/email/test/invites.test.ts
  • packages/uploads/src/client.ts
  • packages/uploads/src/commands/config.ts
  • packages/uploads/src/commands/login.ts
  • packages/uploads/test/commands-login.test.ts
  • packages/uploads/test/config-init.test.ts
  • packages/workspace/README.md
  • packages/workspace/package.json
  • packages/workspace/src/communal.ts
  • packages/workspace/src/index.ts
  • packages/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

Comment thread apps/api/src/workspace-suggestion.ts Outdated
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.
@zachdunn
Zach Dunn (zachdunn) merged commit ffe9ea7 into main Jul 24, 2026
3 checks passed
@zachdunn
Zach Dunn (zachdunn) deleted the chore/retire-communal-workspace branch July 24, 2026 20:28
Zach Dunn (zachdunn) added a commit that referenced this pull request Jul 24, 2026
…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.
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.

Auto-provision a workspace for new users from their GitHub username Decide whether the cloud-hosted product should still have a communal workspace

1 participant