Files
pad/internal
xarmian 8a85eca713 feat(web): admin user table — cheap aggregation columns (TASK-1548) (#603)
* feat(web): add cheap aggregation columns to admin user table (TASK-1548)

Surfaces the per-user aggregations T1544 added to GET /admin/users:

- Workspaces (numeric, after Role)
- Storage (used bytes, formatted via the existing formatStorageBytes
  helper — same units the storage-override field accepts)
- Last Write (relative time, color-coded by writeRecency: green <7d,
  yellow <30d, red ≥30d, gray italic when never)
- Status pill (replaces the standalone "disabled" badge; renders for
  disabled / no-workspace / inactive; suppressed for "active" to keep
  the table calm)

Implementation:

- AdminUser interface in admin.svelte.ts gains last_write_at,
  workspace_count, storage_bytes, status — matching the server-side
  JSON shape from T1544.

- writeRecency() helper in +page.svelte buckets the timestamp into a
  CSS class. Visual half of the API's status pill; same age windows.

- .num-cell utility: right-aligned, tabular-nums so digits line up
  across rows (workspace_count and storage cells share it).

- edit-row colspan updated to 9 (cloud_mode) / 8 (self-hosted) to span
  the new columns.

Frontend purely additive — no API changes, no Go changes. T1549 wires
up pagination + sort + filter; T1550-T1555 build the modal.

Part of PLAN-1542.

* fix: address Codex review on TASK-1548

1. handleAdminGetUser response now includes last_write_at, storage_bytes,
   and status — matching handleAdminListUsers' shape. Without these,
   the row-merge after PATCH (role change, disable, plan edit) kept
   stale values; e.g. disabling an active user wouldn't show the new
   "disabled" status pill until the full list reloaded.

   - Store.UserStorageUsage: new helper that sums attachments across
     all workspaces owned by the user. Mirrors WorkspaceStorageUsage's
     definition.
   - Store.ComputeAdminUserStatusValue: exported wrapper around the
     existing private helper so handler code can compute the pill
     value without round-tripping through SearchUsers.

2. writeRecency threshold: 30d is now "stale" (inclusive) rather than
   "cold". Matches server-side computeAdminUserStatus which only flips
   to "inactive" on > 30 days. Eliminates a 1-day boundary mismatch
   between the recency color and the status pill.

3. A11y: write-recency cell now carries aria-label with the bucket
   name ("Last write: 12d ago (stale)"), so screen-reader users get
   the same meaning the color conveys.
2026-05-20 18:10:25 -04:00
..
2026-03-26 01:52:36 +00:00