Skip to content

feat(frontend): create data hooks for all feature modules#1423

Merged
bjcoombs merged 7 commits intodevelopfrom
034-frontend-alignment--10--feature-data-hooks
Mar 5, 2026
Merged

feat(frontend): create data hooks for all feature modules#1423
bjcoombs merged 7 commits intodevelopfrom
034-frontend-alignment--10--feature-data-hooks

Conversation

@bjcoombs
Copy link
Copy Markdown
Collaborator

@bjcoombs bjcoombs commented Mar 5, 2026

Summary

Extracts inline useQuery calls from all feature module page components into reusable data hooks, following the pattern established in features/accounts/hooks/use-accounts.ts (PR #1413).

  • Creates data hooks for 8 feature modules: payments, ledger, positions, parties, reconciliation, sagas, market-data, and internal-accounts
  • Updates all page components to consume the new hooks instead of inline query logic
  • Converts legacy useAuthenticatedFetch patterns (payments, reconciliation) to Connect-RPC clients

Changes

New hook files (16 files)

Each module gets a hooks/ directory with a hook file and barrel export:

  • payments/hooks/use-payments.tsusePaymentsTable(), usePaymentDetail()
  • ledger/hooks/use-ledger.tsuseBookingLogsTable(), useBookingLogDetail(), useLedgerPostings()
  • positions/hooks/use-positions.tsusePositionLogsTable(), usePositionLogDetail()
  • parties/hooks/use-parties.tsusePartiesTable(), usePartyDetail(), usePartyAssociations()
  • reconciliation/hooks/use-reconciliation.tsuseReconciliationRunsTable(), useReconciliationRunDetail()
  • sagas/hooks/use-sagas.tsuseSagasTable(), useSagaDetail(), useActiveSaga()
  • market-data/hooks/use-market-data.tsuseDatasetsTable(), useDatasetDetail(), useDatasetObservations()
  • internal-accounts/hooks/use-internal-accounts.tsuseInternalAccountsTable(), useInternalAccountDetail()

Modified page files (17 files)

All page components updated to import and use the new hooks, removing inline query logic, helper functions, and unused imports.

Patterns

All hooks follow the established accounts pattern:

  • Table hooks: Return { queryKey, queryFn, tenantSlug } for DataTable component
  • Detail hooks: Return useQuery() result directly
  • Tenant-scoped query keys from @/lib/query-keys.ts
  • Connect-RPC clients from @/api/context
  • useTenantSlug() for tenant scoping

Task Master

034-frontend-alignment.10 — Create data hooks for all feature modules

Test plan

  • Verify each list page renders data via DataTable (payments, ledger, positions, parties, reconciliation, sagas, market-data, internal-accounts)
  • Verify each detail page loads and displays entity data
  • Verify TypeScript compilation passes (npx tsc --noEmit)
  • Verify no regressions in existing page functionality

bjcoombs added 2 commits March 5, 2026 09:13
Add reusable query hooks following the established pattern from
features/accounts/hooks/use-accounts.ts:

- payments: usePaymentsTable, usePaymentDetail
- ledger: useBookingLogsTable, useBookingLogDetail, useLedgerPostings
- positions: usePositionLogsTable, usePositionLogDetail
- parties: usePartiesTable, usePartyDetail, usePartyAssociations
- reconciliation: useReconciliationRunsTable, useReconciliationRunDetail
- sagas: useSagasTable, useSagaDetail, useActiveSaga
- market-data: useDatasetsTable, useDatasetDetail, useDatasetObservations
- internal-accounts: useInternalAccountsTable, useInternalAccountDetail

Each hook uses tenant-scoped query keys, Connect-RPC clients via
useApiClients, and useTenantSlug for tenant scoping.
Replace inline useQuery calls and raw fetch patterns in page components
with the reusable hooks created in the previous commit. This removes
duplicated data-fetching logic from page files and centralizes it in
the hooks layer.

Pages updated:
- payments: list + detail
- ledger: list + booking log detail
- positions: list + detail
- parties: list + detail + associations tab
- reconciliation: list
- sagas: list + detail
- market-data: list + dataset detail
- internal-accounts: list + detail
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduce tenant-scoped React Query hooks across multiple features, add barrel re-exports, refactor pages/components to consume the new hooks instead of inline API/tenant logic, and update tests to mock the new hooks and tenant context.

Changes

Cohort / File(s) Summary
Internal Accounts
frontend/src/features/internal-accounts/hooks/use-internal-accounts.ts, frontend/src/features/internal-accounts/hooks/index.ts, frontend/src/features/internal-accounts/pages/[accountId].tsx, frontend/src/features/internal-accounts/pages/index.tsx
New hooks useInternalAccountsTable and useInternalAccountDetail; index re-export added; pages replaced inline API/tenant logic with the hooks.
Ledger
frontend/src/features/ledger/hooks/use-ledger.ts, frontend/src/features/ledger/hooks/index.ts, frontend/src/features/ledger/pages/booking-log-detail.tsx, frontend/src/features/ledger/pages/booking-log-header.tsx, frontend/src/features/ledger/pages/index.tsx, frontend/src/features/ledger/pages/booking-log-header.test.tsx
Added useBookingLogsTable, useBookingLogDetail, useLedgerPostings and index re-export; pages now use hooks; currency field usages changed (e.g., baseCurrencyinstrumentCode); tests updated accordingly.
Market Data
frontend/src/features/market-data/hooks/use-market-data.ts, frontend/src/features/market-data/hooks/index.ts, frontend/src/features/market-data/pages/[datasetCode].tsx, frontend/src/features/market-data/pages/index.tsx
Added useDatasetsTable, useDatasetDetail, useDatasetObservations and barrel re-export; list/detail/observations pages now use hooks.
Parties
frontend/src/features/parties/hooks/use-parties.ts, frontend/src/features/parties/hooks/index.ts, frontend/src/features/parties/pages/[partyId].tsx, frontend/src/features/parties/pages/index.tsx, frontend/src/features/parties/pages/tabs/associations-tab.tsx, frontend/src/features/parties/pages/[partyId].test.tsx, frontend/src/features/parties/pages/tabs/associations-tab.test.tsx
Added usePartiesTable, usePartyDetail, usePartyAssociations and index re-export; pages refactored to use hooks; tests and tenant mocks updated; removed in-file label mappers.
Payments
frontend/src/features/payments/hooks/use-payments.ts, frontend/src/features/payments/hooks/index.ts, frontend/src/features/payments/pages/index.tsx, frontend/src/features/payments/pages/payment-detail.tsx, frontend/src/features/payments/pages/payment-detail.test.tsx, frontend/src/features/payments/pages/payments-list.test.tsx
Added usePaymentsTable and usePaymentDetail and re-export; pages switched to hook-based data; tests replaced fetch mocks with hook-return mocks and added tenant-context mocks.
Positions
frontend/src/features/positions/hooks/use-positions.ts, frontend/src/features/positions/hooks/index.ts, frontend/src/features/positions/pages/detail.tsx, frontend/src/features/positions/pages/index.tsx, frontend/src/features/positions/pages/detail.test.tsx, frontend/src/features/positions/pages/index.test.tsx
Added usePositionLogsTable and usePositionLogDetail and re-export; pages now use hooks; tests add tenant-context mocks.
Reconciliation
frontend/src/features/reconciliation/hooks/use-reconciliation.ts, frontend/src/features/reconciliation/hooks/index.ts, frontend/src/features/reconciliation/pages/index.tsx, frontend/src/features/reconciliation/pages/index.test.tsx
Added useReconciliationRunsTable and useReconciliationRunDetail and re-export; page consumes hook-provided queryKey/queryFn; tests mock the hook/queryFn and adapt data shape.
Sagas
frontend/src/features/sagas/hooks/use-sagas.ts, frontend/src/features/sagas/hooks/index.ts, frontend/src/features/sagas/pages/detail.tsx, frontend/src/features/sagas/pages/index.tsx, frontend/src/features/sagas/pages/detail.test.tsx, frontend/src/features/sagas/pages/index.test.tsx
Added useSagasTable, useSagaDetail, useActiveSaga and barrel re-export; pages replaced inline queries with hooks; tests add tenant-context mocks.
Barrel index additions
frontend/src/features/*/hooks/index.ts (multiple)
Added re-exports across features to expose the new hooks via each feature's hooks index.
Tests & Tenant Mocks
frontend/src/features/*/*.test.tsx (multiple)
Many tests updated to mock new hooks and tenant-context (useTenantSlug / useTenantContext); fetch-based mocks replaced by hook/queryFn mocks and test data shapes adjusted.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Page as Client Page
participant Hook as Feature Hook
participant Tenant as Tenant Hook
participant API as useApiClients
participant Server as Backend API

Page->>Hook: call (e.g. usePaymentsTable / useXDetail)
Hook->>Tenant: read tenantSlug
Hook->>API: call list/retrieve with tenantSlug & params
API->>Server: HTTP request
Server-->>API: response (items / detail)
API-->>Hook: return data
Hook-->>Page: expose queryKey, queryFn or useQuery result

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.85% 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 'feat(frontend): create data hooks for all feature modules' directly and clearly summarizes the main change: extracting inline queries into reusable data hooks across multiple frontend feature modules.
Description check ✅ Passed The description is detailed and directly related to the changeset, explaining the purpose, scope, patterns, and test plan for creating data hooks across eight feature modules.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 034-frontend-alignment--10--feature-data-hooks

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

@claude
Copy link
Copy Markdown

claude Bot commented Mar 5, 2026

Claude Code Review

Commit: 1f88bf7b | CI: Frontend Tests, Lint, Build all passing; some backend checks still running

Summary

Clean, well-executed extraction of inline useQuery calls into reusable data hooks across 8 feature modules, following the established useAccountsTable/useAccountDetail pattern from PR #1413. The refactoring is mechanical and consistent.

Notable improvements beyond extraction:

  • Multi-tenant query key scoping added to positions (was ["positions"]), sagas (was ["starlark-config"]), and parties detail (was ["party", id]) -- all now properly tenant-scoped via tenantKeys.*
  • useActiveSaga error handling improved -- the old code silently swallowed ALL errors (catch { return null }), the new code correctly distinguishes ConnectError.NotFound (returns null) from other errors (rethrows)
  • Hardcoded GBP fallback removed in booking-log-detail.tsx -- replaced with instrumentCode || postingCurrency || "", eliminating incorrect currency display for non-GBP logs
  • Legacy useAuthenticatedFetch patterns (payments, reconciliation) converted to Connect-RPC clients

This satisfies the requirement for task 034-frontend-alignment.10 -- creating data hooks for all feature modules.

Risk Assessment

Area Level Detail
Blast radius Low Frontend-only refactoring; no API contract changes
Rollback Safe Pure refactoring, easily revertable
Scale Low No new queries; same data fetching patterns
Cross-system Low No backend changes; same RPC calls
Migration N/A No database changes

Findings

No blocking issues found. The extraction is faithful to the original logic with the improvements noted above.

Severity Location Description Status
Note Multiple hooks Query key changes (positions, sagas, parties) improve tenant isolation but will invalidate existing cached data on deploy -- expected and harmless Info
Note use-payments.ts:79-80 sagaSteps/compensationSteps use as PaymentOrderDetail['sagaSteps'] type assertions on the array fields. Acceptable given the proto shapes match the local type definition and all scalar fields are mapped explicitly Info

Bot Review Notes

All bot review threads are now resolved. My assessment of the previously flagged concerns:

  • CodeRabbit: "unsafe double-cast" in payments/reconciliation hooks -- Inaccurate for current code. Both hooks map fields individually with only targeted as casts on nested arrays, not as unknown as T double casts.
  • CodeRabbit: hardcoded GBP fallback -- Fixed in this PR. The new currency derivation logic (instrumentCode || postingCurrency || "") is correct.
  • CodeRabbit: NaN filter parsing (market-data, positions) -- Already handled: market-data uses parseInt() || 0, positions uses !Number.isNaN() guard.
  • CodeRabbit: useActiveSaga error handling -- Properly implemented with ConnectError/Code.NotFound check.
  • CodeRabbit: unused mock variable in payment-detail.test.tsx -- Lint is passing, indicating this was resolved.
  • CodeRabbit: associations-tab unused data -- Acknowledged with TODO comment; placeholder behavior per incremental development approach.

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 for full review. 4 inline suggestions -- all non-blocking improvements.

Comment thread frontend/src/features/payments/hooks/use-payments.ts
Comment thread frontend/src/features/parties/hooks/use-parties.ts
Comment thread frontend/src/features/positions/hooks/use-positions.ts
Comment thread frontend/src/features/reconciliation/hooks/use-reconciliation.ts
coderabbitai[bot]
coderabbitai Bot previously requested changes 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: 9

🧹 Nitpick comments (5)
frontend/src/features/internal-accounts/pages/index.tsx (1)

11-19: Consider exporting InternalAccountRow from the hooks module to avoid duplication.

The InternalAccountRow interface is defined identically in both use-internal-accounts.ts (lines 8-16) and this page component. If the shape changes in the hook, this local definition could become stale.

♻️ Suggested refactor

In use-internal-accounts.ts, export the interface:

-interface InternalAccountRow {
+export interface InternalAccountRow {

Then in index.tsx:

-import { useInternalAccountsTable } from '../hooks'
+import { useInternalAccountsTable, type InternalAccountRow } from '../hooks'
 import { CreateInternalAccountDialog } from './create-internal-account-dialog'

-interface InternalAccountRow {
-  accountId: string
-  accountCode: string
-  name: string
-  behaviorClass: string
-  accountStatus: number
-  instrumentCode: string
-  createdAt?: { seconds: bigint | number; nanos?: number } | null
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/internal-accounts/pages/index.tsx` around lines 11 -
19, The page defines an InternalAccountRow interface duplicate of the one in
use-internal-accounts.ts; export the existing InternalAccountRow from
use-internal-accounts.ts (add an export on the interface) and remove the local
definition in frontend/src/features/internal-accounts/pages/index.tsx, then
import { InternalAccountRow } from the hooks module and use that type for the
page component to keep a single source of truth.
frontend/src/features/internal-accounts/pages/[accountId].tsx (1)

178-190: Consider extracting InternalAccountTransactions query to a hook for consistency.

The main account detail fetch now uses useInternalAccountDetail, but the transactions query within InternalAccountTransactions still uses inline useQuery. This is a minor inconsistency with the hook-based pattern, but acceptable if intentionally scoped out.

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

In `@frontend/src/features/internal-accounts/pages/`[accountId].tsx around lines
178 - 190, Extract the inline useQuery in InternalAccountTransactions into a
reusable hook (e.g. useInternalAccountTransactions) that accepts tenantSlug,
accountId, and optional instrumentCode, calls
clients.financialAccounting.listLedgerPostings with the same pagination and
queryKey logic, and returns { data, isLoading, isError } so
InternalAccountTransactions simply calls
useInternalAccountTransactions(accountId, instrumentCode) and renders from the
hook result; ensure the hook uses useApiClients and useTenantContext (or accepts
tenantSlug) and preserves the enabled: !!accountId behavior and queryKey
structure (tenantKeys.internalAccount(...), 'postings').
frontend/src/features/parties/hooks/use-parties.ts (1)

38-64: Missing tenantSlug in useCallback dependencies.

The queryFn callback depends on clients.party but not on tenantSlug. While tenantSlug isn't directly used inside queryFn, the queryKey returned alongside it does depend on tenantSlug. If tenantSlug changes, consumers using the stale queryFn with a new queryKey could lead to cache inconsistencies.

This is likely fine since DataTable presumably re-renders when tenantSlug changes, but worth verifying the pattern is intentional.

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

In `@frontend/src/features/parties/hooks/use-parties.ts` around lines 38 - 64, The
queryFn passed to useCallback should include tenantSlug in its dependency array
to avoid a stale callback when tenantSlug changes; update the useCallback
dependency list for queryFn to include tenantSlug (in addition to
clients.party), since queryKey (from tenantKeys.parties(tenantSlug)) is returned
alongside queryFn and must stay consistent with tenantSlug changes.
frontend/src/features/reconciliation/hooks/use-reconciliation.ts (1)

6-7: Decouple hook types from page modules

Line 6 and Line 7 import data contracts from ../pages/*, which creates an inverted dependency (hooks depending on pages). Move these interfaces to a feature-level types.ts (or API model module) and import from there.

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

In `@frontend/src/features/reconciliation/hooks/use-reconciliation.ts` around
lines 6 - 7, The hook currently imports ReconciliationRun and
ReconciliationRunDetail from page modules, creating an inverted dependency;
extract those interfaces into a feature-level types file (e.g., create a
types.ts next to use-reconciliation.ts) and move the ReconciliationRun and
ReconciliationRunDetail definitions there, then update use-reconciliation.ts to
import the types from that new feature module instead of ../pages/*; ensure any
other files that referenced the old page-local types are updated to import from
the new types module to keep contracts consistent.
frontend/src/features/positions/hooks/use-positions.ts (1)

7-7: Move shared types out of page modules.

hooks/use-positions.ts importing FinancialPositionLog from pages/index couples the data layer to the UI layer and makes reuse brittle. Please move these interfaces to a dedicated feature types module (or generated API types) and import from there.

♻️ Suggested refactor
- import type { FinancialPositionLog } from '../pages/index'
+ import type { FinancialPositionLog } from '../types'
- // frontend/src/features/positions/pages/index.tsx
- export interface FinancialPositionLog { ... }
+ // frontend/src/features/positions/types.ts
+ export interface FinancialPositionLog { ... }

+ // frontend/src/features/positions/pages/index.tsx
+ import type { FinancialPositionLog } from '../types'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/positions/hooks/use-positions.ts` at line 7, The hook
use-positions.ts currently imports the UI page type FinancialPositionLog from
pages/index which couples data to presentation; create a new feature type module
(e.g., frontend/src/features/positions/types.ts or move into your generated API
types) that exports FinancialPositionLog (and any related interfaces), update
use-positions.ts to import FinancialPositionLog from that new types module, and
update pages/index to import the type from the same new module so both hook
(usePositions) and page consume the shared type instead of pages/index.
🤖 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/features/ledger/hooks/use-ledger.ts`:
- Around line 63-70: The list hook maps response.financialBookingLogs into items
using instrumentCode: getInstrumentCode(log.baseInstrumentCode) while the detail
hook treats instrumentCode as currency—this causes a data-shape drift; update
the mapping and types so instrumentCode consistently represents the instrument
and add a new currencyCode field: extend the FinancialBookingLog type/interface
to include currencyCode, populate currencyCode from the appropriate source in
the same mapping where items is built (alongside id, status, etc.), keep
instrumentCode assigned via getInstrumentCode(log.baseInstrumentCode), and
update any detail hook/consumers to read currencyCode for currency displays and
totals instead of instrumentCode.

In `@frontend/src/features/ledger/pages/booking-log-detail.tsx`:
- Around line 157-159: The code currently falls back to a hardcoded 'GBP' when
data.instrumentCode is missing; change the fallback to derive the currency from
the posting data instead (e.g., use data?.postings[0]?.currency or another
canonical field) and pass that into computeTotals so totals use a
posting-derived currency rather than always 'GBP'; update the variable
assignment for currency near the postings/computeTotals usage (refer to
currency, postings, and computeTotals) to implement this safer fallback.

In `@frontend/src/features/market-data/hooks/use-market-data.ts`:
- Around line 31-32: statusFilter and categoryFilter currently use parseInt
which can yield NaN and that NaN is sent to listDataSets; change the parsing so
any invalid parse coerces to 0 (e.g. parseInt result stored to a temp variable
then set to 0 if Number.isNaN(value)) before building the RPC request; update
the logic around params.filters?.status and params.filters?.category so you pass
only numeric 0 or a valid integer to listDataSets.

In `@frontend/src/features/parties/pages/tabs/associations-tab.tsx`:
- Line 15: The component is calling usePartyAssociations(partyId) but only
destructures isLoading and always renders EmptyState; update the hook usage to
also destructure the returned data (e.g., const { data, isLoading } =
usePartyAssociations(partyId)) and change the render logic to: when isLoading
show a loader, when data exists and has items render the associations (map over
data or render an AssociationsList component), otherwise render EmptyState; if
always showing EmptyState was intentional, add a TODO comment next to
usePartyAssociations to document that placeholder behavior.

In `@frontend/src/features/payments/hooks/use-payments.ts`:
- Around line 60-67: The queryFn in use-payments.ts currently returns the
protobuf PaymentOrder via an unsafe double-cast to PaymentOrderDetail; replace
that cast with explicit mapping: take response.paymentOrder (PaymentOrder) and
construct a PaymentOrderDetail object by extracting amount.value and
currency_code from its MoneyAmount, converting the protobuf status enum to a
string, and populating sagaSteps and compensationSteps (either by fetching the
source that populates them or returning empty arrays/null with a comment if they
are provided elsewhere), so components like SagaTimeline in payment-detail.tsx
receive the expected fields; update the queryFn return to this constructed
PaymentOrderDetail rather than p as unknown as PaymentOrderDetail.

In `@frontend/src/features/positions/hooks/use-positions.ts`:
- Around line 23-27: Guard the parsed status filter before casting to
TransactionStatus: check that params.filters?.status (statusValue) is a valid
numeric value (e.g., Number(statusValue) yields a finite integer within the
TransactionStatus enum range) and only then cast; otherwise pass
TransactionStatus.UNSPECIFIED to
clients.positionKeeping.listFinancialPositionLogs. Update the logic in
use-positions.ts around the statusValue handling to validate/parsesafe the value
(reject NaN/Infinity/invalid enum numbers) and fallback to
TransactionStatus.UNSPECIFIED when invalid.

In `@frontend/src/features/reconciliation/hooks/use-reconciliation.ts`:
- Around line 59-65: The queryFn in use-reconciliation.ts currently bypasses
type safety with response as unknown as ReconciliationRunDetail; instead, remove
the unsafe double-cast and transform/validate the RPC payload returned by
clients.accountReconciliation.getReconciliationRun into a proper
ReconciliationRunDetail using the same normalization/mapping logic the table
hook uses (i.e., the mapper/normalizer that converts RPC payloads and applies
defaults and enum mapping), returning null on missing response and returning the
normalized ReconciliationRunDetail; ensure you reference runId and keep the
function signature intact.

In `@frontend/src/features/sagas/hooks/use-sagas.ts`:
- Around line 60-67: The queryFn in use-sagas.ts currently swallows all errors
from sagaRegistry.getActiveSaga and returns null; change it to only convert
not-found errors to null and rethrow all others: import ConnectError and Code
from '@connectrpc/connect', implement a helper like isNotFoundError(error) that
checks error instanceof ConnectError && error.code === Code.NotFound, call
getActiveSaga inside try/catch in queryFn, return null when
isNotFoundError(error) is true, otherwise rethrow the caught error so React
Query can surface it; reference sagaRegistry.getActiveSaga and the queryFn
wrapper when making this change.

In `@frontend/src/features/sagas/pages/detail.tsx`:
- Around line 134-139: The tenant-scoped saga detail cache (fetched by
useSagaDetail with key ['tenants', tenantSlug, 'sagas', definitionId]) is not
being invalidated after Activate/Deprecate; update the mutation success handlers
that currently only invalidate ['starlark-config', definitionId] to also
invalidate the tenant-scoped query key ['tenants', tenantSlug, 'sagas',
definitionId] (and the tenant sagas list ['tenants', tenantSlug, 'sagas'] if
present) so useSagaDetail and useActiveSaga see fresh data; locate the handlers
referenced in this file (the Activate/Deprecate mutation success callbacks) and
add the extra invalidateQueries calls using tenantSlug and definitionId.

---

Nitpick comments:
In `@frontend/src/features/internal-accounts/pages/`[accountId].tsx:
- Around line 178-190: Extract the inline useQuery in
InternalAccountTransactions into a reusable hook (e.g.
useInternalAccountTransactions) that accepts tenantSlug, accountId, and optional
instrumentCode, calls clients.financialAccounting.listLedgerPostings with the
same pagination and queryKey logic, and returns { data, isLoading, isError } so
InternalAccountTransactions simply calls
useInternalAccountTransactions(accountId, instrumentCode) and renders from the
hook result; ensure the hook uses useApiClients and useTenantContext (or accepts
tenantSlug) and preserves the enabled: !!accountId behavior and queryKey
structure (tenantKeys.internalAccount(...), 'postings').

In `@frontend/src/features/internal-accounts/pages/index.tsx`:
- Around line 11-19: The page defines an InternalAccountRow interface duplicate
of the one in use-internal-accounts.ts; export the existing InternalAccountRow
from use-internal-accounts.ts (add an export on the interface) and remove the
local definition in frontend/src/features/internal-accounts/pages/index.tsx,
then import { InternalAccountRow } from the hooks module and use that type for
the page component to keep a single source of truth.

In `@frontend/src/features/parties/hooks/use-parties.ts`:
- Around line 38-64: The queryFn passed to useCallback should include tenantSlug
in its dependency array to avoid a stale callback when tenantSlug changes;
update the useCallback dependency list for queryFn to include tenantSlug (in
addition to clients.party), since queryKey (from tenantKeys.parties(tenantSlug))
is returned alongside queryFn and must stay consistent with tenantSlug changes.

In `@frontend/src/features/positions/hooks/use-positions.ts`:
- Line 7: The hook use-positions.ts currently imports the UI page type
FinancialPositionLog from pages/index which couples data to presentation; create
a new feature type module (e.g., frontend/src/features/positions/types.ts or
move into your generated API types) that exports FinancialPositionLog (and any
related interfaces), update use-positions.ts to import FinancialPositionLog from
that new types module, and update pages/index to import the type from the same
new module so both hook (usePositions) and page consume the shared type instead
of pages/index.

In `@frontend/src/features/reconciliation/hooks/use-reconciliation.ts`:
- Around line 6-7: The hook currently imports ReconciliationRun and
ReconciliationRunDetail from page modules, creating an inverted dependency;
extract those interfaces into a feature-level types file (e.g., create a
types.ts next to use-reconciliation.ts) and move the ReconciliationRun and
ReconciliationRunDetail definitions there, then update use-reconciliation.ts to
import the types from that new feature module instead of ../pages/*; ensure any
other files that referenced the old page-local types are updated to import from
the new types module to keep contracts consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40243401-b407-46af-a32b-dae1c49c30e3

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb7257 and 6d1dc3f.

📒 Files selected for processing (33)
  • frontend/src/features/internal-accounts/hooks/index.ts
  • frontend/src/features/internal-accounts/hooks/use-internal-accounts.ts
  • frontend/src/features/internal-accounts/pages/[accountId].tsx
  • frontend/src/features/internal-accounts/pages/index.tsx
  • frontend/src/features/ledger/hooks/index.ts
  • frontend/src/features/ledger/hooks/use-ledger.ts
  • frontend/src/features/ledger/pages/booking-log-detail.tsx
  • frontend/src/features/ledger/pages/booking-log-header.tsx
  • frontend/src/features/ledger/pages/index.tsx
  • frontend/src/features/market-data/hooks/index.ts
  • frontend/src/features/market-data/hooks/use-market-data.ts
  • frontend/src/features/market-data/pages/[datasetCode].tsx
  • frontend/src/features/market-data/pages/index.tsx
  • frontend/src/features/parties/hooks/index.ts
  • frontend/src/features/parties/hooks/use-parties.ts
  • frontend/src/features/parties/pages/[partyId].tsx
  • frontend/src/features/parties/pages/index.tsx
  • frontend/src/features/parties/pages/tabs/associations-tab.tsx
  • frontend/src/features/payments/hooks/index.ts
  • frontend/src/features/payments/hooks/use-payments.ts
  • frontend/src/features/payments/pages/index.tsx
  • frontend/src/features/payments/pages/payment-detail.tsx
  • frontend/src/features/positions/hooks/index.ts
  • frontend/src/features/positions/hooks/use-positions.ts
  • frontend/src/features/positions/pages/detail.tsx
  • frontend/src/features/positions/pages/index.tsx
  • frontend/src/features/reconciliation/hooks/index.ts
  • frontend/src/features/reconciliation/hooks/use-reconciliation.ts
  • frontend/src/features/reconciliation/pages/index.tsx
  • frontend/src/features/sagas/hooks/index.ts
  • frontend/src/features/sagas/hooks/use-sagas.ts
  • frontend/src/features/sagas/pages/detail.tsx
  • frontend/src/features/sagas/pages/index.tsx

Comment thread frontend/src/features/ledger/hooks/use-ledger.ts
Comment thread frontend/src/features/ledger/pages/booking-log-detail.tsx
Comment thread frontend/src/features/market-data/hooks/use-market-data.ts Outdated
Comment thread frontend/src/features/parties/pages/tabs/associations-tab.tsx
Comment thread frontend/src/features/payments/hooks/use-payments.ts
Comment thread frontend/src/features/positions/hooks/use-positions.ts Outdated
Comment thread frontend/src/features/reconciliation/hooks/use-reconciliation.ts
Comment thread frontend/src/features/sagas/hooks/use-sagas.ts
Update 10 test files to work with the refactored pages that now use
data hooks instead of inline query logic:

- Add @/hooks/use-tenant-context mock for hooks that need tenantSlug
- Switch from useClients to useApiClients mocks where hooks changed
- Mock ../hooks module for payments and reconciliation tests
- Update booking-log-header test to use instrumentCode (was baseCurrency)
@bjcoombs bjcoombs dismissed coderabbitai[bot]’s stale review March 5, 2026 09:24

Stale review — test fixes pushed

Comment thread frontend/src/features/payments/pages/payment-detail.test.tsx Fixed
Remove unused useClients import and mockPaymentDetailQuery variable
to fix lint errors.
coderabbitai[bot]
coderabbitai Bot previously requested changes 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: 2

🧹 Nitpick comments (3)
frontend/src/features/ledger/pages/booking-log-header.test.tsx (1)

40-42: Test name should be updated to reflect the field change.

The test is named "renders the base currency" but the mock data now uses instrumentCode. Consider updating the test description for consistency and maintainability.

📝 Suggested fix
-  it('renders the base currency', () => {
+  it('renders the instrument code', () => {
     render(<BookingLogHeader bookingLog={mockBookingLog} />)
     expect(screen.getByText('GBP')).toBeInTheDocument()
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/ledger/pages/booking-log-header.test.tsx` around lines
40 - 42, Rename the test description to reflect the field change: update the
it(...) string in booking-log-header.test.tsx for the test that renders
BookingLogHeader with mockBookingLog (the test currently reads "renders the base
currency") to something like "renders the instrument code" or "renders the
instrument" so it matches the mockBookingLog's instrumentCode field and keeps
the test name consistent with the assertion using screen.getByText('GBP').
frontend/src/features/parties/pages/tabs/associations-tab.test.tsx (1)

68-72: Reduce duplicated useApiClients overrides with a shared helper.

Line 68 and the other override blocks repeat near-identical setup, which makes future edits error-prone. Consider reusing the default mockPartyClient and only overriding retrieveAssociations per scenario.

♻️ Suggested refactor
+function mockApiClientWithRetrieve(
+  retrieveAssociationsImpl: () => Promise<unknown>
+) {
+  vi.mocked(useApiClients).mockReturnValue({
+    party: {
+      ...mockPartyClient,
+      retrieveAssociations: vi.fn(retrieveAssociationsImpl),
+    },
+  } as ReturnType<typeof useApiClients>)
+}

 // loading
-vi.mocked(useApiClients).mockReturnValue({
-  party: {
-    retrieveAssociations: vi.fn(() => new Promise(() => {})),
-  },
-} as ReturnType<typeof useApiClients>)
+mockApiClientWithRetrieve(() => new Promise(() => {}))

 // empty state
-vi.mocked(useApiClients).mockReturnValue({
-  party: {
-    retrieveAssociations: vi.fn().mockResolvedValue({}),
-  },
-} as ReturnType<typeof useApiClients>)
+mockApiClientWithRetrieve(() => Promise.resolve({}))

Also applies to: 81-85, 95-99, 109-113, 123-127, 140-142

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

In `@frontend/src/features/parties/pages/tabs/associations-tab.test.tsx` around
lines 68 - 72, Create a shared default mock for the party API client and reuse
it across tests instead of repeating vi.mocked(useApiClients).mockReturnValue
blocks; specifically, add a reusable mockPartyClient object (with defaults for
other methods) and in each test override only retrieveAssociations (e.g.,
vi.mocked(useApiClients).mockReturnValue({ party: { ...mockPartyClient,
retrieveAssociations: /* scenario fn */ } } as ReturnType<typeof
useApiClients>)); update tests that currently set retrieveAssociations at lines
around the override blocks to use this pattern so each test only supplies the
scenario-specific retrieveAssociations implementation.
frontend/src/features/payments/pages/payments-list.test.tsx (1)

66-67: Optional DRY cleanup for repeated mock response setup.

The repeated mockQueryFn.mockResolvedValue({ items: ... }) calls can be extracted into a tiny helper to reduce duplication.

♻️ Proposed refactor
+const setPaymentsResponse = (items: unknown[] = []) => {
+  mockQueryFn.mockResolvedValue({ items })
+}
...
-    mockQueryFn.mockResolvedValue({ items: [] })
+    setPaymentsResponse([])
...
-    mockQueryFn.mockResolvedValue({ items: samplePayments })
+    setPaymentsResponse(samplePayments)

Also applies to: 78-79, 97-98, 112-113, 127-128, 144-145, 172-173

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

In `@frontend/src/features/payments/pages/payments-list.test.tsx` around lines 66
- 67, Extract the repeated mockQueryFn.mockResolvedValue({ items: ... }) calls
into a small helper (e.g., setMockItems or mockQueryResponse) and replace each
direct mockQueryFn.mockResolvedValue invocation in payments-list.test.tsx with
that helper; locate uses of mockQueryFn.mockResolvedValue in the test file and
update them to call the helper with the desired items array so the setup is DRY
and easier to change.
🤖 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/features/payments/pages/payment-detail.test.tsx`:
- Around line 9-10: Delete the unused mock variable definition
mockPaymentDetailQuery (the vi.fn() declaration) from the test file; remove the
const mockPaymentDetailQuery = vi.fn() line and ensure there are no remaining
references to that symbol in payment-detail.test.tsx so lint stops flagging it.

In `@frontend/src/features/reconciliation/pages/index.test.tsx`:
- Around line 8-16: The module mock factory captures mockQueryFn too early; make
mockQueryFn hoisted so it's initialized before vi.mock runs. Replace the
top-level const mockQueryFn = vi.fn() with a hoisted initializer using
vi.hoisted(() => vi.fn()), leaving the vi.mock(...) and its use of
useReconciliationRunsTable/queryFn unchanged so the factory sees the initialized
mockQueryFn.

---

Nitpick comments:
In `@frontend/src/features/ledger/pages/booking-log-header.test.tsx`:
- Around line 40-42: Rename the test description to reflect the field change:
update the it(...) string in booking-log-header.test.tsx for the test that
renders BookingLogHeader with mockBookingLog (the test currently reads "renders
the base currency") to something like "renders the instrument code" or "renders
the instrument" so it matches the mockBookingLog's instrumentCode field and
keeps the test name consistent with the assertion using screen.getByText('GBP').

In `@frontend/src/features/parties/pages/tabs/associations-tab.test.tsx`:
- Around line 68-72: Create a shared default mock for the party API client and
reuse it across tests instead of repeating
vi.mocked(useApiClients).mockReturnValue blocks; specifically, add a reusable
mockPartyClient object (with defaults for other methods) and in each test
override only retrieveAssociations (e.g.,
vi.mocked(useApiClients).mockReturnValue({ party: { ...mockPartyClient,
retrieveAssociations: /* scenario fn */ } } as ReturnType<typeof
useApiClients>)); update tests that currently set retrieveAssociations at lines
around the override blocks to use this pattern so each test only supplies the
scenario-specific retrieveAssociations implementation.

In `@frontend/src/features/payments/pages/payments-list.test.tsx`:
- Around line 66-67: Extract the repeated mockQueryFn.mockResolvedValue({ items:
... }) calls into a small helper (e.g., setMockItems or mockQueryResponse) and
replace each direct mockQueryFn.mockResolvedValue invocation in
payments-list.test.tsx with that helper; locate uses of
mockQueryFn.mockResolvedValue in the test file and update them to call the
helper with the desired items array so the setup is DRY and easier to change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33814a60-9e29-4e67-91be-ebd2ebc2b860

📥 Commits

Reviewing files that changed from the base of the PR and between 6d1dc3f and 32ae109.

📒 Files selected for processing (10)
  • frontend/src/features/ledger/pages/booking-log-header.test.tsx
  • frontend/src/features/parties/pages/[partyId].test.tsx
  • frontend/src/features/parties/pages/tabs/associations-tab.test.tsx
  • frontend/src/features/payments/pages/payment-detail.test.tsx
  • frontend/src/features/payments/pages/payments-list.test.tsx
  • frontend/src/features/positions/pages/detail.test.tsx
  • frontend/src/features/positions/pages/index.test.tsx
  • frontend/src/features/reconciliation/pages/index.test.tsx
  • frontend/src/features/sagas/pages/detail.test.tsx
  • frontend/src/features/sagas/pages/index.test.tsx

Comment thread frontend/src/features/payments/pages/payment-detail.test.tsx Outdated
Comment thread frontend/src/features/reconciliation/pages/index.test.tsx Outdated
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.

🧹 Nitpick comments (3)
frontend/src/features/parties/pages/tabs/associations-tab.test.tsx (2)

9-13: Remove useClients from this test mock to keep migration intent strict.

This file now validates useApiClients; retaining useClients here can let accidental fallback usage pass tests silently.

🧹 Suggested cleanup
 vi.mock('@/api/context', () => ({
-  useClients: vi.fn(() => ({
-    party: {
-      retrieveAssociations: mockRetrieveAssociations,
-    },
-  })),
   useApiClients: vi.fn(() => ({
     party: {
       retrieveAssociations: mockRetrieveAssociations,
       listParties: vi.fn().mockResolvedValue({ parties: [] }),
       registerAssociations: vi.fn(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/parties/pages/tabs/associations-tab.test.tsx` around
lines 9 - 13, Remove the mocked useClients export from the test and replace
references with the explicit useApiClients mock; specifically, delete the
useClients: vi.fn(...) block (which currently returns
party.retrieveAssociations: mockRetrieveAssociations) and ensure the test only
mocks useApiClients to return the party.retrieveAssociations =
mockRetrieveAssociations so the test validates useApiClients rather than
allowing a fallback to useClients.

68-73: Avoid partial party mocks with forced return-type casts.

These overrides provide only retrieveAssociations and then cast to ReturnType<typeof useApiClients>. That’s brittle if AssociationsTab starts reading other party methods during render. Reuse mockPartyClient and override only what each test needs.

♻️ Example pattern
 vi.mocked(useApiClients).mockReturnValue({
   party: {
-    retrieveAssociations: vi.fn(() => new Promise(() => {})),
+    ...mockPartyClient,
+    retrieveAssociations: vi.fn(() => new Promise(() => {})),
   },
 } as ReturnType<typeof useApiClients>)

Also applies to: 81-86, 95-100, 109-114, 123-128, 140-143

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

In `@frontend/src/features/parties/pages/tabs/associations-tab.test.tsx` around
lines 68 - 73, The test currently stubs useApiClients with a partial party
object (only retrieveAssociations) and casts to ReturnType<typeof
useApiClients>, which is brittle; instead import/reuse the existing
mockPartyClient and return a full party client, then override only
retrieveAssociations for the specific test. Replace
vi.mocked(useApiClients).mockReturnValue({ party: { retrieveAssociations: ... }
} as ReturnType<typeof useApiClients>) with
vi.mocked(useApiClients).mockReturnValue({ party: mockPartyClient({...override
retrieveAssociations here...}) }) (or merge overrides onto mockPartyClient) so
AssociationsTab and other consumers always see a complete party client; apply
the same pattern to the other occurrences noted (lines ~81-86, 95-100, 109-114,
123-128, 140-143).
frontend/src/features/payments/pages/payment-detail.test.tsx (1)

88-89: Extract repeated hook-state stubs into a helper.

Line 88, Line 132, Line 189, Line 236, and Line 250 duplicate mock setup. A tiny helper would reduce drift and simplify future state additions.

♻️ Proposed cleanup
+const setPaymentDetailState = (
+  state: Partial<ReturnType<typeof usePaymentDetail>> = {},
+) => {
+  mockUsePaymentDetail.mockReturnValue(
+    { data: sampleDetail, isLoading: false, isError: false, ...state } as ReturnType<typeof usePaymentDetail>,
+  )
+}
+
 describe('PaymentDetailPage - structure', () => {
   beforeEach(() => {
-    mockUsePaymentDetail.mockReturnValue({ data: sampleDetail, isLoading: false, isError: false } as ReturnType<typeof usePaymentDetail>)
+    setPaymentDetailState()
   })
@@
 describe('PaymentDetailPage - Overview tab', () => {
   beforeEach(() => {
-    mockUsePaymentDetail.mockReturnValue({ data: sampleDetail, isLoading: false, isError: false } as ReturnType<typeof usePaymentDetail>)
+    setPaymentDetailState()
   })
@@
 describe('PaymentDetailPage - Saga Steps tab', () => {
   beforeEach(() => {
-    mockUsePaymentDetail.mockReturnValue({ data: sampleDetail, isLoading: false, isError: false } as ReturnType<typeof usePaymentDetail>)
+    setPaymentDetailState()
   })
@@
   it('shows skeleton while loading', () => {
-    mockUsePaymentDetail.mockReturnValue({ data: undefined, isLoading: true, isError: false } as ReturnType<typeof usePaymentDetail>)
+    setPaymentDetailState({ data: undefined, isLoading: true, isError: false })
@@
   it('shows error message on fetch failure', async () => {
-    mockUsePaymentDetail.mockReturnValue({ data: undefined, isLoading: false, isError: true } as ReturnType<typeof usePaymentDetail>)
+    setPaymentDetailState({ data: undefined, isLoading: false, isError: true })

Also applies to: 132-133, 189-190, 236-237, 250-251

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

In `@frontend/src/features/payments/pages/payment-detail.test.tsx` around lines 88
- 89, Repeated mock setups for usePaymentDetail are duplicated across tests;
extract them into a single helper (e.g., stubPaymentDetail or
setMockUsePaymentDetail) in the test file that accepts a partial state (data,
isLoading, isError) and calls mockUsePaymentDetail.mockReturnValue(...) using
ReturnType<typeof usePaymentDetail> and sampleDetail defaults; replace each
direct mockUsePaymentDetail.mockReturnValue(...) occurrence (lines referencing
mockUsePaymentDetail, usePaymentDetail, sampleDetail) with calls to this helper
so all tests reuse the same centralized stub and can easily extend state later.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/features/parties/pages/tabs/associations-tab.test.tsx`:
- Around line 9-13: Remove the mocked useClients export from the test and
replace references with the explicit useApiClients mock; specifically, delete
the useClients: vi.fn(...) block (which currently returns
party.retrieveAssociations: mockRetrieveAssociations) and ensure the test only
mocks useApiClients to return the party.retrieveAssociations =
mockRetrieveAssociations so the test validates useApiClients rather than
allowing a fallback to useClients.
- Around line 68-73: The test currently stubs useApiClients with a partial party
object (only retrieveAssociations) and casts to ReturnType<typeof
useApiClients>, which is brittle; instead import/reuse the existing
mockPartyClient and return a full party client, then override only
retrieveAssociations for the specific test. Replace
vi.mocked(useApiClients).mockReturnValue({ party: { retrieveAssociations: ... }
} as ReturnType<typeof useApiClients>) with
vi.mocked(useApiClients).mockReturnValue({ party: mockPartyClient({...override
retrieveAssociations here...}) }) (or merge overrides onto mockPartyClient) so
AssociationsTab and other consumers always see a complete party client; apply
the same pattern to the other occurrences noted (lines ~81-86, 95-100, 109-114,
123-128, 140-143).

In `@frontend/src/features/payments/pages/payment-detail.test.tsx`:
- Around line 88-89: Repeated mock setups for usePaymentDetail are duplicated
across tests; extract them into a single helper (e.g., stubPaymentDetail or
setMockUsePaymentDetail) in the test file that accepts a partial state (data,
isLoading, isError) and calls mockUsePaymentDetail.mockReturnValue(...) using
ReturnType<typeof usePaymentDetail> and sampleDetail defaults; replace each
direct mockUsePaymentDetail.mockReturnValue(...) occurrence (lines referencing
mockUsePaymentDetail, usePaymentDetail, sampleDetail) with calls to this helper
so all tests reuse the same centralized stub and can easily extend state later.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9517ceb4-6bc8-4c22-96a1-ae523efda165

📥 Commits

Reviewing files that changed from the base of the PR and between 32ae109 and 4c58ca0.

📒 Files selected for processing (2)
  • frontend/src/features/parties/pages/tabs/associations-tab.test.tsx
  • frontend/src/features/payments/pages/payment-detail.test.tsx

@bjcoombs bjcoombs dismissed coderabbitai[bot]’s stale review March 5, 2026 09:32

Suggestions noted — non-blocking improvements for follow-up

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 for full review. 5 non-blocking improvement suggestions (inline comments from prior review still apply). Bot threads assessed -- most concerns are pre-existing behavior, not introduced by this PR.

- Replace unsafe `as unknown as` casts with explicit field mapping
  in usePaymentDetail and useReconciliationRunDetail
- Remove useCallback wrapper from usePartiesTable queryFn for
  consistency with other hooks
- Add tenantSlug guard to enabled checks in usePositionLogDetail
  and useSagaDetail
- Guard against NaN from parseInt in usePositionLogsTable and
  useDatasetsTable filter parsing
- Make instrumentCode mapping consistent between booking log list
  and detail hooks (both use getInstrumentCode/baseInstrumentCode)
- Remove unused getCurrencyName function from useBookingLogDetail
- Derive currency from posting data instead of hardcoding 'GBP'
  fallback in BookingLogDetailPage
- Remove error-swallowing try/catch in useActiveSaga
- Add TODO for unused association data in AssociationsTab
- Update test mock data to use baseInstrumentCode field
coderabbitai[bot]
coderabbitai Bot previously requested changes 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: 1

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/features/ledger/pages/booking-log-detail.test.tsx (1)

371-382: ⚠️ Potential issue | 🟡 Minor

Numeric-currency test does not validate currency behavior.

Line 371’s test name says numeric currency handling is verified, but it only asserts log-001 renders. This can pass even if currency mapping/display is broken.

Suggested test hardening
 it('handles numeric currency code (GBP=1)', async () => {
-  const logWithNumericCurrency = { ...mockBookingLog, baseInstrumentCode: 1 }
+  const logWithNumericCurrency = {
+    ...mockBookingLog,
+    postings: [
+      {
+        ...mockBookingLog.postings[0],
+        postingAmount: { ...mockBookingLog.postings[0].postingAmount, currencyCode: 1 },
+      },
+      {
+        ...mockBookingLog.postings[1],
+        postingAmount: { ...mockBookingLog.postings[1].postingAmount, currencyCode: 1 },
+      },
+    ],
+  }
   setupMockClients({ result: { financialBookingLog: logWithNumericCurrency } })
   renderWithApiClients(<BookingLogDetailPage />, {
     initialToken: createTenantUserToken('tenant-001'),
     queryClient: createTestQueryClient(),
   })

   await waitFor(() => {
-    expect(screen.getAllByText('log-001').length).toBeGreaterThan(0)
+    expect(screen.getByTestId('debit-total')).toBeInTheDocument()
   })
+  expect(screen.getByTestId('debit-total')).toHaveTextContent(/GBP/i)
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/ledger/pages/booking-log-detail.test.tsx` around lines
371 - 382, The test "handles numeric currency code (GBP=1)" only asserts the ID
renders but not that numeric baseInstrumentCode is mapped to GBP; update the
test around the BookingLogDetailPage case that creates logWithNumericCurrency
(using mockBookingLog and setupMockClients) to also assert the rendered currency
display is correct (e.g., check for "GBP" or the expected symbol/format) after
renderWithApiClients and waitFor; ensure you locate the test block that sets
baseInstrumentCode: 1 and add an expectation that verifies the currency
mapping/display rather than just asserting log-001 appears.
♻️ Duplicate comments (2)
frontend/src/features/positions/hooks/use-positions.ts (1)

23-27: ⚠️ Potential issue | 🟡 Minor

Harden status parsing beyond NaN checks.

Line [27] currently rejects NaN but still accepts non-enum numeric values (e.g. decimals/Infinity/out-of-range) and forwards them as TransactionStatus. Validate finite integer + enum membership, otherwise fallback to UNSPECIFIED.

Proposed fix
   const statusValue = params.filters?.status
+  const parsedStatus = Number(statusValue)
+  const status =
+    Number.isFinite(parsedStatus) &&
+    Number.isInteger(parsedStatus) &&
+    Object.values(TransactionStatus).includes(parsedStatus as TransactionStatus)
+      ? (parsedStatus as TransactionStatus)
+      : TransactionStatus.UNSPECIFIED
   const response = await clients.positionKeeping.listFinancialPositionLogs({
     pageToken: params.pageToken ?? '',
     accountId: params.filters?.accountId ?? '',
-    status: statusValue && !Number.isNaN(Number(statusValue)) ? (Number(statusValue) as TransactionStatus) : TransactionStatus.UNSPECIFIED,
+    status,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/positions/hooks/use-positions.ts` around lines 23 - 27,
The status parsing in use-positions.ts currently only guards against NaN and can
still pass decimals, Infinity, or out-of-range numbers to
clients.positionKeeping.listFinancialPositionLogs; update the logic that
computes status (the statusValue handling) to: convert to Number, ensure
Number.isFinite(value) and Number.isInteger(value), then verify the integer is a
valid TransactionStatus (e.g., check membership against
Object.values(TransactionStatus) or a known set of enum values); if any check
fails, set status to TransactionStatus.UNSPECIFIED before calling
listFinancialPositionLogs.
frontend/src/features/sagas/hooks/use-sagas.ts (1)

58-66: ⚠️ Potential issue | 🟠 Major

Add selective NotFound handling to return null instead of surfacing as error state.

The current implementation will surface NotFound errors from getActiveSaga as React Query error states, but NotFound indicates "no active saga exists"—a valid state that should return null. This pattern is already established in similar hooks like use-accounts.ts and use-internal-accounts.ts.

🐛 Proposed fix
 export function useActiveSaga(sagaName: string | undefined, enabled: boolean = true) {
   const { sagaRegistry } = useApiClients()

   return useQuery({
     queryKey: ['starlark-config', 'active', sagaName],
     queryFn: async () => {
       if (!sagaName) return null
-      const response = await sagaRegistry.getActiveSaga({ name: sagaName })
-      return response
+      try {
+        const response = await sagaRegistry.getActiveSaga({ name: sagaName })
+        return response
+      } catch (err: unknown) {
+        if (ConnectError.from(err).code === Code.NotFound) return null
+        throw err
+      }
     },
     enabled: !!sagaName && enabled,
   })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/sagas/hooks/use-sagas.ts` around lines 58 - 66, Modify
the queryFn in the use-sagas hook so that when calling
sagaRegistry.getActiveSaga({ name: sagaName }) it catches errors and if the
error indicates a NotFound condition returns null instead of letting React Query
treat it as an error; if the error is anything else, rethrow it. Locate the
async queryFn inside useQuery in use-sagas.ts and wrap the call to getActiveSaga
with a try/catch that inspects the thrown error (matching the NotFound
identifier used elsewhere in hooks like use-accounts.ts /
use-internal-accounts.ts) to selectively return null for that case and propagate
other errors.
🧹 Nitpick comments (9)
frontend/src/features/parties/hooks/use-parties.ts (1)

6-6: Decouple hook types from page modules.

use-parties.ts currently imports Party from ../pages/index (Line 6). This creates a data-layer → page-layer dependency. Please move Party to a feature-level shared type module (e.g., features/parties/types) and import from there.

♻️ Proposed refactor
- import type { Party } from '../pages/index'
+ import type { Party } from '../types/party'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/parties/hooks/use-parties.ts` at line 6, The hook file
use-parties.ts imports the Party type from a page module which creates an
improper dependency; extract or create a feature-level type module (e.g.,
features/parties/types) that exports the Party type, move the Party definition
there, update use-parties.ts to import Party from the new types module, and
change any other files that currently import Party from ../pages/index to import
from the new feature types module so the data layer no longer depends on the
page layer.
frontend/src/features/positions/hooks/use-positions.ts (1)

7-7: Decouple hook types from page modules.

Line [7] imports FinancialPositionLog from ../pages/index, which couples data hooks to UI pages. Move this type to a feature-level types module (or use the generated API type directly) so hooks stay reusable and avoid page↔hook dependency drift.

Proposed refactor
-import type { FinancialPositionLog } from '../pages/index'
+import type { FinancialPositionLog } from '@/api/gen/meridian/positionkeeping/v1/position_keeping_pb'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/positions/hooks/use-positions.ts` at line 7, The hook
imports the UI page type FinancialPositionLog, coupling the hook to a page;
create a feature-level types module (e.g., export FinancialPositionLog from a
new positions/types or use the generated API type) and update the hook
(usePositions) to import FinancialPositionLog from that feature types module
instead of the page; also update any consumers (the page that previously
exported FinancialPositionLog) to import the type from the new feature types
module so the page↔hook dependency is removed.
frontend/src/features/ledger/hooks/use-ledger.ts (3)

60-60: Avoid as never type cast for status filter.

The as never cast suppresses type checking rather than properly aligning the types. Consider defining a proper type for the status filter value or using the correct enum type from the API schema.

💡 Suggested approach
-      ...(statusFilter !== undefined && { status: statusFilter as never }),
+      ...(statusFilter !== undefined && { status: Number(statusFilter) }),

Or, if the API expects a specific enum type, import and use that type for the conversion.

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

In `@frontend/src/features/ledger/hooks/use-ledger.ts` at line 60, Replace the
unsafe "as never" cast on the object spread expression "...(statusFilter !==
undefined && { status: statusFilter as never })" by using the correct status
type: import the API enum/type (e.g., LedgerStatus or the API's status enum) or
declare a proper union type for statusFilter and cast to that type (e.g.,
status: statusFilter as LedgerStatus) so the object property has the correct
type instead of never; update the use-ledger hook's statusFilter declaration or
the spread to match the imported/declared status type.

73-73: Postings not mapped in list hook but fully mapped in detail hook.

The list hook passes postings through with only a type assertion, while the detail hook (lines 107-126) maps each posting to the LedgerPosting shape with normalized fields. This creates a data-shape inconsistency between list and detail views.

If list consumers expect the same LedgerPosting structure, consider reusing the same mapping logic. If list postings are intentionally raw, document this distinction.

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

In `@frontend/src/features/ledger/hooks/use-ledger.ts` at line 73, The list hook
currently assigns postings with a raw type assertion at the postings property
(postings: (log.postings ?? []) as FinancialBookingLog['postings']) causing a
mismatch with the detail hook which maps each posting to the LedgerPosting
shape; update the list hook to reuse the same mapping/normalization used in the
detail view (the mapping that converts raw posting fields into LedgerPosting) so
list items produce the same normalized LedgerPosting structure, or explicitly
document the intended raw shape if you want them different; locate the postings
assignment in the list branch of use-ledger.ts and apply the same per-posting
transform used around the detail mapping (the LedgerPosting mapping block) to
ensure consistent data shapes.

115-118: Consider extracting BigInt conversion to a reusable helper.

This IIFE pattern for safe BigInt conversion is duplicated in booking-log-detail.tsx (lines 46 and 79). Extracting it would reduce duplication and improve maintainability.

💡 Suggested helper
function toBigInt(value: unknown): bigint {
  if (typeof value === 'bigint') return value
  if (typeof value === 'number' && Number.isSafeInteger(value)) return BigInt(value)
  return 0n
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/ledger/hooks/use-ledger.ts` around lines 115 - 118, The
IIFE used to safely convert postingAmount?.units to a bigint is duplicated (in
use-ledger.ts inside the units property and in booking-log-detail.tsx), so
extract a reusable helper named toBigInt(value: unknown): bigint that returns
the bigint, converts safe integers, or 0n otherwise; replace the anonymous IIFE
calls with toBigInt(p.postingAmount?.units) in useLedger (units) and the two
locations in booking-log-detail to remove duplication and improve
maintainability, and add the helper to a shared place (e.g., a util/module
alongside ledger hooks) and import it where needed.
frontend/src/features/ledger/pages/booking-log-detail.tsx (3)

39-39: Remove unused _currency parameter from computeTotals.

The _currency parameter is not used in the function body. Either remove it or document why it's reserved for future use.

💡 Suggested fix
-function computeTotals(postings: LedgerPosting[], _currency: string): { debitTotal: bigint; creditTotal: bigint } {
+function computeTotals(postings: LedgerPosting[]): { debitTotal: bigint; creditTotal: bigint } {

Update the call site at line 160:

-  const { debitTotal, creditTotal } = computeTotals(postings, currency)
+  const { debitTotal, creditTotal } = computeTotals(postings)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/ledger/pages/booking-log-detail.tsx` at line 39, Remove
the unused `_currency` parameter from the computeTotals function signature and
update all call sites that currently pass a currency argument to call
computeTotals with only the postings array; specifically, change function
computeTotals(postings: LedgerPosting[], _currency: string) to
computeTotals(postings: LedgerPosting[]): { debitTotal: bigint; creditTotal:
bigint } and update invocations (e.g., where BookingLogDetail invokes
computeTotals) to stop supplying the currency argument so TypeScript types
remain consistent.

26-37: Duplicate getDirectionName function.

This function is identical to the one in use-ledger.ts (lines 24-35). Consider exporting it from the hooks module and reusing it here to avoid duplication.

💡 Suggested fix

Export getDirectionName from use-ledger.ts:

export function getDirectionName(direction: unknown): string { ... }

Then in this file:

-import { useBookingLogDetail } from '../hooks'
+import { useBookingLogDetail, getDirectionName } from '../hooks'

And remove the local definition.

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

In `@frontend/src/features/ledger/pages/booking-log-detail.tsx` around lines 26 -
37, The getDirectionName function is duplicated; remove the local definition in
booking-log-detail.tsx and instead export getDirectionName from the hooks module
(use-ledger.ts) by adding an export for the existing function there, then import
getDirectionName into booking-log-detail.tsx and use it. Ensure the exported
symbol name is exactly getDirectionName so references in booking-log-detail.tsx
resolve without changes.

158-159: Currency derivation priority may be semantically reversed.

The current logic prioritizes instrumentCode (mapped from baseInstrumentCode in the hook) over postingCurrency. If instrumentCode represents the financial instrument rather than currency, the fallback order should be reversed to prefer the actual posting currency.

💡 Suggested fix
   const postingCurrency = postings.find((p) => p.postingAmount?.currencyCode)?.postingAmount?.currencyCode
-  const currency = data?.instrumentCode || postingCurrency || ''
+  const currency = postingCurrency || data?.instrumentCode || ''

Alternatively, if instrumentCode truly represents currency in this context, consider renaming it or adding a separate currencyCode field to the FinancialBookingLog type for clarity.

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

In `@frontend/src/features/ledger/pages/booking-log-detail.tsx` around lines 158 -
159, The currency selection currently uses data?.instrumentCode before the
actual posting currency; change the fallback order so postingCurrency (derived
from postings.find(p =>
p.postingAmount?.currencyCode)?.postingAmount?.currencyCode) is used first and
data?.instrumentCode is the fallback when postingCurrency is absent for the
computed currency variable; alternatively, if instrumentCode truly represents
currency, update the FinancialBookingLog type or rename instrumentCode to
currencyCode to make intent explicit (adjust all usages of currency,
postingCurrency, and data?.instrumentCode accordingly).
frontend/src/features/sagas/hooks/use-sagas.ts (1)

40-49: Unreachable fallback query key due to enabled constraint.

The queryKey has a fallback branch ['starlark-config', definitionId] for when tenantSlug is undefined (line 43), but the enabled condition on line 48 requires tenantSlug to be truthy. This makes the fallback branch dead code—the query will never execute without a tenant slug.

Either remove the fallback to simplify the logic, or relax the enabled check if tenant-less queries are valid.

🔧 Option: Simplify by removing unreachable fallback
   return useQuery({
-    queryKey: tenantSlug
-      ? [...tenantKeys.sagas(tenantSlug), definitionId]
-      : ['starlark-config', definitionId],
+    queryKey: [...tenantKeys.sagas(tenantSlug ?? ''), definitionId],
     queryFn: async () => {
       const response = await sagaRegistry.getSaga({ id: definitionId ?? '' })
       return response.saga
     },
     enabled: Boolean(tenantSlug && definitionId),
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/sagas/hooks/use-sagas.ts` around lines 40 - 49, The
current useQuery call in use-sagas.ts builds a fallback queryKey
['starlark-config', definitionId] when tenantSlug is falsy, but the enabled flag
requires tenantSlug, making that branch unreachable; fix by removing the
fallback and use tenant-dependent keys only (use
[...tenantKeys.sagas(tenantSlug), definitionId] when tenantSlug is present) so
queryKey and enabled are consistent, keeping sagaRegistry.getSaga and the
enabled: Boolean(tenantSlug && definitionId) logic; alternatively, if
tenant-less queries should be allowed, relax enabled to Boolean(definitionId)
and keep the fallback key ['starlark-config', definitionId] so both branches can
execute.
🤖 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/features/parties/hooks/use-parties.ts`:
- Around line 73-80: The query functions use non-null assertions (partyId!)
inside the async queryFn blocks (the anonymous queryFn for the retrieve call and
the other queryFn around lines 90-94), which can cause runtime undefined usage
if refetch is invoked; update each queryFn to perform an explicit runtime guard
(e.g., if (!partyId) throw new Error('partyId is required') or return undefined)
before calling clients.party.retrieveParty(...) (and the other clients.party.*
call) so the function never dereferences partyId without checking, while keeping
the existing enabled: Boolean(tenantSlug && partyId) guard.

---

Outside diff comments:
In `@frontend/src/features/ledger/pages/booking-log-detail.test.tsx`:
- Around line 371-382: The test "handles numeric currency code (GBP=1)" only
asserts the ID renders but not that numeric baseInstrumentCode is mapped to GBP;
update the test around the BookingLogDetailPage case that creates
logWithNumericCurrency (using mockBookingLog and setupMockClients) to also
assert the rendered currency display is correct (e.g., check for "GBP" or the
expected symbol/format) after renderWithApiClients and waitFor; ensure you
locate the test block that sets baseInstrumentCode: 1 and add an expectation
that verifies the currency mapping/display rather than just asserting log-001
appears.

---

Duplicate comments:
In `@frontend/src/features/positions/hooks/use-positions.ts`:
- Around line 23-27: The status parsing in use-positions.ts currently only
guards against NaN and can still pass decimals, Infinity, or out-of-range
numbers to clients.positionKeeping.listFinancialPositionLogs; update the logic
that computes status (the statusValue handling) to: convert to Number, ensure
Number.isFinite(value) and Number.isInteger(value), then verify the integer is a
valid TransactionStatus (e.g., check membership against
Object.values(TransactionStatus) or a known set of enum values); if any check
fails, set status to TransactionStatus.UNSPECIFIED before calling
listFinancialPositionLogs.

In `@frontend/src/features/sagas/hooks/use-sagas.ts`:
- Around line 58-66: Modify the queryFn in the use-sagas hook so that when
calling sagaRegistry.getActiveSaga({ name: sagaName }) it catches errors and if
the error indicates a NotFound condition returns null instead of letting React
Query treat it as an error; if the error is anything else, rethrow it. Locate
the async queryFn inside useQuery in use-sagas.ts and wrap the call to
getActiveSaga with a try/catch that inspects the thrown error (matching the
NotFound identifier used elsewhere in hooks like use-accounts.ts /
use-internal-accounts.ts) to selectively return null for that case and propagate
other errors.

---

Nitpick comments:
In `@frontend/src/features/ledger/hooks/use-ledger.ts`:
- Line 60: Replace the unsafe "as never" cast on the object spread expression
"...(statusFilter !== undefined && { status: statusFilter as never })" by using
the correct status type: import the API enum/type (e.g., LedgerStatus or the
API's status enum) or declare a proper union type for statusFilter and cast to
that type (e.g., status: statusFilter as LedgerStatus) so the object property
has the correct type instead of never; update the use-ledger hook's statusFilter
declaration or the spread to match the imported/declared status type.
- Line 73: The list hook currently assigns postings with a raw type assertion at
the postings property (postings: (log.postings ?? []) as
FinancialBookingLog['postings']) causing a mismatch with the detail hook which
maps each posting to the LedgerPosting shape; update the list hook to reuse the
same mapping/normalization used in the detail view (the mapping that converts
raw posting fields into LedgerPosting) so list items produce the same normalized
LedgerPosting structure, or explicitly document the intended raw shape if you
want them different; locate the postings assignment in the list branch of
use-ledger.ts and apply the same per-posting transform used around the detail
mapping (the LedgerPosting mapping block) to ensure consistent data shapes.
- Around line 115-118: The IIFE used to safely convert postingAmount?.units to a
bigint is duplicated (in use-ledger.ts inside the units property and in
booking-log-detail.tsx), so extract a reusable helper named toBigInt(value:
unknown): bigint that returns the bigint, converts safe integers, or 0n
otherwise; replace the anonymous IIFE calls with
toBigInt(p.postingAmount?.units) in useLedger (units) and the two locations in
booking-log-detail to remove duplication and improve maintainability, and add
the helper to a shared place (e.g., a util/module alongside ledger hooks) and
import it where needed.

In `@frontend/src/features/ledger/pages/booking-log-detail.tsx`:
- Line 39: Remove the unused `_currency` parameter from the computeTotals
function signature and update all call sites that currently pass a currency
argument to call computeTotals with only the postings array; specifically,
change function computeTotals(postings: LedgerPosting[], _currency: string) to
computeTotals(postings: LedgerPosting[]): { debitTotal: bigint; creditTotal:
bigint } and update invocations (e.g., where BookingLogDetail invokes
computeTotals) to stop supplying the currency argument so TypeScript types
remain consistent.
- Around line 26-37: The getDirectionName function is duplicated; remove the
local definition in booking-log-detail.tsx and instead export getDirectionName
from the hooks module (use-ledger.ts) by adding an export for the existing
function there, then import getDirectionName into booking-log-detail.tsx and use
it. Ensure the exported symbol name is exactly getDirectionName so references in
booking-log-detail.tsx resolve without changes.
- Around line 158-159: The currency selection currently uses
data?.instrumentCode before the actual posting currency; change the fallback
order so postingCurrency (derived from postings.find(p =>
p.postingAmount?.currencyCode)?.postingAmount?.currencyCode) is used first and
data?.instrumentCode is the fallback when postingCurrency is absent for the
computed currency variable; alternatively, if instrumentCode truly represents
currency, update the FinancialBookingLog type or rename instrumentCode to
currencyCode to make intent explicit (adjust all usages of currency,
postingCurrency, and data?.instrumentCode accordingly).

In `@frontend/src/features/parties/hooks/use-parties.ts`:
- Line 6: The hook file use-parties.ts imports the Party type from a page module
which creates an improper dependency; extract or create a feature-level type
module (e.g., features/parties/types) that exports the Party type, move the
Party definition there, update use-parties.ts to import Party from the new types
module, and change any other files that currently import Party from
../pages/index to import from the new feature types module so the data layer no
longer depends on the page layer.

In `@frontend/src/features/positions/hooks/use-positions.ts`:
- Line 7: The hook imports the UI page type FinancialPositionLog, coupling the
hook to a page; create a feature-level types module (e.g., export
FinancialPositionLog from a new positions/types or use the generated API type)
and update the hook (usePositions) to import FinancialPositionLog from that
feature types module instead of the page; also update any consumers (the page
that previously exported FinancialPositionLog) to import the type from the new
feature types module so the page↔hook dependency is removed.

In `@frontend/src/features/sagas/hooks/use-sagas.ts`:
- Around line 40-49: The current useQuery call in use-sagas.ts builds a fallback
queryKey ['starlark-config', definitionId] when tenantSlug is falsy, but the
enabled flag requires tenantSlug, making that branch unreachable; fix by
removing the fallback and use tenant-dependent keys only (use
[...tenantKeys.sagas(tenantSlug), definitionId] when tenantSlug is present) so
queryKey and enabled are consistent, keeping sagaRegistry.getSaga and the
enabled: Boolean(tenantSlug && definitionId) logic; alternatively, if
tenant-less queries should be allowed, relax enabled to Boolean(definitionId)
and keep the fallback key ['starlark-config', definitionId] so both branches can
execute.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b9ccba46-2a62-42a1-a990-e51427c918ed

📥 Commits

Reviewing files that changed from the base of the PR and between 4c58ca0 and c1eae82.

📒 Files selected for processing (10)
  • frontend/src/features/ledger/hooks/use-ledger.ts
  • frontend/src/features/ledger/pages/booking-log-detail.test.tsx
  • frontend/src/features/ledger/pages/booking-log-detail.tsx
  • frontend/src/features/market-data/hooks/use-market-data.ts
  • frontend/src/features/parties/hooks/use-parties.ts
  • frontend/src/features/parties/pages/tabs/associations-tab.tsx
  • frontend/src/features/payments/hooks/use-payments.ts
  • frontend/src/features/positions/hooks/use-positions.ts
  • frontend/src/features/reconciliation/hooks/use-reconciliation.ts
  • frontend/src/features/sagas/hooks/use-sagas.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/src/features/payments/hooks/use-payments.ts
  • frontend/src/features/market-data/hooks/use-market-data.ts
  • frontend/src/features/parties/pages/tabs/associations-tab.tsx
  • frontend/src/features/reconciliation/hooks/use-reconciliation.ts

Comment thread frontend/src/features/parties/hooks/use-parties.ts
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. 1 inline suggestion.

Comment thread frontend/src/features/sagas/hooks/use-sagas.ts
- Fix cache invalidation in saga detail page to use tenant-scoped
  query keys matching useSagaDetail hook
- Restore NotFound-specific error handling in useActiveSaga instead
  of swallowing all errors
- Use vi.hoisted for mock variable in reconciliation test to ensure
  correct initialization order with vi.mock hoisting
- Replace non-null assertions with nullish coalescing in party hooks
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 feedback addressed. Clean refactoring with consistent patterns, proper tenant scoping, and good test updates. See summary comment for details.

coderabbitai[bot]
coderabbitai Bot previously requested changes 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: 1

🧹 Nitpick comments (1)
frontend/src/features/sagas/pages/detail.tsx (1)

132-134: Consider renaming sagaQueryRoot to reflect actual scope.

This key includes definitionId, so it represents a detail key, not a root namespace. A clearer name reduces future invalidation mistakes.

Suggested rename
-  const sagaQueryRoot = tenantSlug
+  const sagaDetailQueryKey = tenantSlug
     ? [...tenantKeys.sagas(tenantSlug), definitionId]
     : ['starlark-config', definitionId]

...
-      void qc.invalidateQueries({ queryKey: sagaQueryRoot })
+      void qc.invalidateQueries({ queryKey: sagaDetailQueryKey })

...
-      void qc.invalidateQueries({ queryKey: sagaQueryRoot })
+      void qc.invalidateQueries({ queryKey: sagaDetailQueryKey })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/features/sagas/pages/detail.tsx` around lines 132 - 134, Rename
the variable sagaQueryRoot to a name that reflects it includes the detail
identifier (e.g., sagaDetailKey or sagaQueryKey) because it contains
definitionId; update the declaration that builds the array (the expression using
tenantSlug, tenantKeys.sagas(tenantSlug), and definitionId) and rename all
subsequent usages of sagaQueryRoot in this module (and any imports/exports) to
the new identifier to ensure clearer semantics and avoid future invalidation
mistakes.
🤖 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/features/sagas/pages/detail.tsx`:
- Around line 140-145: The page shows a false "not found" because
useSagaDetail(definitionId) is disabled when tenantSlug is falsy; either enforce
tenant selection at the route or allow non-tenant fetches — I recommend enabling
the query in non-tenant mode: in use-sagas.ts remove the tenantSlug && part from
the enabled option so enabled becomes Boolean(definitionId) (ensuring
useSagaDetail can run when tenantSlug is null), and update detail.test.tsx to
add a test case where useTenantSlug returns null/undefined to cover
platform-admin non-tenant behavior; references: useSagaDetail, useActiveSaga,
tenantSlug, enabled flag in use-sagas.ts, and detail.test.tsx.

---

Nitpick comments:
In `@frontend/src/features/sagas/pages/detail.tsx`:
- Around line 132-134: Rename the variable sagaQueryRoot to a name that reflects
it includes the detail identifier (e.g., sagaDetailKey or sagaQueryKey) because
it contains definitionId; update the declaration that builds the array (the
expression using tenantSlug, tenantKeys.sagas(tenantSlug), and definitionId) and
rename all subsequent usages of sagaQueryRoot in this module (and any
imports/exports) to the new identifier to ensure clearer semantics and avoid
future invalidation mistakes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4aabd8f5-1632-4712-b62b-4186e8c95cba

📥 Commits

Reviewing files that changed from the base of the PR and between c1eae82 and df845e3.

📒 Files selected for processing (4)
  • frontend/src/features/parties/hooks/use-parties.ts
  • frontend/src/features/reconciliation/pages/index.test.tsx
  • frontend/src/features/sagas/hooks/use-sagas.ts
  • frontend/src/features/sagas/pages/detail.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/features/sagas/hooks/use-sagas.ts
  • frontend/src/features/reconciliation/pages/index.test.tsx
  • frontend/src/features/parties/hooks/use-parties.ts

Comment thread frontend/src/features/sagas/pages/detail.tsx
Remove tenantSlug guard from useSagaDetail enabled check since the
starlark-config route is accessible to platform admins without a
selected tenant. The hook already has a fallback query key for this
non-tenant case.
@bjcoombs bjcoombs dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] March 5, 2026 11:13

All review issues addressed in subsequent commits

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.

Approving. Clean extraction following established patterns with bonus multi-tenant query key fixes. No blocking issues. See summary comment for full analysis.

@bjcoombs bjcoombs merged commit 87c4620 into develop Mar 5, 2026
41 checks passed
@bjcoombs bjcoombs deleted the 034-frontend-alignment--10--feature-data-hooks branch March 5, 2026 12:10
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