feat(api,web): lower free-tier storage to 250 MB + edit workspace limits in the admin panel#280
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-api | bfad9fe | Commit Preview URL Branch Preview URL |
Jul 20 2026, 12:41 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-auth | b2bedc5 | Commit Preview URL Branch Preview URL |
Jul 20 2026, 12:26 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-web | bfad9fe | Commit Preview URL Branch Preview URL |
Jul 20 2026, 12:41 PM |
|
Important Review skippedAuto reviews are disabled on this repository. 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:
📝 WalkthroughWalkthroughAdds admin API and UI support for viewing and editing four workspace budget limits, including unlimited values, usage display, validation, persistence, and tests. Also lowers the self-serve storage default from 1 GB to 250 MB. ChangesWorkspace limits editing
Self-serve storage default
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant AdminUI
participant Registry
participant Usage
Admin->>AdminUI: Open workspace limits
AdminUI->>Registry: Load workspace record
AdminUI->>Usage: Fetch current usage
Usage-->>AdminUI: Usage or null
AdminUI-->>Admin: Render limits and usage
Admin->>AdminUI: Save limit patch
AdminUI->>Registry: Persist updated workspace record
AdminUI-->>Admin: Return refreshed limits
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/pages/admin/index.astro (1)
283-317: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winLoad limits independently of organization data.
Workspaces without an organization return at Lines 283-287 before fetching limits, and a members/invites failure also prevents the limits request. Those workspaces therefore cannot administer limits despite the API supporting them. Load limits before the organization early return and keep its retry state independent.
🤖 Prompt for 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. In `@apps/web/src/pages/admin/index.astro` around lines 283 - 317, Update the workspace-loading flow around the organization guard and limits request so limits are fetched and rendered for every workspace, including those without an organization and when members/invites loading fails. Move the limits-loading logic out of the organization-dependent try path, preserve the existing renderLimitsForm and failure message, and keep its retry/loading state independent from members and invites.
🤖 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/routes/admin-ui.ts`:
- Around line 297-308: The PATCH /workspaces/:name/limits route must apply the
required workspace-keyed writeRateLimit middleware before performing the KV
mutation. Update the route definition around the limits patch handler to include
writeRateLimit using the workspace name key, while preserving the existing
validation, record update, and response behavior.
- Around line 299-307: Update the workspace mutation flow containing
loadEditableWorkspace and REGISTRY.put so concurrent PATCH requests cannot
overwrite each other’s limit changes. Serialize full-record mutations for each
workspace, or use the registry’s transactional/versioned update mechanism, while
preserving the existing patch validation and field deletion/update behavior.
- Line 300: Update the request-body parsing before validateLimitsPatch in the
admin route to distinguish malformed JSON from an intentionally empty object.
Catch parse failures and throw a structured ValidationError before validation or
persistence, while preserving normal handling for successfully parsed bodies.
---
Outside diff comments:
In `@apps/web/src/pages/admin/index.astro`:
- Around line 283-317: Update the workspace-loading flow around the organization
guard and limits request so limits are fetched and rendered for every workspace,
including those without an organization and when members/invites loading fails.
Move the limits-loading logic out of the organization-dependent try path,
preserve the existing renderLimitsForm and failure message, and keep its
retry/loading state independent from members and invites.
🪄 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: 38e34c75-cbd3-43ac-9512-56849951cccd
📒 Files selected for processing (11)
apps/api/src/routes/admin-ui.test.tsapps/api/src/routes/admin-ui.tsapps/api/src/self-serve-defaults.test.tsapps/api/src/self-serve-defaults.tsapps/api/src/workspace-limits.test.tsapps/api/src/workspace-limits.tsapps/web/src/pages/admin/index.astroapps/web/src/styles/admin-workspaces.cssdocs/superpowers/plans/2026-07-20-admin-workspace-limits-editing.mddocs/superpowers/specs/2026-07-20-admin-workspace-limits-editing-design.mdpackages/errors/src/codes.ts
| .patch("/workspaces/:name/limits", async (c) => { | ||
| const name = c.req.param("name"); | ||
| const record = await loadEditableWorkspace(c.env, name); | ||
| const patch = validateLimitsPatch(await c.req.json().catch(() => ({}))); | ||
| for (const field of LIMIT_FIELDS) { | ||
| if (!(field in patch)) continue; | ||
| const value = patch[field]; | ||
| if (value === null) delete record[field]; | ||
| else record[field] = value; | ||
| } | ||
| await c.env.REGISTRY.put(`ws:${name}`, JSON.stringify(record)); | ||
| return c.json(await limitsResponse(c.env, name, record)); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Rate-limit this workspace mutation.
This route writes KV without the required workspace-keyed writeRateLimit middleware, allowing an authenticated admin to issue unbounded writes.
As per coding guidelines, “Mutating routes must use the writeRateLimit middleware keyed by workspace; the middleware may no-op only when the WRITE_LIMITER binding is absent.”
🤖 Prompt for 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.
In `@apps/api/src/routes/admin-ui.ts` around lines 297 - 308, The PATCH
/workspaces/:name/limits route must apply the required workspace-keyed
writeRateLimit middleware before performing the KV mutation. Update the route
definition around the limits patch handler to include writeRateLimit using the
workspace name key, while preserving the existing validation, record update, and
response behavior.
Source: Coding guidelines
| const record = await loadEditableWorkspace(c.env, name); | ||
| const patch = validateLimitsPatch(await c.req.json().catch(() => ({}))); | ||
| for (const field of LIMIT_FIELDS) { | ||
| if (!(field in patch)) continue; | ||
| const value = patch[field]; | ||
| if (value === null) delete record[field]; | ||
| else record[field] = value; | ||
| } | ||
| await c.env.REGISTRY.put(`ws:${name}`, JSON.stringify(record)); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Prevent concurrent full-record writes from losing updates.
Two PATCH requests can both read the old record and each write a full replacement; the later write silently removes the other request’s limit changes. Serialize workspace-record mutations or use a transactional/versioned persistence mechanism.
🤖 Prompt for 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.
In `@apps/api/src/routes/admin-ui.ts` around lines 299 - 307, Update the workspace
mutation flow containing loadEditableWorkspace and REGISTRY.put so concurrent
PATCH requests cannot overwrite each other’s limit changes. Serialize
full-record mutations for each workspace, or use the registry’s
transactional/versioned update mechanism, while preserving the existing patch
validation and field deletion/update behavior.
| .patch("/workspaces/:name/limits", async (c) => { | ||
| const name = c.req.param("name"); | ||
| const record = await loadEditableWorkspace(c.env, name); | ||
| const patch = validateLimitsPatch(await c.req.json().catch(() => ({}))); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject malformed JSON instead of persisting a no-op.
Line 300 converts a JSON parse failure to {}, so invalid request bodies return 200 and still write the record. Throw a structured ValidationError before calling validateLimitsPatch.
Proposed fix
- const patch = validateLimitsPatch(await c.req.json().catch(() => ({})));
+ const body = await c.req.json().catch(() => {
+ throw new ValidationError("limits body must be a JSON object", {
+ code: "invalid_limit",
+ });
+ });
+ const patch = validateLimitsPatch(body);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const patch = validateLimitsPatch(await c.req.json().catch(() => ({}))); | |
| const body = await c.req.json().catch(() => { | |
| throw new ValidationError("limits body must be a JSON object", { | |
| code: "invalid_limit", | |
| }); | |
| }); | |
| const patch = validateLimitsPatch(body); |
🤖 Prompt for 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.
In `@apps/api/src/routes/admin-ui.ts` at line 300, Update the request-body parsing
before validateLimitsPatch in the admin route to distinguish malformed JSON from
an intentionally empty object. Catch parse failures and throw a structured
ValidationError before validation or persistence, while preserving normal
handling for successfully parsed bodies.
…independent of org
What & why
Two related changes to free-tier limits, prompted by the free-account storage cap being too generous for a screenshot-first host.
1. Lower the free-tier storage default: 1 GB → 250 MB (
self-serve-defaults.ts)New self-serve workspaces start at 250 MB instead of 1 GB. Uploads are WebP-converted by default, so 250 MB of stored assets holds far more screenshots than the raw byte figure suggests. Only affects newly created workspaces — existing ones keep their stamped limit. The monthly upload count (3000) and per-file caps (25 MB / 8 MB video) are unchanged.
2. Edit per-workspace budget limits from the admin panel
Previously the only way to change a workspace's limits was running
scripts/set-workspace-limits.mjsfrom an operator's laptop (direct KV mutation via wrangler). This adds an in-app path for global admins:validateLimitsPatch(workspace-limits.ts) — a pure, unit-tested validator: each of the four budget fields is a positive integer (set) ornull(clear → unlimited); unknown keys ignored; bad values → 400invalid_limit.GET+PATCH /admin-ui/workspaces/:name/limits— session-cookie +requireAdminUsergated (same gate as the existing Workspaces panel). PATCH raw-reads the record, mutates only the four budget fields (set number / delete for unlimited), and writes the whole record back so key-policy, org linkage, and every other field survive. Unknown / soft-deleted / tombstoned workspaces → 404. Response includes current usage (null-tolerant).cacheTtlcaveat).Editable fields:
maxStorageBytes,maxUploadsPerPeriod,maxUploadBytes,maxVideoUploadBytes. Retention/key-policy editing, a token-gated/admintwin, and audit logging are intentionally out of scope (the CLI script still covers those).Testing
workspace-limits(7),admin-ui(28 = 19 existing + 9 new),self-serve-defaults(3),routes-budget(6) — 44/44.astro check && tsc): 0 errors / 0 warnings.Browser E2E deferred — needs the full 3-server local stack + a seeded admin session (same precedent as #268/#278). Logic is covered by the tests above plus a clean whole-branch review (auth gating, whole-record write-back, no unescaped
innerHTML, contract match, unlimited round-trip, non-retroactive 250 MB change all verified).Known minor follow-ups (non-blocking)
formatBytes(0)renders "0 GiB" rather than "0 MB" on a zero-usage workspace (cosmetic).catcharound the usage read is currently unreachable (getWorkspaceUsagereturns an empty row rather than throwing).Docs
Design + implementation plan under
docs/superpowers/{specs,plans}/2026-07-20-admin-workspace-limits-editing*.Summary by CodeRabbit
New Features
Bug Fixes
Configuration