Skip to content

Console Redesign Batch 1: dark-chrome shell, Tenants list, Dashboard - #32

Merged
thevladbog merged 20 commits into
mainfrom
worktree-console-redesign
Jul 11, 2026
Merged

Console Redesign Batch 1: dark-chrome shell, Tenants list, Dashboard#32
thevladbog merged 20 commits into
mainfrom
worktree-console-redesign

Conversation

@thevladbog

@thevladbog thevladbog commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

First batch of the Platform Console visual redesign, importing the design from Claude Design project 165a9ba5-4bb1-4ede-9048-546ccb1742af ("Idento Console.dc.html"). Ships the dark top-chrome shell, a reskinned Tenants list, and a reskinned Dashboard, plus the shared infrastructure the rest of the redesign builds on.

Scope decision (confirmed with the user before planning): the source design file bundles a visual redesign of already-shipped features and an entirely new manual-invoicing subsystem (Invoices, Billing tab, PDF documents, RU/EN templates, Service catalog) that needs new backend data models, PDF generation, and proration logic. This batch — and this whole initiative — is scoped to the visual redesign only. Invoicing is deferred to its own future initiative with a proper brainstorming + spec pass.

Batch 2 (not in this PR) covers the rest: Tenant Detail (stacked sections + sticky anchor rail), Suspend modal + Archive side-sheet, Impersonation ceremony reskin, Audit log reskin, Plans editor reskin.

What shipped

  • Backend: optional reason field on the lifecycle (suspend/reactivate/archive) and impersonation endpoints, persisted into the existing audit log's changes JSONB. Dormant in this batch (no UI consumes it yet) — Batch 2's Suspend/Archive/Impersonate dialogs will send it.
  • Frontend test infrastructure: vitest + Testing Library, from zero (no test runner existed before this batch). 32 tests across 8 files, all real assertions.
  • Shared utilities: a meter-tone/percent utility (green <80% / amber ≥80% / red >100%, unlimited-safe), shared tenant-queue filters (trials-ending, over-limit, custom-limits), and a promoted BarRow chart component (used by both Analytics and the new Dashboard).
  • Design tokens: dark console-chrome surface + a 4th "trial" status color, reconciled into the existing shadcn HSL-var system rather than replacing it.
  • Dark-chrome shell: replaces the light sidebar with a dark top nav (matching the design's own canonical choice — it's a design-exploration doc with rival option variants; the doc's own text resolves them). Fixes two latent gaps found during the rewrite: zero active-route highlighting existed before, and the Dashboard/Analytics nav items shared one icon.
  • Tenants list: saved-queue filter chips with live counts, a meter-aware "Attendees vs limit" column, OVER LIMIT / CUSTOM badges, a new Last Activity column (field already existed in the API response, just wasn't rendered), and real client-side pagination (the backend has no server-side filter/paginate params).
  • Dashboard: replaced 4 stub KPI tiles + "coming soon" placeholders with 6 real KPI tiles, 3 live queue modules (trials ending, over-limit, recently-suspended — with a working inline Reactivate action), and an analytics row.

Honest scope notes (no fabricated data)

  • The design's "Trial → paid, 90 d" KPI has no real backend cohort metric — implemented as the existing all-time paid_conversion field labeled "Paid conversion" instead of inventing a 90-day number.
  • The design's "Analytics" top-nav item has no matching screen anywhere in the source file (a dangling reference in the design doc itself) — left out of the new nav; the /super-admin/analytics page is unchanged and still reachable by URL.

Verification

  • Gates: go test ./... clean, gofmt/golangci-lint clean (1 pre-existing, unrelated staticcheck finding in main.go, not touched by this batch); tsc -b --noEmit, eslint, vitest run (32/32), npm run build all clean.
  • Live click-through: ran the real app (backend + Postgres + dev server), logged in as super-admin, exercised /super-admin, /super-admin/organizations, /super-admin/organizations/:id in both dark/light mode and RU/EN locale. Tested the Dashboard's Reactivate action end-to-end against the real backend.
  • Caught during click-through, fixed before merge: in light mode, the new header's hardcoded chrome-foreground text color made the theme-toggle button (outline-variant, its own theme-aware background) invisible — white icon on white background. Root-caused via getComputedStyle and cross-checked against the untouched customer-facing header (renders correctly there), confirming it was a genuine regression from the new shell, not pre-existing. Fixed with a header-scoped override; independently re-verified live.

Process

Every task (10 total) went through: failing test → implementation → self-review → independent task review → fix round where issues were found (Tasks 2, 9, 10 needed fix rounds; 1, 3, 4, 5, 6, 7, 8 passed clean). Final whole-branch review (most capable model) checked cross-task integration specifically — shared-utility consumption, i18n key parity (789/789 EN/RU, zero duplicates), token wiring, and confirmed nothing touches auth/authz or tenant isolation.

Follow-ups (tracked, not blocking)

  • --status-trial* CSS tokens (added this batch) are unused — StatusBadge's trial variant still hardcodes bg-blue-*. Reconcile when Batch 2 wires the unified tenant-status identity header.
  • Shell search placeholder says "name, slug, owner email" but the actual filter only matches name + contact email; also doesn't resync if you search again from the same route.
  • Organizations.tsx has some inline ad-hoc types duplicating the shared TenantStat shape.
  • Dashboard's "Check-ins today" assumes the analytics endpoint's last time-bucket is always today; not derived by explicit date.
  • A pre-existing (not introduced by this batch) untranslated plan column header, visible in both locales.
  • Backend: UpdateTenant should reject an empty name server-side; GetEventStaff raw TenantID comparison; mobile/kiosk tenant_suspended handling — all pre-existing backlog carried from Phase 1.

Next: Batch 2 (Tenant Detail, Suspend/Archive dialogs, Impersonation ceremony, Audit log, Plans editor) — will get its own plan once this merges.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Redesigned the platform console with a dark top navigation shell and keyboard-accessible search.
    • Added dashboard KPIs, analytics charts, tenant monitoring queues, usage meters, and tenant reactivation actions.
    • Added saved tenant queues, pagination, last-activity details, custom-limit and over-limit indicators.
    • Added reusable side-panel components and improved trial status badges.
    • Super-admin actions can now include optional reasons in audit records.
  • Bug Fixes

    • Preserved tenant lifecycle and impersonation actions when no request reason is provided.
  • Tests

    • Added automated coverage for dashboard, tenant queues, navigation, status badges, and audit logging.

CI Bot and others added 15 commits July 11, 2026 00:43
…d to audit log

Adds optional `reason` field to setTenantStatus (suspend/reactivate/archive)
and ImpersonateTenant handlers. The reason is read from request body JSON
and persisted in the audit log changes map. Empty reason is omitted from
audit changes. No changes to response bodies or status codes; requests
without body or empty JSON work exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…or vitest.config.ts (Task 2 review fixes)

- Add passWithNoTests: true to vitest test config to allow zero test files (bootstrap phase)
- Add vitest.config.ts to tsconfig.node.json include for tsc coverage
- Cast plugins array to any to resolve vitest/vite version mismatch in type checking
- Remove empty web/src/lib/__tests__/ directory left after smoke test deletion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… component

- Add tenantQueues.ts with filter utilities: trialsEndingWithinDays, overLimitTenants, onCustomLimitTenants, resolvedLimit
- Promote BarRow component from Analytics.tsx to shared components
- Analytics.tsx now imports the shared BarRow instead of defining it privately

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tom/over-limit badges, pagination

Task 9: adds saved-queue filter chips with live counts, a usage-meter-aware
Attendees column, CUSTOM/OVER LIMIT badges, a new Last Activity column, and
client-side pagination to the super-admin Organizations (Tenants) list.
Consumes Task 8's tenantQueues helpers and Task 3's meters helpers.
…, analytics row

Replaces Dashboard.tsx's 4 stub KPI tiles with 6 real ones fed by
GET /api/super-admin/tenants + GET /api/super-admin/analytics, adds 3 live
queue modules (trials ending within 7 days, over-limit, recently-suspended
with an inline Reactivate action), and a 4-panel analytics row built on the
shared BarRow component. Paid-conversion tile intentionally reuses the
all-time paid_conversion field (no 90-day cohort metric exists yet) under
an honest "Paid conversion" label per agreed scope.
…inal review fix)

ModeToggle's outline-variant Button set bg-background (theme-aware), which
inherits the header's white text color unchanged but paints the button
white in light mode, making the icon invisible. Wrap ModeToggle and
LanguageToggle in a header-scoped div that forces a transparent/chrome
background, border, and icon color for both button variants, using
important-modifier utilities since Tailwind's generated source order
otherwise doesn't guarantee the wrapper wins over the button's own classes.
Copilot AI review requested due to automatic review settings July 10, 2026 23:38

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added documentation Improvements or additions to documentation backend web labels Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@thevladbog, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6cf0b4d-8550-47ae-90cc-40a1df3e63d8

📥 Commits

Reviewing files that changed from the base of the PR and between 973cd0e and 8a60f88.

📒 Files selected for processing (10)
  • backend/internal/handler/super_admin_test.go
  • web/src/components/ui/__tests__/sheet.test.tsx
  • web/src/components/ui/sheet.tsx
  • web/src/i18n.ts
  • web/src/lib/__tests__/meters.test.ts
  • web/src/lib/__tests__/tenantQueues.test.ts
  • web/src/lib/meters.ts
  • web/src/lib/navUtils.ts
  • web/src/pages/super-admin/SuperAdminLayout.tsx
  • web/src/pages/super-admin/__tests__/SuperAdminLayout.test.tsx
📝 Walkthrough

Walkthrough

This PR adds optional audit reasons to super-admin tenant actions and redesigns the super-admin frontend with new testing infrastructure, shared utilities, navigation, tenant queues, dashboard analytics, styling, translations, and component tests.

Changes

Super-admin audit enhancements

Layer / File(s) Summary
Audit reasons for tenant actions
backend/internal/handler/super_admin.go, backend/internal/handler/super_admin_test.go
Lifecycle and impersonation handlers persist non-empty request reasons in audit changes while preserving no-body behavior and existing status fields.

Frontend console redesign

Layer / File(s) Summary
Frontend testing and shared primitives
web/vitest.config.ts, web/src/lib/*, web/src/components/*, web/src/components/ui/sheet.tsx, web/src/index.css, web/src/i18n.ts
Adds Vitest/jsdom setup, meter and tenant queue helpers, reusable UI components, trial styling, console tokens, and English/Russian translations.
Navigation and tenant queue experience
web/src/pages/super-admin/SuperAdminLayout.tsx, web/src/pages/super-admin/Organizations.tsx, web/src/pages/super-admin/Analytics.tsx
Introduces top navigation and search, saved tenant queues, client-side filtering and pagination, usage indicators, and shared analytics bar rows.
Analytics dashboard and validation
web/src/pages/super-admin/Dashboard.tsx, web/src/pages/super-admin/__tests__/*
Adds parallel analytics loading, KPI and queue cards, charts, reactivation behavior, and route-level tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main redesign scope: the dark console shell, Tenants list, and Dashboard updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-console-redesign

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.

@socket-security

socket-security Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​vitest@​2.1.998257999100
Addednpm/​jsdom@​25.0.1951001009070
Addednpm/​@​testing-library/​user-event@​14.6.110010010086100
Addednpm/​@​testing-library/​react@​16.3.210010010087100
Addednpm/​@​testing-library/​jest-dom@​6.9.110010010089100

View full report

@socket-security

socket-security Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn Critical
Critical CVE: When Vitest UI server is listening, arbitrary file can be read and executed

CVE: GHSA-5xrq-8626-4rwp When Vitest UI server is listening, arbitrary file can be read and executed (CRITICAL)

Affected versions: >= 4.0.0 < 4.1.0; < 3.2.6

Patched version: 3.2.6

From: web/package-lock.jsonnpm/vitest@2.1.9

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vitest@2.1.9. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm data-urls is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package-lock.jsonnpm/jsdom@25.0.1npm/data-urls@5.0.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/data-urls@5.0.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm rrweb-cssom is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package-lock.jsonnpm/jsdom@25.0.1npm/rrweb-cssom@0.7.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/rrweb-cssom@0.7.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm rrweb-cssom is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: web/package-lock.jsonnpm/jsdom@25.0.1npm/rrweb-cssom@0.8.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/rrweb-cssom@0.8.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (6)
web/src/pages/super-admin/SuperAdminLayout.tsx (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move isActiveNavPath to a separate utility file to fix fast-refresh.

ESLint flags that fast refresh only works when a file exclusively exports components. Exporting isActiveNavPath from this component file breaks HMR — edits to this file will trigger a full reload instead of a fast refresh. Extract the helper to a dedicated module (e.g., @/lib/navUtils.ts) and import it in both the component and test.

🤖 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 `@web/src/pages/super-admin/SuperAdminLayout.tsx` at line 11, Move the exported
isActiveNavPath helper from SuperAdminLayout.tsx into a dedicated utility module
such as `@/lib/navUtils.ts`, then import it in SuperAdminLayout and its tests;
leave the component file exporting only components to satisfy fast-refresh
requirements.

Source: Linters/SAST tools

docs/superpowers/plans/2026-07-11-console-redesign-batch1.md (1)

1779-1782: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the queue, not only the tenant name.

The test passes if “Acme Conf Group” appears in another dashboard section, even when the over-limit queue is missing. Assert the queue heading/count and the row’s Review action.

🤖 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 `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md` around lines
1779 - 1782, Strengthen the Dashboard test “renders the over-limit queue with
the tenant that exceeds its limit” by asserting the over-limit queue heading and
count, then verifying the Acme Conf Group row includes its Review action. Do not
rely solely on getByText('Acme Conf Group'), since that text may appear in
another dashboard section.
web/src/lib/__tests__/tenantQueues.test.ts (1)

41-55: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover every over-limit dimension.

The utility checks events, attendees, and users, but these tests exercise only attendees. Add cases for events_per_month, users, and custom-limit overrides so regressions in the other branches fail reliably.

🤖 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 `@web/src/lib/__tests__/tenantQueues.test.ts` around lines 41 - 55, Expand the
overLimitTenants tests to cover each limit dimension handled by
overLimitTenants: events_per_month and users, including tenants below and above
their limits. Add cases verifying custom-limit overrides take precedence over
plan limits, while preserving the unlimited (-1) behavior where applicable.
web/src/index.css (1)

111-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Tailwind v4 @utility directives for new utilities.

The supplied Tailwind v4 guidance marks @layer utilities as the legacy form for custom utilities. Move these additions to individual @utility declarations, or verify the generated production CSS to ensure the classes are emitted and remain compatible with the v4 pipeline.

🤖 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 `@web/src/index.css` around lines 111 - 115, Replace the custom utility rules
for bg-console-chrome, bg-console-chrome-active, text-console-chrome-foreground,
and text-console-chrome-muted-foreground with individual Tailwind v4 `@utility`
declarations, preserving their existing HSL variable values and confirming the
classes are emitted in production CSS.
web/src/lib/meters.ts (1)

15-18: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider the limit === 0 inconsistency between meterTone and meterPercent.

When limit === 0 and count > 0, meterTone returns 'over' but meterPercent returns 0. This means a usage meter would render 0% width with a red/destructive tone, which could be visually confusing. If this is intentional (limit of 0 has no meaningful percentage), consider adding a brief comment to document the design choice. If not, meterPercent could return 100 for the limit === 0 && count > 0 case to align with the 'over' tone.

🤖 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 `@web/src/lib/meters.ts` around lines 15 - 18, Resolve the limit-zero behavior
mismatch between meterPercent and meterTone: update meterPercent so limit === 0
with count > 0 returns 100, while preserving 0 for zero usage and
unlimited/nonpositive limits as appropriate, aligning the percentage with the
over tone.
web/src/lib/__tests__/meters.test.ts (1)

29-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a test case for meterPercent with limit === 0.

meterTone has an explicit limit === 0 branch, but meterPercent has no corresponding test. Adding expect(meterPercent(5, 0)).toBe(0) would lock in the current behavior and document the design decision noted above.

🤖 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 `@web/src/lib/__tests__/meters.test.ts` around lines 29 - 38, Add a test case
in the meterPercent test suite asserting meterPercent(5, 0) returns 0,
documenting the expected behavior for a zero limit alongside the existing
clamping and unlimited-limit cases.
🤖 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 `@backend/internal/handler/super_admin_test.go`:
- Around line 69-112: The no-body test does not verify that the audit callback
executed. In TestSetTenantStatus_NoBodyStillWorks, add a capturedChanges == nil
assertion failure immediately after the handler/status checks and before
inspecting the reason key, then retain the existing assertion that reason is
absent.
- Around line 16-67: Refactor the related audit-change tests, including
TestSetTenantStatus_ReasonPersistedToAuditChanges and the additional cases, into
one table-driven test with subtests and t.Parallel(). Hoist any shared
JWT-secret setup before entering parallel subtests because t.Setenv cannot run
in parallel tests, and parameterize each scenario’s request, expected status,
and captured audit changes while preserving the existing assertions.

In `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md`:
- Around line 1620-1622: Update the Last Activity cell in the tenant table to
format dates with the active i18next locale: use i18n.language with
Intl.DateTimeFormat instead of the browser-default toLocaleDateString(), while
preserving the em dash for missing tenant.last_activity values.
- Around line 771-774: Replace the hard-coded “Close” text in the
SheetPrimitive.Close accessible label with the existing i18next translation
mechanism, using an appropriate EN/RU key and translated screen-reader text or
aria-label while preserving the visual close icon.
- Around line 1835-1847: Add error-state handling to the dashboard’s load
function: catch rejected requests from the parallel tenant and analytics API
calls, store a user-visible error message, and render an error state with a
retry action that invokes load again. Preserve the finally block to clear
loading, and clear any previous error when a new load starts.
- Around line 937-951: Update isActiveNavPath so non-root routes match only on
an exact path or when pathname begins with the item path followed by a slash,
preventing collisions such as organizations versus organizations-legacy. Extend
the isActiveNavPath tests with this shared-prefix case.
- Around line 1527-1528: The Organizations search state is only initialized from
the URL and does not update when the query changes. In the component using
`useSearchParams` and `searchQuery`, add an effect that watches `searchParams`
(or its `q` value) and updates `searchQuery` via `setSearchQuery`, including the
corresponding occurrence noted at the second location.
- Around line 1955-1972: The queue label incorrectly implies recency while
displaying all suspended tenants. Update the CardTitle translation reference
from recentlySuspendedQueue to a neutral suspended label such as suspendedQueue,
and add or update the corresponding translation entry; alternatively, introduce
a backend suspension timestamp and filter the suspended collection before
rendering.
- Around line 1310-1319: The isOverLimit function compares tenant-wide totals
against scoped limits, causing false over-limit results. Update TenantStat/API
usage data to provide month-scoped event counts and event-scoped attendee
counts, then use those fields in the checks for events_per_month and
attendees_per_event while retaining users_count for users.

In `@web/package.json`:
- Around line 69-75: Align the dependency versions in web/package.json by
upgrading vitest from 2.1.9 to a major version compatible with vite 8.1.4, and
update the lockfile accordingly; alternatively, downgrade vite to a version
supported by Vitest 2.x.

In `@web/src/lib/__tests__/tenantQueues.test.ts`:
- Around line 1-8: Add test cases in the overLimitTenants() test suite for
tenants exceeding events_count and users_count limits, alongside the existing
attendees-limit case. Use TenantStat fixtures with each respective count above
its configured limit and assert those tenants are returned.

In `@web/src/pages/super-admin/Organizations.tsx`:
- Around line 22-23: Add a useEffect in the Organizations component to watch
searchParams and update searchQuery from searchParams.get('q') (falling back to
an empty string), so same-page header searches refresh the filter without
relying on remounting.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md`:
- Around line 1779-1782: Strengthen the Dashboard test “renders the over-limit
queue with the tenant that exceeds its limit” by asserting the over-limit queue
heading and count, then verifying the Acme Conf Group row includes its Review
action. Do not rely solely on getByText('Acme Conf Group'), since that text may
appear in another dashboard section.

In `@web/src/index.css`:
- Around line 111-115: Replace the custom utility rules for bg-console-chrome,
bg-console-chrome-active, text-console-chrome-foreground, and
text-console-chrome-muted-foreground with individual Tailwind v4 `@utility`
declarations, preserving their existing HSL variable values and confirming the
classes are emitted in production CSS.

In `@web/src/lib/__tests__/meters.test.ts`:
- Around line 29-38: Add a test case in the meterPercent test suite asserting
meterPercent(5, 0) returns 0, documenting the expected behavior for a zero limit
alongside the existing clamping and unlimited-limit cases.

In `@web/src/lib/__tests__/tenantQueues.test.ts`:
- Around line 41-55: Expand the overLimitTenants tests to cover each limit
dimension handled by overLimitTenants: events_per_month and users, including
tenants below and above their limits. Add cases verifying custom-limit overrides
take precedence over plan limits, while preserving the unlimited (-1) behavior
where applicable.

In `@web/src/lib/meters.ts`:
- Around line 15-18: Resolve the limit-zero behavior mismatch between
meterPercent and meterTone: update meterPercent so limit === 0 with count > 0
returns 100, while preserving 0 for zero usage and unlimited/nonpositive limits
as appropriate, aligning the percentage with the over tone.

In `@web/src/pages/super-admin/SuperAdminLayout.tsx`:
- Line 11: Move the exported isActiveNavPath helper from SuperAdminLayout.tsx
into a dedicated utility module such as `@/lib/navUtils.ts`, then import it in
SuperAdminLayout and its tests; leave the component file exporting only
components to satisfy fast-refresh requirements.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2dc94cb-bbf9-4960-8869-9ef350226a42

📥 Commits

Reviewing files that changed from the base of the PR and between 5f8586e and 973cd0e.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • backend/internal/handler/super_admin.go
  • backend/internal/handler/super_admin_test.go
  • docs/superpowers/plans/2026-07-11-console-redesign-batch1.md
  • web/package.json
  • web/src/components/BarRow.tsx
  • web/src/components/StatusBadge.tsx
  • web/src/components/__tests__/BarRow.test.tsx
  • web/src/components/__tests__/StatusBadge.test.tsx
  • web/src/components/ui/__tests__/sheet.test.tsx
  • web/src/components/ui/sheet.tsx
  • web/src/i18n.ts
  • web/src/index.css
  • web/src/lib/__tests__/meters.test.ts
  • web/src/lib/__tests__/tenantQueues.test.ts
  • web/src/lib/meters.ts
  • web/src/lib/tenantQueues.ts
  • web/src/pages/super-admin/Analytics.tsx
  • web/src/pages/super-admin/Dashboard.tsx
  • web/src/pages/super-admin/Organizations.tsx
  • web/src/pages/super-admin/SuperAdminLayout.tsx
  • web/src/pages/super-admin/__tests__/Dashboard.test.tsx
  • web/src/pages/super-admin/__tests__/Organizations.test.tsx
  • web/src/pages/super-admin/__tests__/SuperAdminLayout.test.tsx
  • web/src/test/setup.ts
  • web/tsconfig.node.json
  • web/vitest.config.ts

Comment on lines +16 to +67
func TestSetTenantStatus_ReasonPersistedToAuditChanges(t *testing.T) {
e := echo.New()
tenantID := uuid.New()
adminID := uuid.New()
var capturedChanges map[string]interface{}

fs := &fakeStore{
getTenantStatus: func(id uuid.UUID) (string, error) {
if id == tenantID {
return "active", nil
}
return "", nil
},
updateTenantStatus: func(id uuid.UUID, status string) error {
return nil
},
logAdminAction: func(audID uuid.UUID, action, targetType string, targetID uuid.UUID, changes interface{}, ip, userAgent string) error {
capturedChanges = changes.(map[string]interface{})
return nil
},
}

h := &Handler{Store: fs}
body, _ := json.Marshal(map[string]string{"reason": "Spring Summit 2026, approved by JR"})
req := httptest.NewRequest(http.MethodPost, "/api/super-admin/tenants/"+tenantID.String()+"/suspend", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetParamNames("id")
c.SetParamValues(tenantID.String())
c.Set("user", &models.JWTCustomClaims{
UserID: adminID.String(),
TenantID: uuid.New().String(),
Role: "admin",
})

if err := h.SuspendTenant(c); err != nil {
t.Fatalf("SuspendTenant returned error: %v", err)
}
if rec.Code != http.StatusOK {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
if capturedChanges == nil {
t.Fatalf("expected changes to be captured, got nil")
}
if capturedChanges["reason"] != "Spring Summit 2026, approved by JR" {
t.Fatalf("expected reason in audit changes, got %#v", capturedChanges)
}
if capturedChanges["from"] != "active" || capturedChanges["to"] != "suspended" {
t.Fatalf("expected from/to preserved alongside reason, got %#v", capturedChanges)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use table-driven subtests and parallel execution.

These three cases duplicate the same Echo/fake-store setup and none uses t.Parallel(). Consolidate the scenarios into table-driven subtests; hoist shared JWT-secret setup before parallel subtests because t.Setenv cannot be called from a parallel test.

As per coding guidelines: Write unit tests using table-driven patterns and parallel execution.

Also applies to: 69-112, 114-160

🤖 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 `@backend/internal/handler/super_admin_test.go` around lines 16 - 67, Refactor
the related audit-change tests, including
TestSetTenantStatus_ReasonPersistedToAuditChanges and the additional cases, into
one table-driven test with subtests and t.Parallel(). Hoist any shared
JWT-secret setup before entering parallel subtests because t.Setenv cannot run
in parallel tests, and parameterize each scenario’s request, expected status,
and captured audit changes while preserving the existing assertions.

Source: Coding guidelines

Comment thread backend/internal/handler/super_admin_test.go
Comment on lines +771 to +774
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the Sheet close label.

The screen-reader text is hard-coded as Close, so Russian users receive an English accessible label. Use an i18n key or a translated aria-label.

As per coding guidelines: Use i18next for internationalization supporting EN/RU languages.

🤖 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 `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md` around lines
771 - 774, Replace the hard-coded “Close” text in the SheetPrimitive.Close
accessible label with the existing i18next translation mechanism, using an
appropriate EN/RU key and translated screen-reader text or aria-label while
preserving the visual close icon.

Source: Coding guidelines

Comment on lines +937 to +951
describe('isActiveNavPath', () => {
it('matches the dashboard root only on an exact path', () => {
expect(isActiveNavPath('/super-admin', '/super-admin')).toBe(true);
expect(isActiveNavPath('/super-admin', '/super-admin/organizations')).toBe(false);
});

it('matches nested routes by prefix for non-root items', () => {
expect(isActiveNavPath('/super-admin/organizations', '/super-admin/organizations')).toBe(true);
expect(isActiveNavPath('/super-admin/organizations', '/super-admin/organizations/abc-123')).toBe(true);
expect(isActiveNavPath('/super-admin/organizations', '/super-admin/plans')).toBe(false);
});

it('does not cross-match distinct top-level sections that share a prefix', () => {
expect(isActiveNavPath('/super-admin/users', '/super-admin/organizations')).toBe(false);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid prefix collisions in active-route matching.

pathname.startsWith(itemPath) marks paths such as /super-admin/organizations-legacy as Organizations. Use an exact match or a /${itemPath}/ boundary, and add that shared-prefix case to the test.

Proposed fix
-  return pathname.startsWith(itemPath);
+  return pathname === itemPath || pathname.startsWith(`${itemPath}/`);

Also applies to: 988-991

🤖 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 `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md` around lines
937 - 951, Update isActiveNavPath so non-root routes match only on an exact path
or when pathname begins with the item path followed by a slash, preventing
collisions such as organizations versus organizations-legacy. Extend the
isActiveNavPath tests with this shared-prefix case.

Comment on lines +1310 to +1319
function isOverLimit(t: TenantStat): boolean {
const checks: Array<['events_per_month' | 'attendees_per_event' | 'users', number]> = [
['events_per_month', t.events_count ?? 0],
['attendees_per_event', t.attendees_count ?? 0],
['users', t.users_count ?? 0],
];
return checks.some(([key, count]) => {
const limit = resolvedLimit(t.subscription, key);
return limit !== UNLIMITED && count > limit;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "attendees_count|events_count|attendees_per_event|events_per_month" backend web

Repository: thevladbog/idento

Length of output: 9401


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' web/src/lib/tenantQueues.ts
printf '\n--- pg_store excerpt ---\n'
sed -n '880,930p' backend/internal/store/pg_store.go
printf '\n--- models excerpt ---\n'
sed -n '130,170p' backend/internal/models/models.go
printf '\n--- queue tests excerpt ---\n'
sed -n '1,220p' web/src/lib/__tests__/tenantQueues.test.ts

Repository: thevladbog/idento

Length of output: 8823


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "events_per_month|CheckLimits|resolveTenantLimit|UsageLog|monthly" backend/internal -g '!**/*_test.go'
printf '\n--- limit enforcement excerpt ---\n'
sed -n '1460,1565p' backend/internal/store/pg_store.go
printf '\n--- middleware excerpt ---\n'
sed -n '1,140p' backend/internal/middleware/limits.go

Repository: thevladbog/idento

Length of output: 9982


Match the over-limit checks to the right usage scopes. events_count and attendees_count are tenant-wide totals, but events_per_month and attendees_per_event are month/event-scoped limits. This will flag tenants as over limit when they aren’t; use scope-matching usage fields or expose scoped aggregates in the API.

🤖 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 `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md` around lines
1310 - 1319, The isOverLimit function compares tenant-wide totals against scoped
limits, causing false over-limit results. Update TenantStat/API usage data to
provide month-scoped event counts and event-scoped attendee counts, then use
those fields in the checks for events_per_month and attendees_per_event while
retaining users_count for users.

Comment on lines +1835 to +1847
async function load() {
setLoading(true);
try {
const [tenantsRes, analyticsRes] = await Promise.all([
api.get('/api/super-admin/tenants'),
api.get('/api/super-admin/analytics'),
]);
setTenants(tenantsRes.data || []);
setAnalytics(analyticsRes.data);
} finally {
setLoading(false);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Surface dashboard load failures.

The try/finally clears loading, but there is no catch or error state. A rejected request escapes the effect and leaves the dashboard rendering empty data without explaining the failure or offering retry.

🤖 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 `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md` around lines
1835 - 1847, Add error-state handling to the dashboard’s load function: catch
rejected requests from the parallel tenant and analytics API calls, store a
user-visible error message, and render an error state with a retry action that
invokes load again. Preserve the finally block to clear loading, and clear any
previous error when a new load starts.

Comment on lines +1955 to +1972
<Card>
<CardHeader><CardTitle className="text-sm">{t('recentlySuspendedQueue')} · {suspended.length}</CardTitle></CardHeader>
<CardContent className="space-y-2">
{suspended.length === 0 && <p className="text-sm text-muted-foreground">{t('noItemsInQueue')}</p>}
{suspended.map((tn) => (
<div key={tn.tenant?.id} className="flex items-center justify-between text-sm">
<Link to={`/super-admin/organizations/${tn.tenant?.id}`} className="hover:underline">
{tn.tenant?.name}
</Link>
<Button
size="sm"
disabled={reactivatingId === tn.tenant?.id}
onClick={() => tn.tenant?.id && handleReactivate(tn.tenant.id)}
>
{t('reactivateAction')}
</Button>
</div>
))}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not label every suspended tenant as “recently suspended.”

This queue contains all tenants whose current status is suspended; no recency field or timestamp is used. Rename the queue to “Suspended” or add a backend-provided suspension timestamp and filter.

🤖 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 `@docs/superpowers/plans/2026-07-11-console-redesign-batch1.md` around lines
1955 - 1972, The queue label incorrectly implies recency while displaying all
suspended tenants. Update the CardTitle translation reference from
recentlySuspendedQueue to a neutral suspended label such as suspendedQueue, and
add or update the corresponding translation entry; alternatively, introduce a
backend suspension timestamp and filter the suspended collection before
rendering.

Comment thread web/package.json
Comment on lines +69 to +75
"jsdom": "^25.0.1",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.17",
"typescript": "~5.6.3",
"typescript-eslint": "^8.15.0",
"vite": "^8.1.4"
"vite": "^8.1.4",
"vitest": "^2.1.9"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cd web
npm ls vite vitest --all

Repository: thevladbog/idento

Length of output: 350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- web/package.json excerpt ---'
sed -n '1,140p' web/package.json

echo
echo '--- lockfile presence ---'
git ls-files 'web/package-lock.json' 'web/pnpm-lock.yaml' 'web/yarn.lock' 'package-lock.json' 'pnpm-lock.yaml' 'yarn.lock'

Repository: thevladbog/idento

Length of output: 2465


🌐 Web query:

Vitest 2.1.9 package.json peerDependencies Vite 8 compatibility

💡 Result:

Vitest 2.1.9 is not compatible with Vite 8. Vitest 2.x releases were designed to support Vite 5 (and limited support for Vite 6 was briefly introduced and then reverted in 2.1.7) [1][2]. Vite 8 is a significantly newer version released in 2026. Official support for Vite 8 was introduced in later major versions of Vitest, specifically Vitest 4.x [3]. Projects attempting to use Vitest 2.1.9 with Vite 8 will face dependency incompatibilities because Vitest 2.x does not declare Vite 8 in its peer dependencies or dependency requirements [2].

Citations:


Align Vitest with Vite 8 (web/package.json)

Vitest 2.1.9 is not compatible with Vite 8, so this combo can break the test runner or pull in mismatched Vite internals. Upgrade Vitest to a Vite 8–compatible major, or pin Vite to a version supported by Vitest 2.x.

🤖 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 `@web/package.json` around lines 69 - 75, Align the dependency versions in
web/package.json by upgrading vitest from 2.1.9 to a major version compatible
with vite 8.1.4, and update the lockfile accordingly; alternatively, downgrade
vite to a version supported by Vitest 2.x.

Comment thread web/src/lib/__tests__/tenantQueues.test.ts
Comment on lines +22 to +23
const [searchParams] = useSearchParams();
const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Header search doesn't filter the list when already on the Organizations page.

searchQuery is initialized from searchParams.get('q') via useState, which only reads on mount. When a user is already on /super-admin/organizations and submits a search from the header, SuperAdminLayout navigates to the same route with a ?q= param — the component re-renders but does not remount, so searchQuery never updates and the list stays unfiltered.

Add a useEffect to sync the URL param back into state:

🔧 Proposed fix
 const [searchParams] = useSearchParams();
 const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');
+
+useEffect(() => {
+  setSearchQuery(searchParams.get('q') || '');
+}, [searchParams]);
📝 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.

Suggested change
const [searchParams] = useSearchParams();
const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');
const [searchParams] = useSearchParams();
const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');
useEffect(() => {
setSearchQuery(searchParams.get('q') || '');
}, [searchParams]);
🤖 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 `@web/src/pages/super-admin/Organizations.tsx` around lines 22 - 23, Add a
useEffect in the Organizations component to watch searchParams and update
searchQuery from searchParams.get('q') (falling back to an empty string), so
same-page header searches refresh the filter without relying on remounting.

CI Bot and others added 5 commits July 11, 2026 04:17
TestSetTenantStatus_NoBodyStillWorks read capturedChanges["reason"]
directly, which is safe on a nil map — the test would still pass even
if logAdminAction were never invoked. Add an explicit nil check so the
test proves the audit callback ran, not just that IF it ran, no reason
key was set.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
isActiveNavPath's non-root branch used a naive pathname.startsWith(itemPath)
check, so /super-admin/organizations would incorrectly match a hypothetical
/super-admin/organizations-legacy route as active. Require an exact match or
a path-boundary-respecting prefix (itemPath + "/").

Also move isActiveNavPath out of SuperAdminLayout.tsx into a new
web/src/lib/navUtils.ts — exporting a non-component function alongside a
default-exported React component breaks Vite's fast-refresh HMR
(react-refresh/only-export-components), which was a previously-accepted
eslint warning. Update the test's import and add a collision-scenario test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rage

meterPercent(count, 0) always returned 0, even when count > 0 — but
meterTone already treats a zero limit with nonzero usage as 'over'. That
produced a visually contradictory meter: a red "over" label on a
0%-width bar. Distinguish limit === 0 from the unlimited (-1) sentinel
and return 100 when usage exceeds a zero limit.

Also add overLimitTenants test coverage for the events_per_month and
users dimensions — previously only attendees_per_event and the
unlimited-plan case were exercised. No implementation change needed
there; isOverLimit already handled all three dimensions correctly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SheetContent's close button hardcoded an English-only "Close" sr-only
label. Reuse the existing `close` i18n key (already defined for the
QR-token dialog, EN "Close" / RU "Закрыть") via useTranslation instead
of adding a duplicate key. Not yet consumed by any page in this batch,
but infrastructure for a future Archive dialog — fixed now so it isn't
missed later.

dialog.tsx has the identical hardcoded "Close" issue but is out of
scope for this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The recentlySuspendedQueue label read "Recently suspended" but the
queue actually shows every tenant currently in suspended status, with
no recency filter or suspension timestamp anywhere in the data — a
tenant suspended a year ago renders identically to one suspended five
minutes ago. Relabel to plain "Suspended" (EN) / "Приостановлены" (RU,
matching savedQueueSuspended's existing wording). Copy-only change, key
name and component logic untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@thevladbog

Copy link
Copy Markdown
Owner Author

CodeRabbit review triage

18 findings (12 actionable + 6 nitpick). Verified each against the actual shipped code — CodeRabbit anchored 9 of them to line numbers inside docs/superpowers/plans/2026-07-11-console-redesign-batch1.md's embedded code snippets rather than the real source files, since the plan doc was part of this PR's diff; several of those were already fixed in an earlier round and just reflect stale pre-fix snippets.

Fixed (7, commits 973cd0e..8a60f88, independently re-reviewed):

  • Backend: TestSetTenantStatus_NoBodyStillWorks now actually asserts the audit callback ran (was a false-pass risk, not a crash).
  • isActiveNavPath prefix-collision bug fixed (/super-admin/organizations no longer matches a hypothetical /super-admin/organizations-legacy) + moved to lib/navUtils.ts, resolving the react-refresh warning.
  • meterPercent(count, 0) now returns 100 when count > 0, consistent with meterTone's 'over' verdict for the same input.
  • Added overLimitTenants test coverage for the events_per_month and users dimensions (previously only attendees_per_event was tested).
  • Sheet's hardcoded "Close" screen-reader label now uses i18n (reused the existing close key, EN+RU).
  • Dashboard's "Recently suspended" queue relabeled to "Suspended" — it has no recency filter (no suspension timestamp exists anywhere), so the old label overclaimed.

Skipped, with reason (11): pure test-style refactor suggestions (table-driven + t.Parallel()) matching prior-session precedent for this repo; the Vitest 2.1.9/Vite 8 version note (known, already mitigated via passWithNoTests + a typed cast in the original batch, tests demonstrably pass, a major-version upgrade is a real but separate task); @layer utilities vs Tailwind v4 @utility (verified working, matches this file's pre-existing convention); two items already fixed in the prior review round (Dashboard error handling, the over-limit test's scoping); the Last-Activity date-locale nitpick (matches the pre-existing Created column's identical convention — fixing one without the other would be inconsistent within the same row).

One flagged rather than silently fixed or ignored: the isOverLimit check (Tenants list's OVER LIMIT badge, the saved-queue chip, and the Dashboard's Over-limit queue) compares tenant-wide cumulative totals (events_count, attendees_count) against monthly/per-event plan limits (events_per_month, attendees_per_event) — a real scope mismatch CodeRabbit is right to flag. There's no backend field for scoped (monthly / per-event) usage today, so a correct fix needs backend work. The alternative — restricting the check to only the users dimension, the one that's scope-correct — would make this feature (spanning 3 already-approved tasks) nearly inert for realistic data. That's a big enough behavior change that I didn't make it unilaterally in a comment-fix pass; it's documented in code and tracked as a Batch 2 / backend follow-up.

Full gates after the fix round: backend clean, frontend tsc/eslint clean (zero warnings now, down from one previously-accepted one), vitest 37/37, build succeeds.

@thevladbog
thevladbog merged commit fff116d into main Jul 11, 2026
24 checks passed
@thevladbog
thevladbog deleted the worktree-console-redesign branch July 11, 2026 02:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend documentation Improvements or additions to documentation web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants