Console Redesign Batch 1: dark-chrome shell, Tenants list, Dashboard - #32
Conversation
…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>
…Task 2 re-review fix)
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.
…st (Task 10 review fixes)
…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.
|
Warning Review limit reached
Next review available in: 48 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughThis 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. ChangesSuper-admin audit enhancements
Frontend console redesign
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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.
|
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (6)
web/src/pages/super-admin/SuperAdminLayout.tsx (1)
11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove
isActiveNavPathto a separate utility file to fix fast-refresh.ESLint flags that fast refresh only works when a file exclusively exports components. Exporting
isActiveNavPathfrom 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 winAssert 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 winCover 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 winUse Tailwind v4
@utilitydirectives for new utilities.The supplied Tailwind v4 guidance marks
@layer utilitiesas the legacy form for custom utilities. Move these additions to individual@utilitydeclarations, 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 valueConsider the
limit === 0inconsistency betweenmeterToneandmeterPercent.When
limit === 0andcount > 0,meterTonereturns'over'butmeterPercentreturns0. 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,meterPercentcould return100for thelimit === 0 && count > 0case 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 valueAdd a test case for
meterPercentwithlimit === 0.
meterTonehas an explicitlimit === 0branch, butmeterPercenthas no corresponding test. Addingexpect(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
⛔ Files ignored due to path filters (1)
web/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (26)
backend/internal/handler/super_admin.gobackend/internal/handler/super_admin_test.godocs/superpowers/plans/2026-07-11-console-redesign-batch1.mdweb/package.jsonweb/src/components/BarRow.tsxweb/src/components/StatusBadge.tsxweb/src/components/__tests__/BarRow.test.tsxweb/src/components/__tests__/StatusBadge.test.tsxweb/src/components/ui/__tests__/sheet.test.tsxweb/src/components/ui/sheet.tsxweb/src/i18n.tsweb/src/index.cssweb/src/lib/__tests__/meters.test.tsweb/src/lib/__tests__/tenantQueues.test.tsweb/src/lib/meters.tsweb/src/lib/tenantQueues.tsweb/src/pages/super-admin/Analytics.tsxweb/src/pages/super-admin/Dashboard.tsxweb/src/pages/super-admin/Organizations.tsxweb/src/pages/super-admin/SuperAdminLayout.tsxweb/src/pages/super-admin/__tests__/Dashboard.test.tsxweb/src/pages/super-admin/__tests__/Organizations.test.tsxweb/src/pages/super-admin/__tests__/SuperAdminLayout.test.tsxweb/src/test/setup.tsweb/tsconfig.node.jsonweb/vitest.config.ts
| 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) | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 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
| <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> |
There was a problem hiding this comment.
🎯 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
| 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); | ||
| }); |
There was a problem hiding this comment.
🎯 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.
| 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; | ||
| }); |
There was a problem hiding this comment.
🗄️ 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 webRepository: 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.tsRepository: 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.goRepository: 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.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 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.
| <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> | ||
| ))} |
There was a problem hiding this comment.
🗄️ 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.
| "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" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
cd web
npm ls vite vitest --allRepository: 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:
- 1: chore(deps): update dependency vitest to v2.1.9 [security] total-typescript/pro-essentials-workshop#37
- 2: chore(deps-dev): bump vitest from 2.1.5 to 2.1.9 kunkunsh/kunkun#89
- 3: @vitest/mocker peerDependencies are not updated vitest-dev/vitest#9807
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.
| const [searchParams] = useSearchParams(); | ||
| const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || ''); |
There was a problem hiding this comment.
🎯 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.
| 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.
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>
CodeRabbit review triage18 findings (12 actionable + 6 nitpick). Verified each against the actual shipped code — CodeRabbit anchored 9 of them to line numbers inside Fixed (7, commits 973cd0e..8a60f88, independently re-reviewed):
Skipped, with reason (11): pure test-style refactor suggestions (table-driven + One flagged rather than silently fixed or ignored: the Full gates after the fix round: backend clean, frontend |
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
reasonfield on the lifecycle (suspend/reactivate/archive) and impersonation endpoints, persisted into the existing audit log'schangesJSONB. Dormant in this batch (no UI consumes it yet) — Batch 2's Suspend/Archive/Impersonate dialogs will send it.BarRowchart component (used by both Analytics and the new Dashboard).Honest scope notes (no fabricated data)
paid_conversionfield labeled "Paid conversion" instead of inventing a 90-day number./super-admin/analyticspage is unchanged and still reachable by URL.Verification
go test ./...clean,gofmt/golangci-lintclean (1 pre-existing, unrelated staticcheck finding inmain.go, not touched by this batch);tsc -b --noEmit,eslint,vitest run(32/32),npm run buildall clean./super-admin,/super-admin/organizations,/super-admin/organizations/:idin both dark/light mode and RU/EN locale. Tested the Dashboard's Reactivate action end-to-end against the real backend.getComputedStyleand 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 hardcodesbg-blue-*. Reconcile when Batch 2 wires the unified tenant-status identity header.Organizations.tsxhas some inline ad-hoc types duplicating the sharedTenantStatshape.plancolumn header, visible in both locales.UpdateTenantshould reject an emptynameserver-side;GetEventStaffraw TenantID comparison; mobile/kiosktenant_suspendedhandling — 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
Bug Fixes
Tests