Skip to content

Make the admin counts say what they counted, and drop the types nothing reads - #780

Merged
InfinityBowman merged 3 commits into
mainfrom
fix/admin-honest-numbers
Sep 12, 2026
Merged

Make the admin counts say what they counted, and drop the types nothing reads#780
InfinityBowman merged 3 commits into
mainfrom
fix/admin-honest-numbers

Conversation

@InfinityBowman

Copy link
Copy Markdown
Owner

Follow-up to #768. Two themes: finish the type cleanup that PR started, and fix three admin numbers that were reporting something other than their label.

Cleanup

admin-projects.server.ts - deletes four interfaces (ProjectMember, ProjectFile, ProjectInvitation, ProjectData) that lost their last callers in #768. They declared fields the server never selects, so leaving them invites the next caller to trust a shape that does not exist.

Stripe Tools had the same antipattern #768 cleared everywhere else: four hand-written Stripe shapes and four data as X casts. Inferring from the server functions instead turned up two bugs the casts were hiding:

  • The subscription period line read sub.currentPeriodStart / currentPeriodEnd, which Stripe moved onto each item. Every subscription row rendered an empty date range.
  • invoice.status and customer.currency are nullable in Stripe's own types; the hand-written ones said otherwise.

The customer lookup's found/not-found union now narrows instead of being flattened into one optional-everything shape.

Numbers

Event Ledger stats were page arithmetic. total was entries.length computed after .limit(50), so "Total" was always the row limit and the status cards described only the newest page. Now a GROUP BY status count over every row matching the same filter, with a footer saying how much of the ledger is on screen. Against the local DB the page now reads Total 183 / Failed 183 with 50 rows in the table; before, both cards read 50.

"Active Sessions" counted expired sessions. It was count() over the whole session table. Now filters expiresAt > now.

hasMore was computed in three places and read in none. getAdminSubscriptionStats scans Stripe 100 subscriptions at a time, so a platform with more than 100 in any status silently showed exactly 100. The flag is now truncated, carries the scan limit, and the Subscriptions panel says the counts are floors when it trips. The per-customer flags show up as Subscriptions (20+) / Recent Invoices (10+).

Also drops stats.byType from the ledger response, which nothing read.

Verification

Three new server tests. I checked they are not vacuous by reverting both fixes and re-running - all three fail against the old code (expected 2 to be 5, expected 1 to be 2, expected 2 to be 1) and pass against the new.

The session fix is also verified end to end: inserted an expired session row into the local D1, reloaded the dashboard, confirmed it still read 2 of 3 rows, then removed the probe row.

Typecheck, lint and 311 web tests pass.

https://claude.ai/code/session_01LqxkXwhjRDsJ1N9cBYpU1n

These four interfaces moved into admin-projects.server.ts when the
component-side types.ts went away, then lost their last callers when the
pages switched to inferred types. They declared fields the server never
selects (userDisplayName, creatorDisplayName), so leaving them around
invites the next caller to trust a shape that does not exist.

Claude-Session: https://claude.ai/code/session_01LqxkXwhjRDsJ1N9cBYpU1n
The page declared its own StripeCustomer, StripeSubscription,
StripeInvoice and StripePaymentMethod and cast every response into them.
Inferring from the server functions instead turned up two things the
casts were hiding:

- The subscription period line read sub.currentPeriodStart and
  currentPeriodEnd, which Stripe moved onto each item. Every row
  rendered an empty date range.
- invoice.status and customer.currency are nullable in Stripe's own
  types; the hand-written ones said otherwise.

The lookup's found/not-found union now narrows instead of being flattened
into one optional-everything shape, so the not-found branch no longer
pretends to carry a customer.

Loading invoices or subscriptions keeps the hasMore flag the server
already returned, so a capped list reads "Subscriptions (20+)" rather
than claiming the customer has exactly twenty.

Claude-Session: https://claude.ai/code/session_01LqxkXwhjRDsJ1N9cBYpU1n
Three admin numbers were reporting something other than what their
labels claimed.

The Event Ledger's stat row was computed from the page of entries the
query had just capped, so "Total" was always the row limit and the
status cards only described the newest page. It now counts with a GROUP
BY over every row matching the same filter, and the panel footer says
how much of the ledger is on screen.

"Active Sessions" on the dashboard counted every row in the session
table, expired ones included. It now filters on expiresAt.

getAdminSubscriptionStats scans Stripe 100 subscriptions at a time and
computed a hasMore flag that nothing read, so a platform with more than
100 in any status silently showed 100. The flag is now named truncated,
carries the scan limit, and the Subscriptions panel says the counts are
floors when it trips.

Also drops stats.byType from the ledger response, which nothing read.

Claude-Session: https://claude.ai/code/session_01LqxkXwhjRDsJ1N9cBYpU1n
@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 8 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: f1925672-93f6-43f9-ad87-37292761e48f

📥 Commits

Reviewing files that changed from the base of the PR and between 3eb3f19 and 0116fa3.

📒 Files selected for processing (10)
  • packages/web/src/components/admin/AnalyticsSection.tsx
  • packages/web/src/routes/_app/_protected/admin/billing.ledger.tsx
  • packages/web/src/routes/_app/_protected/admin/billing.stripe-tools.tsx
  • packages/web/src/server/functions/__tests__/admin-billing-observability.server.test.ts
  • packages/web/src/server/functions/__tests__/admin-stats.server.test.ts
  • packages/web/src/server/functions/__tests__/admin-users.server.test.ts
  • packages/web/src/server/functions/admin-billing.server.ts
  • packages/web/src/server/functions/admin-projects.server.ts
  • packages/web/src/server/functions/admin-stats.server.ts
  • packages/web/src/server/functions/admin-stripe.server.ts

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.

@InfinityBowman
InfinityBowman merged commit aabbe6e into main Sep 12, 2026
10 checks passed
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