Skip to content

feat(admin): Storage tab with a close-archive-breaker button - #2135

Merged
simple-agent-manager[bot] merged 3 commits into
mainfrom
sam/admin-archive-breaker-button
Sep 23, 2026
Merged

simple-agent-manager[bot] merged 3 commits into
mainfrom
sam/admin-archive-breaker-button

Conversation

@simple-agent-manager

@simple-agent-manager simple-agent-manager Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds Admin → Storage (/admin/storage): a superadmin-only tab that lists every project's ProjectData archive circuit breaker (state, reason, opened/updated, storage usage) plus storage telemetry, with a Close breaker button that works from a phone.
  • Until now the only way to close a tripped breaker was a raw superadmin API call from a desktop browser. Nothing auto-closes a breaker, so after the archive fix in fix(api): resume verified ProjectData archive copies #2133 shipped, SAM's own project drain stayed stopped (breaker open since 2026-09-21, root DO at 10.10 GB vs 10.0 GB soft limit).
  • New API: GET /api/admin/project-data/storage/archive-sharding/circuit-breakers (D1-only, 1 round trip, bounded by the existing PROJECT_DATA_ARCHIVE_ROLLOUT_LIST_LIMIT_* config, malformed rows skipped and counted per rule 50). Lives in a new file apps/api/src/routes/admin/project-data-archive-breakers.ts because project-data-storage.ts is already over the 500-line ceiling; parseArchiveRolloutLimit moved there and is reused.
  • The button calls the existing POST .../:projectId/archive-sharding/circuit-breaker with state: "closed" and an operator-entered reason (default "Closed from admin UI"), then invalidates the list. Server gate: adminRoutes.use('/*', requireAuth(), requireApproved(), requireSuperadmin()) (apps/api/src/routes/admin.ts:27); client gate: SuperadminRoute + Admin redirect, tab only visible to superadmins.
  • Requested by Raphaël on 2026-09-23: admin operational actions must be buttons in the admin UI, gated to admins, usable on mobile. Idea 01M35R2B08V0BBDK80TPC4PC5P.

Validation

  • pnpm lint — eslint on all changed files: clean
  • pnpm typechecktsc --noEmit for apps/api and apps/web: clean
  • pnpm testapps/api admin-project-data-archive-breakers.test.ts + admin-project-data-archive-sharding.test.ts: 14/14; apps/web AdminStorage.test.tsx: 4/4, plus app-routes, AdminUsers, AdminTrials: 16/16. Full suites run in CI.
  • Additional validation run — Playwright admin-storage-audit.spec.ts at iPhone SE 375x667, iPhone 14 390x844, Desktop 1280x800: 12/12, assertNoOverflow on every capture
  • No sweep/cron/alarm candidate selection changed

Staging Verification (REQUIRED for all code changes — merge-blocking)

  • Staging deployment greenDeploy Staging run 35910276585 succeeded for this branch (2026-09-23 19:43Z)
  • Live app verified via Playwright — token-login as SAM_PLAYWRIGHT_PRIMARY_USER (superadmin) against api.sammy.party, then app.sammy.party/admin/storage at 375x667 and 1280x800
  • Existing workflows confirmed working — Admin tab strip, sidebar and Storage tab render with live data (one breaker, storage telemetry for the staging projects); zero page errors and zero console errors in both viewports; no horizontal overflow
  • New feature/fix verified on staging — see evidence below
  • Infrastructure verification completed — N/A: no infra changes
  • Mobile and desktop verification notes added for UI changes (see UI Screenshot Evidence)

Staging Verification Evidence

Real end-to-end exercise of the feature on the disposable staging fixture project pr2008-validation-fixture (01M1JPTX00FZNR1GNRXNXMS594), whose breaker was already closed:

  1. GET /api/admin/project-data/storage/archive-sharding/circuit-breakers (new route) returned the one staging breaker with project name and repository joined, skippedRows: 0.
  2. Opened the fixture's breaker through the existing superadmin POST (state: "open", reason "staging validation: opened to test the admin Close breaker button (PR feat(admin): Storage tab with a close-archive-breaker button #2135)").
  3. Loaded /admin/storage on a 375x667 mobile context: the card rendered with the Open badge, the reason, opened/updated timestamps and storage usage.
  4. Tapped Close breaker, edited the reason in the dialog, submitted.
  5. The dialog closed, the success toast appeared, the card switched to Closed, the button disappeared (0 close buttons on the card).
  6. Read back through the GET route: state: "closed", openedAt: null, reason equal to the text entered in the dialog, updatedAt advanced. The fixture is left in its original closed state.
  7. pageerror/console.error count: 0; document.documentElement.scrollWidth > innerWidth: false.

Local captures of steps 3 to 5 (staging-e2e-1-open.png, -2-dialog.png, -3-closed.png) were opened and reviewed; the only visual nit was an unrounded estimated_days_to_limit (fixed in the follow-up commit).

UI Compliance Checklist (Required for UI changes)

  • Mobile-first layout verified (cards stack; button is full width under sm)
  • Accessibility checks completed (labelled dialog via aria-labelledby, <label>-wrapped reason input, role="alert" errors, aria-modal, Escape closes; autoFocus avoided per jsx-a11y)
  • Shared UI components used (Card, Button, Dialog, Input, Alert, Spinner from @simple-agent-manager/ui; formatBytes reused from deployment-card-format)
  • Playwright visual audit run locally — normal (open/frozen/closed/deleted-project), long text + special characters + <script> tag, empty, error (500), plus the click-through close flow, at 375x667 and 1280x800 (and 390x844); no horizontal overflow
  • Desktop and mobile screenshots for every changed UI surface are posted in a PR comment and linked below
  • Agent reviewed the posted screenshots for quality control

UI Screenshot Evidence

Screenshots were taken with Playwright (apps/web/tests/playwright/admin-storage-audit.spec.ts).

Surface: Admin → Storage tab (breaker list, telemetry list, close-breaker dialog)

  • Desktop evidence: feat(admin): Storage tab with a close-archive-breaker button #2135 (comment)
  • Mobile evidence: feat(admin): Storage tab with a close-archive-breaker button #2135 (comment)
  • Mock/stress data used: four breakers (open with a 250-char reason containing an em dash, <script>alert(1)</script> and an emoji, a frozen breaker, an open breaker whose project was deleted so name/repository are null, and a closed breaker), a 90-char project name, three telemetry rows (101% degraded, 42% warning, a single-character name at 0% with a last_error), an empty response for both lists, a 500 error for both lists, and the stateful close flow (dialog, reason edit, POST body assertion, refreshed list).
  • Screenshot quality review: every capture was opened and reviewed for layout, overflow, clipping, readability and responsive behaviour at 375x667, 390x844 and 1280x800. No issues found: long names and reasons wrap, the code-styled repository breaks on all, the button is full width on mobile and inline on desktop, the dialog fits within the mobile viewport with stacked buttons, and after closing the badge switches to "Closed" with the button removed. The first audit run captured only the onboarding wizard (rule 62); the spec now dismisses the wizard before boot and the captures were re-reviewed.

End-to-End Verification (Required for multi-component changes)

  • Data flow traced with code path citations
  • Capability test exercises the complete happy path across boundaries
  • Spec/doc assumptions verified against code
  • Gaps documented below

Data Flow Trace

AdminStorage.tsx BreakerCard "Close breaker" → Dialog reason form → useMutationcloseAdminProjectDataArchiveCircuitBreaker() (apps/web/src/lib/api/admin-project-data-storage.ts) → POST /api/admin/project-data/storage/:projectId/archive-sharding/circuit-breaker behind requireSuperadmin() (apps/api/src/routes/admin.ts:27) → setProjectDataArchiveCircuitBreaker() (apps/api/src/services/project-data-archive-rollout-controls.ts) D1 upsert state='closed', opened_at=NULLinvalidateQueries(adminProjectDataStorageQueryKeys.all)GET .../archive-sharding/circuit-breakers (apps/api/src/routes/admin/project-data-archive-breakers.ts) → card re-renders as Closed. The scheduled sweep's selector queries require state='closed', so the drain resumes on the next hourly pass.

Capability coverage: the API test closes via the real POST route and reads the change back through the real GET route against SQLite; the Playwright close-flow test drives the real button, dialog and form and asserts the exact POST path/body and the refreshed list; the vitest page test covers the mutation, error path and refetch.

Untested Gaps

The live production breaker close itself is performed by Raphaël after deploy (agents have no production admin login).

Post-Mortem (Required for bug fix PRs)

N/A: feature PR, not a bug fix.

Specialist Review Evidence (Required for agent-authored PRs)

  • All local reviewers completed and findings addressed before merge — none run; waived, see table
  • If any reviewer did NOT complete: needs-human-review label added and merge deferred to human
Reviewer Status Outcome
specialist subagents (security-auditor, ui-ux-specialist, architecture-reviewer, task-completion-validator) DEFERRED Not run. Raphaël explicitly waived specialist review for this PR on 2026-09-23 ("get the PR green as fast as possible and ship it", "I'm giving you permission to bypass our merge protections"); deferred to his own review of the PR. See Exceptions.

CodeRabbit Review Evidence (Required for agent-authored PRs)

  • coderabbit-review label applied
  • All CodeRabbit findings implemented or explicitly reviewed and closed/resolved — none requested
  • Incremental CodeRabbit review completed after final pushed fixes, or no fixes were needed
  • Latest CodeRabbit review has no unresolved feedback

CodeRabbit Notes

Waived by Raphaël for this PR (same shape as #2119/#2120/#2121/#2133); CodeRabbit has not produced a review on any SAM PR since 2026-09-14.

Exceptions (If any)

  • Scope: specialist subagent reviews and CodeRabbit are skipped; staging verification is attempted in parallel with CI and recorded above, but Raphaël authorized merging on green CI without it.
  • Rationale: Raphaël (session c0eda1ee, 2026-09-23) directed "get the PR green as fast as possible and ship it" and explicitly granted permission to bypass merge protections because the change is a small admin button; SAM's archive drain is stopped until the breaker is closed.
  • Expiration: this PR only.

Agent Preflight (Required)

  • Preflight completed before code changes

Classification

  • external-api-change
  • cross-component-change
  • business-logic-change
  • public-surface-change
  • docs-sync-change
  • security-sensitive-change
  • ui-change
  • infra-change

External References

N/A: no external service or third-party API involved; internal routes, schemas and UI primitives were read directly (apps/api/src/routes/admin/project-data-storage.ts, apps/api/src/schemas/admin.ts, packages/ui/src/components/*, apps/web/tests/playwright/audit-helpers.ts).

Codebase Impact Analysis

  • packages/shared/src/types/admin.ts (+ index): breaker/telemetry response types.
  • apps/api/src/routes/admin/project-data-archive-breakers.ts (new) and apps/api/src/routes/admin/project-data-storage.ts (mounts it, parseArchiveRolloutLimit moved): new bounded D1-only GET; existing POST untouched. I/O budget: GET list = 1 D1 round trip.
  • apps/web/src/pages/AdminStorage.tsx (new, lazy-loaded), apps/web/src/pages/Admin.tsx (tab), apps/web/src/App.tsx (route), apps/web/src/lib/api/admin-project-data-storage.ts + index.ts, apps/web/src/lib/query-options/admin-project-data-storage.ts + index.ts: TanStack Query surface, stale-while-revalidate (spinner only with no data, refetch never unmounts content).
  • Tests: apps/api/tests/unit/routes/admin-project-data-archive-breakers.test.ts, apps/web/tests/unit/AdminStorage.test.tsx, apps/web/tests/playwright/admin-storage-audit.spec.ts.

Documentation & Specs

apps/www/src/content/docs/docs/guides/self-hosting.mdx: new "Storage and archive circuit breakers" subsection under First Login & Admin Access describing the tab and the route it calls. No spec files touched.

Constitution & Risk Check

Principle XI: no hardcoded URLs/timeouts/limits; the list limit resolves through the existing PROJECT_DATA_ARCHIVE_ROLLOUT_LIST_LIMIT_DEFAULT/MAX env config; the client uses API_URL. Risk: the close action is a superadmin mutation, gated server-side by the existing admin middleware and confirmed via a dialog with a required reason; it never thaws frozen migrations (documented in the dialog). Rule 50 fault isolation on the new list read; rule 18 respected by placing the route in a new file.

raphaeltm and others added 2 commits September 23, 2026 19:22
Add Admin → Storage (/admin/storage): lists per-project ProjectData
archive circuit breakers (new superadmin GET
/api/admin/project-data/storage/archive-sharding/circuit-breakers) and
storage telemetry, with a phone-usable Close breaker control that calls
the existing POST .../:projectId/archive-sharding/circuit-breaker with
state=closed and an operator reason.

Until now the only way to close a tripped breaker was a superadmin API
call from a desktop browser; nothing auto-closes it, so a stopped
archive drain stayed stopped after the underlying fix shipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e audit

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • coderabbit-review

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: Repository: raphaeltm/simple-agent-manager/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: db2f7c11-1615-4ad2-b39e-55dd1e53cbf7

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@simple-agent-manager

Copy link
Copy Markdown
Contributor Author

Playwright Screenshot Evidence — Admin → Storage tab

Captured by apps/web/tests/playwright/admin-storage-audit.spec.ts (12/12 passing) at iPhone SE 375x667, iPhone 14 390x844 and Desktop 1280x800, dark theme, assertNoOverflow asserted on every capture. Files live in the gitignored .codex/tmp/playwright-screenshots/ of the agent worktree:

Scenario Mobile (375x667) Desktop (1280x800)
Breaker list: open (250-char reason with em dash, <script>alert(1)</script>, emoji), frozen, open with deleted project (null name/repo), closed; three telemetry rows incl. 101% degraded and a single-character name admin-storage-breakers-375x667.png admin-storage-breakers-1280x800.png
Empty (no breakers, no telemetry) admin-storage-empty-375x667.png admin-storage-empty-1280x800.png
Error (both endpoints 500) admin-storage-error-375x667.png admin-storage-error-1280x800.png
Close dialog open (reason prefilled) admin-storage-close-dialog-375x667.png admin-storage-close-dialog-1280x800.png
After close (toast, badge → Closed, button gone) admin-storage-after-close-375x667.png admin-storage-after-close-1280x800.png

Quality review (every image opened)

  • Mobile 375px: page header, tab strip and cards fit the viewport; the 90-char project name and the 250-char reason wrap cleanly; the monospace repository line breaks on all; the <script> text renders as literal text; Close breaker is full width; the dialog fits inside the viewport with stacked full-width buttons (Close breaker above Cancel); after closing, the success toast appears at the top, the card shows Closed and its button is removed while the frozen project's button remains.
  • Desktop 1280px: cards use a label/value grid, badges sit inline with the title, the button is inline-width, the dialog is centred at max-w-md.
  • Empty / error: both sections show their own message ("No archive circuit breakers recorded." / "No storage telemetry recorded yet.") or a role="alert" with the API message; the page chrome renders around them (liveness).
  • No horizontal overflow, clipping or off-screen content in any capture.
  • Note: the first audit run captured only the first-run onboarding wizard covering the page (rule 62). The spec now marks the wizard dismissed before boot; all captures above are from the corrected run and differ per scenario.

@codspeed

codspeed Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing sam/admin-archive-breaker-button (6d489b0) with main (8b53cb4)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (f5ff1e6) during the generation of this report, so 8b53cb4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

…l-mock crash task

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@simple-agent-manager
simple-agent-manager Bot marked this pull request as ready for review September 23, 2026 20:00
@sonarqubecloud

Copy link
Copy Markdown

@simple-agent-manager
simple-agent-manager Bot merged commit 2a06187 into main Sep 23, 2026
31 checks passed
@simple-agent-manager
simple-agent-manager Bot deleted the sam/admin-archive-breaker-button branch September 23, 2026 20:20
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