Skip to content

fix: Frontend demo polish - broken pages, navigation, UX#1430

Merged
bjcoombs merged 3 commits intodevelopfrom
frontend-demo-polish
Mar 5, 2026
Merged

fix: Frontend demo polish - broken pages, navigation, UX#1430
bjcoombs merged 3 commits intodevelopfrom
frontend-demo-polish

Conversation

@bjcoombs
Copy link
Copy Markdown
Collaborator

@bjcoombs bjcoombs commented Mar 5, 2026

Summary

Fixes ~15 issues found during demo.meridianhub.cloud walkthrough (build 87C4620) to make the frontend presentable before showcasing Starlark event triggers.

Changes

Backend (Proto)

  • Add google.api.http annotations to ManifestHistoryService and ApplyManifestService protos, enabling the manifest current view and history tabs to work via Connect-Web

Broken Pages Fixed

  • Positions detail: Fix BigInt crash (cannot mix bigint and other types) by safely converting number to BigInt before arithmetic
  • Manifest current view: Now works with HTTP annotations in place

Sidebar Navigation

  • Group 17 flat nav items into 3 sections: Operations (9), Configuration (7), Admin (1) with small uppercase section headers

Audit Trail

  • Replace raw fetch() with typed Connect-RPC client (clients.audit.listAuditEntries)
  • Add retry button on error state
  • Remove stub/coming-soon fallback (audit service is implemented)

Data Display

  • Party overview: Map numeric enums to human-readable labels (INDIVIDUAL, ORGANIZATION, ACTIVE, SUSPENDED), render status via StatusBadge
  • Accounts list: Add Party column showing EntityLink
  • Payments/Reconciliation: Add friendly empty state messages instead of generic "No results"

UX Polish

  • Saga dialog: Sticky footer so submit button is always visible when scrolling
  • Saga script editor: Replace plain <textarea> with StarlarkEditor (CodeMirror with Python mode, syntax highlighting, linting)

Test plan

  • npx tsc --noEmit - zero errors
  • npm run test -- --run - 1580/1580 tests passing (120 test files)
  • npx vite build - production build succeeds
  • go build ./... - Go backend compiles cleanly
  • Manual walkthrough on demo after deploy

Out of scope

  • MCP config dynamic capabilities
  • Platform monitoring page
  • Gateway mapping RPC dropdown
  • Client-side account code validation

- Add HTTP annotations to manifest history and apply protos
- Fix positions detail BigInt crash when rawAmount is a number
- Group sidebar navigation into Operations/Configuration/Admin sections
- Wire audit trail to Connect client instead of raw fetch
- Add party type/status enum labels on party overview
- Add party column to accounts list table
- Add empty state messaging for payments and reconciliation
- Replace saga script textarea with StarlarkEditor (CodeMirror)
- Make saga dialog footer sticky for better scroll UX
- Update tests for all changed components
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Adds HTTP REST bindings to protobuf RPCs, introduces an AuditService client, restructures sidebar navigation into groups, surfaces party IDs in accounts, updates party/status formatting, adds empty-table states, fixes BigInt balance handling, swaps a textarea for StarlarkEditor, and integrates the audit trail with real API clients.

Changes

Cohort / File(s) Summary
Proto HTTP Bindings
api/proto/meridian/control_plane/v1/apply_manifest_service.proto, api/proto/meridian/control_plane/v1/manifest_history_service.proto
Added google.api.http annotations and imported google/api/annotations.proto to expose RPCs as REST endpoints (ApplyManifest POST; manifest history GET endpoints).
API Client & Tests
frontend/src/api/clients.ts, frontend/src/test/api/clients.test.ts
Added AuditService to service client factory and updated tests to expect an audit client (client count increased).
Sidebar Navigation Restructure
frontend/src/components/layout/sidebar.tsx
Replaced flat nav with NavGroup-based TENANT_NAV_GROUPS, centralized visibility logic, and changed rendering to grouped sections with headers and separators.
Accounts: Party Column
frontend/src/features/accounts/hooks/use-accounts.ts, frontend/src/features/accounts/pages/index.tsx
Added optional partyId to account records and a new "Party" column rendering EntityLink when present.
Party Overview Formatting & Tests
frontend/src/features/parties/pages/tabs/overview-tab.tsx, frontend/src/features/parties/pages/tabs/overview-tab.test.tsx
Introduced PARTY_TYPE_LABELS/PARTY_STATUS_LABELS and formatting helpers; render formatted type/status with StatusBadge; updated tests accordingly.
Empty States for Tables
frontend/src/features/payments/pages/index.tsx, frontend/src/features/reconciliation/pages/index.tsx
Added contextual emptyState UIs for Payments and Reconciliation data tables when no rows exist.
Position Balance BigInt Fix
frontend/src/features/positions/pages/detail.tsx
Normalize rawAmount to BigInt for non-BigInt inputs to ensure consistent BigInt arithmetic in balance calculations.
Saga Editor Replacement & Tests
frontend/src/features/sagas/pages/create-saga-draft-dialog.tsx, frontend/src/features/sagas/pages/create-saga-draft-dialog.test.tsx
Replaced script textarea with StarlarkEditor, added handleScriptChange to update/clear errors, adjusted layout; tests updated to use test id and starter template behavior.
Audit Trail Integration & Tests
frontend/src/shared/audit-trail.tsx, frontend/src/shared/audit-trail.test.tsx, frontend/src/shared/index.ts
Replaced stub/fetch logic with useApiClients audit.listAuditEntries, added AuditOperation type and operation normalization, transformed entry shape handling, added Retry UI, removed AuditEntriesResponse export, and updated tests to mock the client instead of MSW.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: Frontend demo polish - broken pages, navigation, UX' accurately describes the main scope of changes: fixing broken pages, improving navigation grouping, and polish to user experience across the frontend.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering all major areas of changes including backend proto updates, broken page fixes, navigation improvements, audit trail refactoring, data display changes, and UX polish.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch frontend-demo-polish

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

@bjcoombs bjcoombs closed this Mar 5, 2026
@bjcoombs bjcoombs reopened this Mar 5, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/features/sagas/pages/create-saga-draft-dialog.tsx (1)

260-271: ⚠️ Potential issue | 🟠 Major

StarlarkEditor component lacks accessible label/error association support.

The StarlarkEditor component (lines 263–267) does not accept id, aria-labelledby, or aria-describedby props and does not forward them to its underlying elements. This prevents screen readers from reliably associating the Script label and validation error with the editor control.

To fix this accessibility issue, the StarlarkEditor component needs to be extended to:

  1. Accept id, aria-labelledby, and aria-describedby in its StarlarkEditorProps interface
  2. Forward these attributes to the editable element (the ref div at line 194–197)

Then the parent form can properly link the label and error messaging to the editor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/sagas/pages/create-saga-draft-dialog.tsx` around lines
260 - 271, The StarlarkEditor lacks accessibility attribute passing; update its
StarlarkEditorProps to include id, aria-labelledby, and aria-describedby
(optional strings), update the StarlarkEditor component signature to accept and
type those props, and forward them onto the editable element (the inner editable
div/element referenced by the component's ref) so the parent can link the label
and error via id/aria-describedby; ensure TypeScript types and any prop
spreading or ref-forwarding is adjusted so these attributes are applied to the
underlying DOM element.
frontend/src/test/api/clients.test.ts (1)

23-27: ⚠️ Potential issue | 🟡 Minor

Update the test title to match the new expected count.

The test name says “18 service clients” while the assertion now expects 19, which makes the spec misleading.

Proposed fix
-  it('returns an object with all 18 service clients', () => {
+  it('returns an object with all 19 service clients', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/test/api/clients.test.ts` around lines 23 - 27, Update the test
title string to reflect the new expected count: change the description in the
it(...) call currently reading "returns an object with all 18 service clients"
to state "returns an object with all 19 service clients" so it matches the
assertion that Object.keys(clients) has length 19; the test references
createServiceClients and makeTransport so only the title text needs updating.
🧹 Nitpick comments (4)
frontend/src/features/payments/pages/index.tsx (1)

86-91: Consider a shared TableEmptyState component to avoid repeated markup.

This pattern now exists in multiple pages and is likely to grow; extracting a small reusable component will keep copy/layout consistent and reduce drift.

♻️ Example refactor
-        emptyState={
-          <div className="flex flex-col items-center gap-2 py-12 text-muted-foreground">
-            <span className="text-sm font-medium">No payments yet</span>
-            <span className="text-xs">Payments will appear here once initiated.</span>
-          </div>
-        }
+        emptyState={
+          <TableEmptyState
+            title="No payments yet"
+            description="Payments will appear here once initiated."
+          />
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/payments/pages/index.tsx` around lines 86 - 91, Extract
the repeated empty-state markup into a reusable React component named
TableEmptyState that accepts props like title and subtitle; implement
TableEmptyState to render the exact JSX (container div with classes "flex
flex-col items-center gap-2 py-12 text-muted-foreground" and two spans for
title/subtitle) and replace the inline emptyState in the payments page with
<TableEmptyState title="No payments yet" subtitle="Payments will appear here
once initiated." />; update other pages using the same pattern to use
TableEmptyState to keep layout/strings consistent and export the component for
reuse.
frontend/src/features/positions/pages/detail.tsx (1)

36-48: Good fix for the BigInt arithmetic crash.

The change correctly ensures all numeric values are converted to BigInt before arithmetic operations, preventing the TypeError that occurs when mixing number and bigint types.

One edge case to be aware of: BigInt(rawAmount) will throw a RangeError if rawAmount is a floating-point number (e.g., 123.45), or a SyntaxError for non-numeric strings. If the upstream data could ever contain decimals, consider wrapping in a try-catch or truncating:

🛡️ Optional: Defensive conversion with fallback
     const rawAmount = entry.amount?.amount
     if (rawAmount === undefined || rawAmount === null) continue
-    const amt = typeof rawAmount === 'bigint' ? rawAmount : BigInt(rawAmount)
+    let amt: bigint
+    if (typeof rawAmount === 'bigint') {
+      amt = rawAmount
+    } else {
+      try {
+        amt = BigInt(Math.trunc(Number(rawAmount)))
+      } catch {
+        continue // Skip malformed entries
+      }
+    }
     const signed = entry.direction === 'CREDIT' ? amt : -amt
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/positions/pages/detail.tsx` around lines 36 - 48, The
conversion to BigInt for rawAmount (in the loop over entries that computes
provisionalTotal and availableTotal) can throw for floating-point or invalid
string inputs; update the conversion logic around rawAmount/amt (where you
currently call BigInt(rawAmount)) to defensively handle decimals/non-numeric
values: either attempt a safe parse inside a try/catch and skip or coerce floats
via truncation (e.g., Math.trunc or string split) before calling BigInt, and
ensure the existing signed, provisionalTotal and availableTotal updates still
only run when the conversion succeeded; keep checks for null/undefined and
preserve behavior tied to entry.direction and entry.qualityLevel.
frontend/src/components/layout/sidebar.tsx (1)

128-149: Consider extracting shared nav-item rendering.

The <Link> item markup is duplicated for tenant and platform items. Pulling it into a helper reduces maintenance overhead and avoids style drift.

Refactor sketch
+  function renderNavItem(item: NavItem) {
+    const Icon = item.icon
+    const isActive = currentPath === item.href
+    return (
+      <li key={item.href}>
+        <Link
+          to={item.href}
+          aria-current={isActive ? 'page' : undefined}
+          className={cn(
+            'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
+            isActive ? 'bg-gray-700 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
+          )}
+        >
+          <Icon className="size-4 shrink-0" />
+          {item.label}
+        </Link>
+      </li>
+    )
+  }
...
-                  {visibleItems.map((item) => {
-                    const Icon = item.icon
-                    const isActive = currentPath === item.href
-                    return (
-                      <li key={item.href}>
-                        <Link ...>
-                          <Icon className="size-4 shrink-0" />
-                          {item.label}
-                        </Link>
-                      </li>
-                    )
-                  })}
+                  {visibleItems.map(renderNavItem)}
...
-                  {PLATFORM_NAV_ITEMS.map((item) => {
-                    const Icon = item.icon
-                    const isActive = currentPath === item.href
-                    return (
-                      <li key={item.href}>
-                        <Link ...>
-                          <Icon className="size-4 shrink-0" />
-                          {item.label}
-                        </Link>
-                      </li>
-                    )
-                  })}
+                  {PLATFORM_NAV_ITEMS.map(renderNavItem)}

Also applies to: 162-183

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/layout/sidebar.tsx` around lines 128 - 149, The
Link/LI markup for menu entries is duplicated; extract it into a single helper
(e.g., a NavItem component or renderNavItem function) that accepts an item prop
(with href, label, icon), currentPath, and uses cn to build the class string,
sets aria-current, renders Icon (item.icon) and label, and returns the
<li><Link...>…</Link></li> structure; replace the two duplicated blocks that map
visibleItems (and the other list at the other location) to call this helper to
avoid style drift and centralize rendering.
frontend/src/features/sagas/pages/create-saga-draft-dialog.test.tsx (1)

154-162: Test intent says “submits successfully” but doesn’t assert submit side effect.

Right now this can pass even if the submit path breaks before mutation. Add a submit-side assertion to match the test name.

Suggested test tightening
     await user.click(screen.getByRole('button', { name: /create saga draft/i }))
     await waitFor(() => {
       expect(screen.queryByText(/script is required/i)).not.toBeInTheDocument()
+      expect(mockCreateSagaDraft).toHaveBeenCalledOnce()
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/sagas/pages/create-saga-draft-dialog.test.tsx` around
lines 154 - 162, The test currently only checks that the validation message is
absent but doesn't assert that the submit path was executed; update the "submits
with pre-filled script successfully (script validation)" test to assert a
submit-side effect: either spy/mock the saga-draft creation mutation or submit
handler used by renderDialog() (e.g., the createSagaDraft mutation or onSubmit)
and expect it to have been called with the expected payload after clicking the
"create saga draft" button, or assert a visible post-submit outcome (dialog
closed, success toast/message present). Ensure you reference the existing setup
calls (renderDialog(), user.click(screen.getByRole('button', { name: /create
saga draft/i })), and the form fields like screen.getByLabelText(/^name$/i))
when wiring the spy/assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/layout/sidebar.tsx`:
- Around line 123-125: The `first:mt-0` utility is ineffective on the inner div
because that div is the first child of every `<li>`, so every group's top margin
is being reset; move the `first:mt-0` Tailwind class from the inner div to the
`<li>` element (the element keyed by {group.label}) so only the very first group
`<li>` gets its top margin removed, leaving subsequent groups with the intended
spacing.

In `@frontend/src/features/accounts/pages/index.tsx`:
- Around line 43-49: The Party column's cell renderer (accessorKey: 'partyId',
cell: ({ row }) => ...) currently renders <EntityLink> which bubbles clicks to
the DataTable's row onClick (TableRow) and triggers
navigate('/accounts/{accountId}'); update the cell renderer to stop propagation
on interactive element clicks by wrapping EntityLink with an onClick handler
that calls event.stopPropagation(), or alternatively ensure the DataTable row
onClick ignores events originating from anchors/buttons; modify the cell for
'partyId' (where EntityLink is returned) to add an event handler that calls
event.stopPropagation() before letting the link navigate.

In `@frontend/src/features/parties/pages/tabs/overview-tab.tsx`:
- Around line 23-33: Change the string branch in formatPartyType and
formatPartyStatus to normalize enum-like strings (e.g. "PARTY_TYPE_ORGANIZATION"
→ "ORGANIZATION", "PARTY_STATUS_ACTIVE" → "ACTIVE") instead of returning them
unchanged: when typeof value === 'string', strip the "PARTY_TYPE_" or
"PARTY_STATUS_" prefix (or otherwise extract the portion after the last
underscore) and return that normalized label; fall back to the existing
PARTY_TYPE_LABELS[Number(value)]/PARTY_STATUS_LABELS[Number(value)] or
String(value) if no prefix match. Apply the same normalization to the other
occurrences of these helpers/branches referenced in the file.

In `@frontend/src/shared/audit-trail.tsx`:
- Around line 40-43: The toOperationName function currently casts any string to
AuditOperation blindly; update it to validate string inputs against the allowed
AuditOperation values before returning them (e.g., check membership in the
AuditOperation union or a set/enum of allowed names) and only return the string
if it matches, otherwise fall back to OPERATION_NAMES[op] or 'UPDATE'; reference
toOperationName, OPERATION_NAMES, and the AuditOperation type when making the
guard.

---

Outside diff comments:
In `@frontend/src/features/sagas/pages/create-saga-draft-dialog.tsx`:
- Around line 260-271: The StarlarkEditor lacks accessibility attribute passing;
update its StarlarkEditorProps to include id, aria-labelledby, and
aria-describedby (optional strings), update the StarlarkEditor component
signature to accept and type those props, and forward them onto the editable
element (the inner editable div/element referenced by the component's ref) so
the parent can link the label and error via id/aria-describedby; ensure
TypeScript types and any prop spreading or ref-forwarding is adjusted so these
attributes are applied to the underlying DOM element.

In `@frontend/src/test/api/clients.test.ts`:
- Around line 23-27: Update the test title string to reflect the new expected
count: change the description in the it(...) call currently reading "returns an
object with all 18 service clients" to state "returns an object with all 19
service clients" so it matches the assertion that Object.keys(clients) has
length 19; the test references createServiceClients and makeTransport so only
the title text needs updating.

---

Nitpick comments:
In `@frontend/src/components/layout/sidebar.tsx`:
- Around line 128-149: The Link/LI markup for menu entries is duplicated;
extract it into a single helper (e.g., a NavItem component or renderNavItem
function) that accepts an item prop (with href, label, icon), currentPath, and
uses cn to build the class string, sets aria-current, renders Icon (item.icon)
and label, and returns the <li><Link...>…</Link></li> structure; replace the two
duplicated blocks that map visibleItems (and the other list at the other
location) to call this helper to avoid style drift and centralize rendering.

In `@frontend/src/features/payments/pages/index.tsx`:
- Around line 86-91: Extract the repeated empty-state markup into a reusable
React component named TableEmptyState that accepts props like title and
subtitle; implement TableEmptyState to render the exact JSX (container div with
classes "flex flex-col items-center gap-2 py-12 text-muted-foreground" and two
spans for title/subtitle) and replace the inline emptyState in the payments page
with <TableEmptyState title="No payments yet" subtitle="Payments will appear
here once initiated." />; update other pages using the same pattern to use
TableEmptyState to keep layout/strings consistent and export the component for
reuse.

In `@frontend/src/features/positions/pages/detail.tsx`:
- Around line 36-48: The conversion to BigInt for rawAmount (in the loop over
entries that computes provisionalTotal and availableTotal) can throw for
floating-point or invalid string inputs; update the conversion logic around
rawAmount/amt (where you currently call BigInt(rawAmount)) to defensively handle
decimals/non-numeric values: either attempt a safe parse inside a try/catch and
skip or coerce floats via truncation (e.g., Math.trunc or string split) before
calling BigInt, and ensure the existing signed, provisionalTotal and
availableTotal updates still only run when the conversion succeeded; keep checks
for null/undefined and preserve behavior tied to entry.direction and
entry.qualityLevel.

In `@frontend/src/features/sagas/pages/create-saga-draft-dialog.test.tsx`:
- Around line 154-162: The test currently only checks that the validation
message is absent but doesn't assert that the submit path was executed; update
the "submits with pre-filled script successfully (script validation)" test to
assert a submit-side effect: either spy/mock the saga-draft creation mutation or
submit handler used by renderDialog() (e.g., the createSagaDraft mutation or
onSubmit) and expect it to have been called with the expected payload after
clicking the "create saga draft" button, or assert a visible post-submit outcome
(dialog closed, success toast/message present). Ensure you reference the
existing setup calls (renderDialog(), user.click(screen.getByRole('button', {
name: /create saga draft/i })), and the form fields like
screen.getByLabelText(/^name$/i)) when wiring the spy/assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3fa767c4-5d19-4a17-accc-46b7c16e1d35

📥 Commits

Reviewing files that changed from the base of the PR and between aeca864 and b3b5631.

📒 Files selected for processing (17)
  • api/proto/meridian/control_plane/v1/apply_manifest_service.proto
  • api/proto/meridian/control_plane/v1/manifest_history_service.proto
  • frontend/src/api/clients.ts
  • frontend/src/components/layout/sidebar.tsx
  • frontend/src/features/accounts/hooks/use-accounts.ts
  • frontend/src/features/accounts/pages/index.tsx
  • frontend/src/features/parties/pages/tabs/overview-tab.test.tsx
  • frontend/src/features/parties/pages/tabs/overview-tab.tsx
  • frontend/src/features/payments/pages/index.tsx
  • frontend/src/features/positions/pages/detail.tsx
  • frontend/src/features/reconciliation/pages/index.tsx
  • frontend/src/features/sagas/pages/create-saga-draft-dialog.test.tsx
  • frontend/src/features/sagas/pages/create-saga-draft-dialog.tsx
  • frontend/src/shared/audit-trail.test.tsx
  • frontend/src/shared/audit-trail.tsx
  • frontend/src/shared/index.ts
  • frontend/src/test/api/clients.test.ts

Comment thread frontend/src/components/layout/sidebar.tsx
Comment thread frontend/src/features/accounts/pages/index.tsx
Comment thread frontend/src/features/parties/pages/tabs/overview-tab.tsx
Comment thread frontend/src/shared/audit-trail.tsx
@claude
Copy link
Copy Markdown

claude Bot commented Mar 5, 2026

Claude Code Review

Commit: 7dc422d | CI: running (Proto Compilation + Validation passed)

Summary

Well-scoped demo polish PR fixing real user-facing issues (BigInt crash, broken pages, raw enum display, flat nav). The audit trail refactor from raw fetch() to typed Connect-RPC client is the most significant change and is done cleanly. Proto HTTP annotations are additive and non-breaking. All previous review suggestions have been addressed.

Risk Assessment

Area Level Detail
Blast radius Low Frontend-only UI fixes + additive proto annotations
Rollback Safe No migrations, no data transforms, purely additive
Scale Low No new queries or loops; audit fetches capped at pageSize: 50
Cross-system Low Proto annotations are additive; no backend logic changes
Migration N/A No database migrations

Findings

Severity Location Description Status
Note audit-trail.tsx:46 toOperationName defaults unknown/UNSPECIFIED (0) to UPDATE. Cosmetic only but slightly misleading for UNSPECIFIED entries. Informational
Note audit-trail.tsx:261-262 oldValues/newValues rendered as raw Protobuf Struct format (with fields/stringValue wrappers). Fine for demo but will want to flatten for production. Informational

Bot Review Notes

All 4 CodeRabbit threads on this PR have been addressed:

  • sidebar.tsx:125 (first:mt-0 ineffective) - Fixed via acc.visibleIndex > 0 && "mt-4" conditional
  • accounts/index.tsx:49 (EntityLink click bubbling) - Fixed with e.stopPropagation() wrapper. CodeRabbit itself marked this resolved.
  • overview-tab.tsx:33 (string enum prefix inconsistency) - Fixed via replace(/^PARTY_TYPE_/, "") and replace(/^PARTY_STATUS_/, "")
  • audit-trail.tsx:45 (blind-casting strings to AuditOperation) - Fixed via VALID_OPERATIONS.has(op) guard

Previously Flagged

Severity Location Description Status
Suggestion sidebar.tsx:125 first:mt-0 always applied Resolved (visibleIndex approach)
Suggestion overview-tab.tsx String enum prefix not stripped Resolved (replace added)
Suggestion audit-trail.tsx:45 Unchecked string cast to AuditOperation Resolved (Set guard added)

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

See summary comment. 3 inline suggestions.

Comment thread frontend/src/components/layout/sidebar.tsx
Comment thread frontend/src/features/parties/pages/tabs/overview-tab.tsx Outdated
Comment thread frontend/src/shared/audit-trail.tsx
- Sidebar: fix first:mt-0 ineffective on group headers, use reduce with
  index tracking instead
- Accounts: add stopPropagation on EntityLink to prevent row click
  hijacking party navigation
- Party overview: strip PARTY_TYPE_/PARTY_STATUS_ prefixes from string
  enums for consistent display
- Audit trail: validate string operations against known set before casting
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/shared/audit-trail.tsx (1)

275-283: ⚠️ Potential issue | 🟡 Minor

Sort order can be incorrect for entries in the same second.

Line 276 and Line 279 only compare timestamp.seconds; nanos is ignored. Two events in the same second can render out of order.

🛠️ Proposed fix
 const sorted = [...entries].sort((a, b) => {
   const aTime = typeof a.timestamp?.seconds === 'bigint'
     ? Number(a.timestamp.seconds)
     : (a.timestamp?.seconds ?? 0);
   const bTime = typeof b.timestamp?.seconds === 'bigint'
     ? Number(b.timestamp.seconds)
     : (b.timestamp?.seconds ?? 0);
-  return bTime - aTime;
+  if (bTime !== aTime) return bTime - aTime;
+  const aNanos = a.timestamp?.nanos ?? 0;
+  const bNanos = b.timestamp?.nanos ?? 0;
+  return bNanos - aNanos;
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/shared/audit-trail.tsx` around lines 275 - 283, The sort
currently only compares timestamp.seconds (in the sorting of sorted created from
entries) so events within the same second can be misordered; update the
comparator in the sorted = [...entries].sort(...) function to also compare
timestamp.nanos (handling possible bigint or undefined values the same way you
handle seconds) by converting both seconds and nanos to numbers (or otherwise
normalizing types), then return a descending order by first comparing seconds
and, if equal, comparing nanos (use bSec - aSec, then bNanos - aNanos) so events
in the same second are correctly ordered.
🧹 Nitpick comments (2)
frontend/src/features/parties/pages/tabs/overview-tab.tsx (1)

23-33: Consider extracting a shared enum-format helper to reduce drift.

Both formatters now share the same pattern (prefix-strip + numeric map + fallback). A small helper would keep future behavior changes in one place.

Refactor sketch
+function formatEnumValue(
+  value: unknown,
+  prefix: string,
+  labels: Record<number, string>,
+): string {
+  if (typeof value === 'string') return value.replace(new RegExp(`^${prefix}`), '')
+  if (typeof value === 'number') return labels[value] ?? String(value)
+  return String(value ?? '')
+}
+
 function formatPartyType(value: unknown): string {
-  if (typeof value === 'string') return value.replace(/^PARTY_TYPE_/, '')
-  if (typeof value === 'number') return PARTY_TYPE_LABELS[value] ?? String(value)
-  return String(value ?? '')
+  return formatEnumValue(value, 'PARTY_TYPE_', PARTY_TYPE_LABELS)
 }
 
 function formatPartyStatus(value: unknown): string {
-  if (typeof value === 'string') return value.replace(/^PARTY_STATUS_/, '')
-  if (typeof value === 'number') return PARTY_STATUS_LABELS[value] ?? String(value)
-  return String(value ?? '')
+  return formatEnumValue(value, 'PARTY_STATUS_', PARTY_STATUS_LABELS)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/parties/pages/tabs/overview-tab.tsx` around lines 23 -
33, Extract a shared helper (e.g., formatEnumValue or formatPrefixedEnum) that
encapsulates the common logic (strip a prefix from string values, map numeric
values via a labels map, and fallback to String(value ?? '')), then replace the
bodies of formatPartyType and formatPartyStatus to call that helper with the
appropriate prefix regex (e.g., /^PARTY_TYPE_/ and /^PARTY_STATUS_/) and maps
(PARTY_TYPE_LABELS, PARTY_STATUS_LABELS); ensure the helper signature accepts
(value: unknown, prefix: RegExp, labels?: Record<number,string>) so both
functions become thin wrappers delegating to it.
frontend/src/components/layout/sidebar.tsx (1)

127-149: Extract repeated nav-link markup into a shared renderer.

The tenant and platform item loops duplicate the same <li><Link>...</Link></li> structure. This is likely to drift over time (active-state, classes, aria handling). Consider centralizing it in a small helper/component.

♻️ Proposed refactor
+function NavLinkItem({
+  item,
+  currentPath,
+}: {
+  item: NavItem
+  currentPath: string
+}) {
+  const Icon = item.icon
+  const isActive = currentPath === item.href
+  return (
+    <li key={item.href}>
+      <Link
+        to={item.href}
+        aria-current={isActive ? 'page' : undefined}
+        className={cn(
+          'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
+          isActive ? 'bg-gray-700 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
+        )}
+      >
+        <Icon className="size-4 shrink-0" />
+        {item.label}
+      </Link>
+    </li>
+  )
+}
...
- {visibleItems.map((item) => {
-   const Icon = item.icon
-   const isActive = currentPath === item.href
-   return ( ... )
- })}
+ {visibleItems.map((item) => (
+   <NavLinkItem key={item.href} item={item} currentPath={currentPath} />
+ ))}
...
- {PLATFORM_NAV_ITEMS.map((item) => {
-   const Icon = item.icon
-   const isActive = currentPath === item.href
-   return ( ... )
- })}
+ {PLATFORM_NAV_ITEMS.map((item) => (
+   <NavLinkItem key={item.href} item={item} currentPath={currentPath} />
+ ))}

Also applies to: 163-185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/layout/sidebar.tsx` around lines 127 - 149, The
repeated list-item/link markup should be centralized into a small reusable
component (e.g., SidebarNavItem or NavLinkRenderer) to avoid duplication between
the tenant and platform loops: create a component that accepts props (item,
currentPath) or (href, label, icon, isActive) and encapsulates the Link,
aria-current logic, cn class composition, Icon rendering and key handling; then
replace the map body over visibleItems (and the similar loop at lines 163-185)
to render <SidebarNavItem .../> passing item and currentPath so active-state,
classes and accessibility remain consistent and maintained in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@frontend/src/shared/audit-trail.tsx`:
- Around line 275-283: The sort currently only compares timestamp.seconds (in
the sorting of sorted created from entries) so events within the same second can
be misordered; update the comparator in the sorted = [...entries].sort(...)
function to also compare timestamp.nanos (handling possible bigint or undefined
values the same way you handle seconds) by converting both seconds and nanos to
numbers (or otherwise normalizing types), then return a descending order by
first comparing seconds and, if equal, comparing nanos (use bSec - aSec, then
bNanos - aNanos) so events in the same second are correctly ordered.

---

Nitpick comments:
In `@frontend/src/components/layout/sidebar.tsx`:
- Around line 127-149: The repeated list-item/link markup should be centralized
into a small reusable component (e.g., SidebarNavItem or NavLinkRenderer) to
avoid duplication between the tenant and platform loops: create a component that
accepts props (item, currentPath) or (href, label, icon, isActive) and
encapsulates the Link, aria-current logic, cn class composition, Icon rendering
and key handling; then replace the map body over visibleItems (and the similar
loop at lines 163-185) to render <SidebarNavItem .../> passing item and
currentPath so active-state, classes and accessibility remain consistent and
maintained in one place.

In `@frontend/src/features/parties/pages/tabs/overview-tab.tsx`:
- Around line 23-33: Extract a shared helper (e.g., formatEnumValue or
formatPrefixedEnum) that encapsulates the common logic (strip a prefix from
string values, map numeric values via a labels map, and fallback to String(value
?? '')), then replace the bodies of formatPartyType and formatPartyStatus to
call that helper with the appropriate prefix regex (e.g., /^PARTY_TYPE_/ and
/^PARTY_STATUS_/) and maps (PARTY_TYPE_LABELS, PARTY_STATUS_LABELS); ensure the
helper signature accepts (value: unknown, prefix: RegExp, labels?:
Record<number,string>) so both functions become thin wrappers delegating to it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9a199103-91dc-49fd-a068-e8b3bcc8edd2

📥 Commits

Reviewing files that changed from the base of the PR and between b3b5631 and 7dc422d.

📒 Files selected for processing (5)
  • frontend/src/components/layout/sidebar.tsx
  • frontend/src/features/accounts/pages/index.tsx
  • frontend/src/features/parties/pages/tabs/overview-tab.test.tsx
  • frontend/src/features/parties/pages/tabs/overview-tab.tsx
  • frontend/src/shared/audit-trail.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/features/accounts/pages/index.tsx
  • frontend/src/features/parties/pages/tabs/overview-tab.test.tsx

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

All previous suggestions addressed. No blocking issues found. Two informational notes in summary comment (UNSPECIFIED enum default, raw Struct display). All CodeRabbit threads resolved.

@bjcoombs bjcoombs merged commit 8bdaf3d into develop Mar 5, 2026
47 checks passed
@bjcoombs bjcoombs deleted the frontend-demo-polish branch March 5, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant