Skip to content

Fix/f 047 mobile nav aria label#306

Open
DavisVT wants to merge 33 commits intoPi-Defi-world:devfrom
DavisVT:fix/f-047-mobile-nav-aria-label
Open

Fix/f 047 mobile nav aria label#306
DavisVT wants to merge 33 commits intoPi-Defi-world:devfrom
DavisVT:fix/f-047-mobile-nav-aria-label

Conversation

@DavisVT
Copy link
Copy Markdown
Contributor

@DavisVT DavisVT commented Apr 29, 2026

closes #218

Summary by CodeRabbit

  • New Features

    • Multi-language support now available for Nigeria and Kenya regions.
    • Enhanced form validation with improved error messaging and feedback.
    • Accessibility improvements across the app, including better navigation labels and screen reader support.
  • Bug Fixes

    • Simplified and improved error displays throughout the application.
  • Tests

    • Added automated accessibility testing suite.

dubemoyibe-star and others added 30 commits April 23, 2026 08:50
- Add proper htmlFor/id pairing to all form inputs across mint, burn, send,
  and savings withdraw pages
- Implement ARIA attributes (aria-describedby, aria-label, aria-invalid)
  for screen reader support
- Fix viewport zoom issue by removing maximum-scale=1 to allow text scaling
- Improve color contrast for loading states and muted text to meet WCAG 2.1 AA
  (contrast ratio now 7.2:1 vs required 4.5:1)
- Add role=alert and role=status for dynamic content announcements
- Add focus management and keyboard navigation improvements
- Implement axe-core Playwright tests to prevent future regressions

Changes include:
- app/mint/page.tsx: Label associations for select and amount inputs
- app/burn/page.tsx: Complete form labeling with hint text
- app/send/page.tsx: Dialog and form accessibility enhancements
- app/savings/withdraw/page.tsx: Proper label-id pairing
- app/layout.tsx: Updated viewport config to allow zooming
- app/globals.css: Adjusted muted-foreground colors for contrast compliance
- tests/accessibility.spec.ts: New axe-core test suite

All money forms now have zero axe-core critical violations.

Closes Pi-Defi-world#217
…ocalization

implemeted  i18n / localization "F-045 — No i18n / localization
fix(a11y): resolve accessibility violations on money forms (F-046)
…e-list-keys

fix: replace unstable list keys and fix burn page build errors
fix(theme): improve send status badge contrast in dark mode
fix(frontend): prevent PII leaks in production console logs
…ature/fix-savings-deposit-handler

Revert "fix: wire savings deposit dialog to API and show pending/completed st…"
Junman140 and others added 3 commits April 28, 2026 03:13
Enhance Multi-Currency Bank Validation and Burn Page Form Logic
Use concise destination-based aria-label text on mobile nav icon links so VoiceOver announces each route clearly.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

Establishes internationalization support via next-intl with locale-prefixed routing, centralizes error logging through a new lib/logger module, simplifies error handling by removing error reporting and boundary wrappers, refactors form validation in burn page with React Hook Form and currency-specific Zod rules, updates authentication to use sessionStorage-based apiKey, and enhances accessibility across multiple pages with aria-labels and live regions.

Changes

Cohort / File(s) Summary
Internationalization Setup
i18n/request.ts, i18n/messages/*, next.config.mjs, middleware.ts
New i18n request configuration supporting locales en, en-NG, en-KE with dynamic message imports; locale-prefixed routing via next-intl middleware replacing prior security header logic.
Layout Restructuring
app/layout.tsx, app/[locale]/layout.tsx
Root layout simplified to return children directly; new locale-scoped layout handles metadata, viewport, locale determination, and wraps children with NextIntlClientProvider, ErrorBoundary, AuthProvider, AuthGuard, AppLayout, and analytics.
Error Handling Simplification
app/error.tsx, components/error-boundary.tsx
Removed structured error reporting (errorReporter), error boundary level/onError props, and PageErrorBoundary/ComponentErrorBoundary wrappers; both now use logger.error and render minimal fallback UI.
Logging Centralization
lib/logger.ts, lib/stellar-wallets-kit.ts, lib/stellar/trustlines.ts
New structured logger utility with debug-mode logic; replaces console.error/warn/info calls across files with logger.* equivalents.
Authentication Update
contexts/auth-context.tsx
Auth now requires apiKey parameter alongside userId; session state persists both to sessionStorage and API client token via setToken; removed cookie-based getMe() validation in favor of direct sessionStorage reading.
Burn Page Form Refactor
app/burn/page.tsx
Migrates to React Hook Form with state management via form.watch and form.formState.isValid; replaces generic regex validation with Zod superRefine currency-dependent rules (NGN exact digits, KES patterns); adds server-side HTTP 400 error mapping to form fields.
Home & Feature Pages i18n
app/[locale]/page.tsx, app/help/page.tsx, app/business/page.tsx
Home page uses useTranslations and useFormatter to localize feature metadata and numeric displays; FAQ accordion uses question string as key instead of index; business page removes feature-flag check and updates SME navigation target.
Money Flow Pages Refactor
app/mint/page.tsx, app/send/page.tsx, app/send/[id]/page.tsx, app/savings/deposit/page.tsx, app/savings/withdraw/page.tsx
Mint removes quote/fee computation and error-display service in favor of local state and "Estimated at confirmation" text; send removes virtualization and global error handling, adds ARIA labels and number-input type validation; send detail adds safeFormatDate, live regions, and status descriptions; savings pages remove recipient-resolution UI and backend resolver calls.
Styling & Theme
app/globals.css
Adds status color tokens (--status-success/warning/neutral and corresponding foreground/border variants) at :root, .dark, and @theme inline levels; updates dark --muted-foreground brightness.
Accessibility & Testing
components/mobile-nav.tsx, global.d.ts, playwright.config.ts, tests/accessibility.spec.ts, package.json
Mobile nav aria-label changed to "Go to {item.name}"; adds CSS module typing; new Playwright config and axe-based accessibility test suite; adds npm scripts for a11y testing.
Project Configuration
.gitignore
Adds .env ignore pattern for environment files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Fix: create a fallback error boundary component #250: Directly conflicts on components/error-boundary.tsx — one removes level/onError and wrapper exports while the other adds them; requires coordination to avoid merge conflicts and design alignment.
  • Fix/frontend money error mapping #296: Both modify error-handling surfaces across app/burn/page.tsx, app/mint/page.tsx, app/savings/withdraw/page.tsx, and shared libs (lib/stellar/trustlines.ts); this PR centralizes logging while #296 centralizes API error mapping—overlapping refactoring targets.
  • fix: remove API key from sessionStorage to prevent XSS attacks #266: Directly conflicts on contexts/auth-context.tsx — this PR adds apiKey/sessionStorage-based auth while #266 removes it in favor of httpOnly-cookie auth; opposing authentication strategies require resolution before merge.

Poem

🐰 Locales unlocked with i18n's grace,
Error logs hop to their proper place,
Forms now hook and validate with care,
Accessibility blooms everywhere!
Auth stores secrets safe and sound—
A rabbit's refactor, review-profound!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning PR title is overly narrow and vague. While it references the mobile nav aria label fix, the changeset includes 50+ files with substantial localization, auth refactoring, accessibility, testing, and dependency updates unrelated to the title's scope. Revise title to reflect the primary changes: e.g., 'Add i18n localization, accessibility improvements, and auth refactoring' or split into focused PRs.
Out of Scope Changes check ⚠️ Warning The PR contains extensive out-of-scope changes beyond fixing #218: i18n localization (new locale files, layout refactoring), auth context redesign with apiKey handling, error boundary simplification, logging infrastructure, Playwright test setup, dependency updates, and form refactoring unrelated to the linked mobile nav accessibility issue. Isolate the mobile-nav aria-label fix (#218) into a separate PR; consolidate the i18n, auth, testing, and other changes into focused feature branches.
Docstring Coverage ⚠️ Warning Docstring coverage is 44.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR addresses issue #218 (mobile nav aria-labels) with the specific change in components/mobile-nav.tsx updating link labels from item.name to 'Go to ${item.name}' format, meeting the stated fix direction.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/f-047-mobile-nav-aria-label

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Apr 29, 2026

@DavisVT Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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: 13

Caution

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

⚠️ Outside diff range comments (2)
app/[locale]/page.tsx (1)

310-354: ⚠️ Potential issue | 🟡 Minor

Variable shadowing: t shadows the translation function.

The map callback parameter t shadows the useTranslations hook result t from line 115. While the current code only accesses transaction properties (t.type, t.status, etc.), any future attempt to use translations inside this callback will silently use the wrong t, causing runtime errors.

🔧 Proposed fix: rename the callback parameter
-              {transactions.slice(0, 5).map((t) => (
-                  <Link key={t.transaction_id} href={`/transactions/${t.transaction_id}`} className="block rounded-lg border border-border bg-card p-3 transition-colors active:bg-muted">
+              {transactions.slice(0, 5).map((tx) => (
+                  <Link key={tx.transaction_id} href={`/transactions/${tx.transaction_id}`} className="block rounded-lg border border-border bg-card p-3 transition-colors active:bg-muted">
                     <div className="flex items-center gap-3 mb-2">
                       <div
                         className={`p-2 rounded-full flex-shrink-0 ${
-                          t.type === 'mint'
+                          tx.type === 'mint'
                             ? 'bg-green-100 dark:bg-green-900/30'
-                            : t.type === 'burn'
+                            : tx.type === 'burn'
                               ? 'bg-red-100 dark:bg-red-900/30'
                             : 'bg-blue-100 dark:bg-blue-900/30'
                         }`}

Continue renaming all t. references to tx. within this map callback (lines 310-354).

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

In `@app/`[locale]/page.tsx around lines 310 - 354, The map callback parameter `t`
in the transactions rendering shadows the `t` translation function from
useTranslations; rename the callback parameter to `tx` in the
transactions.slice(0, 5).map(...) callback and update every reference inside
that callback (key, href, tx.type, tx.transaction_id, tx.created_at,
tx.acbu_amount_burned, tx.amount_acbu, tx.local_currency, tx.local_amount,
tx.status, and any other tx.* uses) so translation `t` remains available and no
shadowing occurs while keeping the existing formatting helpers (formatDate,
formatAcbu, formatAmount) and Badge/Link usage unchanged.
app/burn/page.tsx (1)

145-221: ⚠️ Potential issue | 🔴 Critical

Critical: data is undefined — should be values throughout onSubmit.

The function parameter is named values (line 145), but the function body references data at lines 155-157, 173-174, 206-207, and 214-215. This will cause ReferenceError: data is not defined when the form is submitted.

🐛 Fix undefined variable references
     const recipientAccount: BurnRecipientAccount = {
-      account_number: data.accountNumber.trim(),
-      bank_code: data.bankCode.trim(),
-      account_name: data.accountName.trim(),
+      account_number: values.accountNumber.trim(),
+      bank_code: values.bankCode.trim(),
+      account_name: values.accountName.trim(),
       type: "bank",
     };
       const submit = await submitBurnRedeemSingleClient({
         userAddress: stellarAddress,
-        amountAcbu: data.acbuAmount,
-        currency: data.currency,
+        amountAcbu: values.acbuAmount,
+        currency: values.currency,
         userSecret: secret,
       });
       const submit = await submitBurnRedeemSingleClient({
         userAddress: stellarAddress,
-        amountAcbu: data.acbuAmount,
-        currency: data.currency,
+        amountAcbu: values.acbuAmount,
+        currency: values.currency,
         external: { kit, address },
       });
     const res = await burnApi.burnAcbu(
-      data.acbuAmount,
-      data.currency,
+      values.acbuAmount,
+      values.currency,
       recipientAccount,
       opts,
       burnTxHash,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` around lines 145 - 221, The onSubmit handler uses an
undefined identifier data; replace all usages of data with the function
parameter values (e.g., when building
recipientAccount.account_number/bank_code/account_name, passing amount/currency
to submitBurnRedeemSingleClient and burnApi.burnAcbu, and when calling
form.reset) so the function uses the passed BurnFormValues; verify occurrences
around recipientAccount construction, the two submitBurnRedeemSingleClient
calls, the burnApi.burnAcbu call, setTxId/form.reset lines, and ensure variable
names like getWalletSecretAnyLocal, submitBurnRedeemSingleClient,
burnApi.burnAcbu, setTxId, and form.reset remain unchanged.
🧹 Nitpick comments (14)
test-results/.last-run.json (1)

1-4: Drop the committed Playwright run state.

.last-run.json is machine-generated state. Keeping it in version control will create noisy diffs and merge conflicts without adding any real test coverage; please gitignore it instead.

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

In `@test-results/.last-run.json` around lines 1 - 4, Remove the committed
Playwright run state file `.last-run.json` from source control and add it to the
repository ignore list: stop tracking the file (git rm --cached .last-run.json),
commit that removal, and add `.last-run.json` to `.gitignore` (or the project’s
appropriate ignore file) so future runs don’t produce noisy diffs or merge
conflicts.
app/send/[id]/page.tsx (2)

204-214: Redundant ARIA attributes on amount value.

The <span> has both aria-labelledby="amount-label" and aria-describedby="amount-value", but amount-value is the same element's child, making aria-describedby point to itself. This is unnecessary and could confuse screen readers.

♻️ Simplified markup
             <span 
               className="font-semibold"
               aria-labelledby="amount-label"
-              aria-describedby="amount-value"
             >
-              <span id="amount-value">
                 {isFiatRecord
                   ? `${localCurrency} ${formatAmount(localAmount)}`
                   : `ACBU ${formatAmount(amountAcbu)}`}
-              </span>
             </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/send/`[id]/page.tsx around lines 204 - 214, The span rendering the amount
(the element with id "amount-value" inside the outer span having
aria-labelledby="amount-label") currently includes aria-describedby pointing to
its own child, which is redundant and can confuse screen readers; remove the
aria-describedby="amount-value" attribute from the outer span (or from whichever
element references the same id) so the outer element only uses
aria-labelledby="amount-label" and the inner span keeps id="amount-value" for
any required reference elsewhere.

263-267: Live region announces status on every render, not just changes.

The live region content is always present based on the current status value, so it will announce on initial page load rather than only when the status changes. If the intent is to announce dynamic updates, consider conditionally rendering only when status transitions occur.

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

In `@app/send/`[id]/page.tsx around lines 263 - 267, The sr-only live region
currently renders on every render because it directly reads the current status;
change this so it only mounts when status actually changes by tracking the
previous status and an announce flag (e.g., add a useState like announce and
prevStatus or useRef prevStatus, then in a useEffect compare prevStatus.current
to status and set announce true when they differ, update prevStatus.current =
status, and optionally clear announce after a short timeout); then render the
<div role="status" aria-live="polite" aria-atomic="true"> only when announce is
true so the screen reader only receives announcements on transitions, and ensure
you clear the announce flag afterwards to avoid repeated announcements.
tests/accessibility.spec.ts (3)

121-127: Silent skip when elements aren't found reduces test coverage.

When fiatSelect or amountInput aren't found, the test silently continues without asserting anything meaningful. Consider failing explicitly or logging a warning.

♻️ Proposed approach
     if (fiatSelect) {
       // Check if there are options
       const options = await fiatSelect.locator('option').count();
       if (options > 1) {
         await fiatSelect.selectOption({ index: 1 });
       }
+    } else {
+      console.warn('Fiat select not found - skipping select interaction');
     }

Alternatively, use test.skip() or expect.soft() to make the skip explicit.

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

In `@tests/accessibility.spec.ts` around lines 121 - 127, The current block
silently ignores missing elements (fiatSelect, amountInput) so tests can pass
without asserting behavior; update the test to explicitly fail or mark as
skipped when these locators are not found: after obtaining fiatSelect and
amountInput (the locator variables used in this snippet), assert their presence
with Playwright assertions (e.g., use
expect(fiatSelect).not.toBeNull()/toBeVisible() or call test.skip() when
intentionally optional) or use expect.soft() to record a soft failure; ensure
you also handle the options count via fiatSelect.locator('option').count() only
after asserting fiatSelect exists so the test fails/records when elements are
missing rather than silently continuing.

17-17: Avoid arbitrary waitForTimeout calls.

Hard-coded timeouts (lines 17, 23, 103, 174, 202-204) make tests flaky. Prefer waiting for specific conditions like element visibility or network idle states.

♻️ Example improvement for line 17
-    // Wait a bit more for content to stabilize
-    await page.waitForTimeout(1000);
+    // Wait for main content to be visible
+    await page.locator('main, [role="main"], .page-content').first().waitFor({ state: 'visible', timeout: 5000 }).catch(() => {});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/accessibility.spec.ts` at line 17, The test uses an arbitrary pause via
page.waitForTimeout(1000) which makes the spec flaky; replace that call with a
targeted wait such as waiting for a specific element or network/state transition
(e.g., page.waitForSelector('#selector'), page.waitForLoadState('networkidle'),
or page.waitForResponse(...) ) inside the same test function so the test
proceeds only when the required condition is met; locate and update the
page.waitForTimeout usage in tests/accessibility.spec.ts (search for
page.waitForTimeout) and swap each occurrence with an appropriate conditional
wait tied to the element or event that the subsequent assertions expect.

6-6: Add TypeScript type annotation for page parameter.

The helper function lacks a type annotation, which reduces type safety.

♻️ Proposed fix
-  async function waitForPageReady(page) {
+  async function waitForPageReady(page: import('@playwright/test').Page) {

Apply the same fix to mockAuth at line 28.

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

In `@tests/accessibility.spec.ts` at line 6, Add explicit TypeScript parameter
types for the test helpers: annotate the waitForPageReady(page) parameter with
the Playwright Page type (e.g., page: Page) and likewise annotate mockAuth's
parameter(s) (the helper at line 28) with their appropriate types; import the
Page type from your test library (e.g., '@playwright/test' or 'playwright') at
the top and update the function signatures for waitForPageReady and mockAuth to
use those types to restore type safety.
app/[locale]/page.tsx (1)

187-188: Hardcoded strings not using translations.

The header contains hardcoded English text while the rest of the page uses t() for translations. This creates an inconsistent localization experience.

♻️ Proposed fix: use translation keys
-            <h1 className="text-base font-bold text-foreground">Welcome back</h1>
-            <p className="text-xs text-muted-foreground">Manage your finances</p>
+            <h1 className="text-base font-bold text-foreground">{t('welcome_back')}</h1>
+            <p className="text-xs text-muted-foreground">{t('manage_finances')}</p>

Ensure the corresponding keys are added to the locale JSON files.

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

In `@app/`[locale]/page.tsx around lines 187 - 188, Replace the hardcoded header
strings in app/[locale]/page.tsx with translation keys (e.g., use
t('welcome_back') for the <h1> and t('manage_finances') for the <p>) so they
follow the same localization approach as the rest of the page; update your
locale JSON files to include the new keys and corresponding translations, and
ensure the component is importing/using the same translation helper (e.g., t or
useTranslations) already used elsewhere in this file.
i18n/request.ts (1)

8-11: Address TypeScript/ESLint issues for cleaner type handling.

Per static analysis: use @ts-expect-error instead of @ts-ignore, and avoid any. The type assertion can be improved.

♻️ Proposed fix
- // `@ts-ignore`
-export default getRequestConfig(async ({ locale }) => {
+export default getRequestConfig(async ({ locale }: { locale: string }) => {
   // Validate that the incoming `locale` parameter is valid
-  if (!locales.includes(locale as any)) notFound();
+  if (!locales.includes(locale as typeof locales[number])) notFound();
 
   return {
     messages: (await import(`./messages/${locale}.json`)).default
   };
 });

This removes both the @ts-ignore and any cast while maintaining type safety.

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

In `@i18n/request.ts` around lines 8 - 11, Replace the blanket ignore and the use
of `any` by tightening the types around `locale`: remove `// `@ts-ignore``, use
`// `@ts-expect-error`` only where a known compiler quirk demands it, and change
the `includes` check to use a properly typed guard (e.g., cast `locale` to the
union/explicit type used by your `locales` array or run a type-safe predicate)
so you call `notFound()` only when locale is not a valid member; update the
`getRequestConfig` callback signature or perform a narrow type check against the
`locales` symbol to avoid `any` while keeping the `notFound()` behavior intact
(refer to getRequestConfig, locales, and notFound).
app/send/page.tsx (2)

199-210: Missing "error" status in badge styling.

The getStatusBadgeClassName handles "completed", "pending", "failed", and a default case. If the API returns "error" as a distinct status, it would fall through to the neutral styling rather than destructive. Consider whether "error" should be handled alongside "failed".

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

In `@app/send/page.tsx` around lines 199 - 210, The getStatusBadgeClassName
function currently maps "completed", "pending", "failed", and a default but
omits the "error" status so "error" falls back to neutral styling; update
getStatusBadgeClassName to include a case for "error" (treating it the same as
"failed" or using the destructive classes) so API-returned "error" uses the
destructive badge styling instead of the neutral default.

89-103: Consider using async/await for consistency.

The loadTransfers and loadContacts callbacks use Promise chains (.then().catch().finally()) while the rest of the codebase uses async/await. This is functional but slightly inconsistent.

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

In `@app/send/page.tsx` around lines 89 - 103, Both loadTransfers and loadContacts
use Promise chains instead of async/await; convert each to an async function
(keep the useCallback wrappers) and replace .then/.catch/.finally with try {
const data = await transfersApi.getTransfers(opts); setTransfers(data.transfers
?? []); setLoadError(""); } catch (e) { setLoadError(e instanceof Error ?
e.message : 'Failed to load transfers'); } finally { setLoadingTransfers(false);
} (and similarly for loadContacts: await userApi.getContacts(opts),
setContacts(...), setLoadingContacts(false)); keep the same state updates and
error messages but use try/catch/finally for consistency.
app/burn/page.tsx (2)

43-43: Replace any with proper Zod refinement types.

ESLint flags explicit any usage. Zod provides proper types for superRefine callbacks.

♻️ Use Zod's refinement context type
-}).superRefine((data: any, ctx: any) => {
+}).superRefine((data, ctx) => {

TypeScript will infer the correct types from the schema definition. Alternatively, import and use z.RefinementCtx explicitly:

import { z, RefinementCtx } from "zod";
// ...
}).superRefine((data: z.infer<typeof baseSchema>, ctx: RefinementCtx) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` at line 43, Replace the explicit any types in the
superRefine callback with Zod refinement types: import z and RefinementCtx from
"zod", change the callback signature from (data: any, ctx: any) to (data:
z.infer<typeof <yourSchemaVariable>>, ctx: RefinementCtx) where
<yourSchemaVariable> is the schema you called .superRefine on (or let TypeScript
infer by using z.infer on that schema), and update any usages accordingly so
ESLint no longer sees explicit any.

222-245: Type the error handling more strictly.

The catch block and error details handling use any types. Consider typing the API error structure based on the ApiError interface from lib/api/client.ts.

♻️ Improve error typing
-    } catch (e: any) {
+    } catch (e: unknown) {
+      const apiErr = e as { status?: number; details?: Record<string, unknown> };
       // Handle server-side validation errors if they follow a specific format
-      if (e?.status === 400 && e?.details) {
-        const details = e.details as any;
+      if (apiErr?.status === 400 && apiErr?.details) {
+        const details = apiErr.details;
         const errors = details.errors || (details.error && typeof details.error === 'object' ? details.error : null);
         
         if (errors && typeof errors === 'object') {
-          Object.entries(errors).forEach(([key, msg]) => {
+          Object.entries(errors as Record<string, string>).forEach(([key, msg]) => {
             const formKey = key === 'account_number' ? 'accountNumber' :
                             key === 'bank_code' ? 'bankCode' :
                             key === 'account_name' ? 'accountName' :
                             key === 'acbu_amount' ? 'acbuAmount' :
-                            key as any;
+                            key as keyof BurnFormValues;
             
             if (['accountNumber', 'bankCode', 'accountName', 'acbuAmount', 'currency'].includes(formKey)) {
-              form.setError(formKey as any, { type: 'server', message: msg as string });
+              form.setError(formKey as keyof BurnFormValues, { type: 'server', message: msg });
             }
           });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/burn/page.tsx` around lines 222 - 245, The catch currently types the
exception as any and processes e.details loosely; import and use the ApiError
type from lib/api/client.ts, change the catch parameter to unknown and add a
type-guard function isApiError(err: unknown): err is ApiError to validate shape
(status, details, details.errors), then narrow e with that guard before
accessing details and errors; when iterating errors ensure the value is string
(or map object to string) before passing to form.setError (referencing
form.setError and setApiError) and fallback to setApiError(e) for non-ApiError
cases.
app/mint/page.tsx (1)

234-309: Consider extracting duplicated burn execution logic.

handleExecuteBurn duplicates most of the logic from app/burn/page.tsx's onSubmit (wallet secret retrieval, address verification, submitBurnRedeemSingleClient calls, wallet modal handling). This creates maintenance burden and divergence risk.

Consider extracting shared wallet-signing logic into a utility (e.g., lib/stellar/wallet-sign.ts) that both pages can use.

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

In `@app/mint/page.tsx` around lines 234 - 309, handleExecuteBurn duplicates
wallet-secret retrieval, address verification, wallet-modal flow and
submitBurnRedeemSingleClient logic found in app/burn/page.tsx; extract that
shared flow into a utility (e.g., lib/stellar/wallet-sign.ts) that exposes a
function (e.g., signAndSubmitBurnOrRedeem) which accepts parameters used here
(userId, stellarAddress, burnAmount, selectedFiatCurrency, kit, opts) and
internally uses getWalletSecretAnyLocal, Keypair.fromSecret checks,
kit.openModal wallet selection, and calls submitBurnRedeemSingleClient (or
returns transactionHash) so both handleExecuteBurn and the onSubmit in
app/burn/page.tsx call the same helper and then call fiatApi.postOffRamp/handle
UI state; update both callers to use the new helper and remove the duplicated
logic.
components/error-boundary.tsx (1)

41-55: Consider adding accessibility attributes to error UI.

Given this PR focuses on accessibility, the error fallback UI could benefit from role="alert" and aria-live="assertive" to announce the error state to screen readers.

♿ Suggested accessibility improvement
       return (
-        <div className="flex min-h-[200px] flex-col items-center justify-center gap-4 p-4 text-center">
+        <div 
+          className="flex min-h-[200px] flex-col items-center justify-center gap-4 p-4 text-center"
+          role="alert"
+          aria-live="assertive"
+        >
           <div className="rounded-full bg-red-100 p-3">
-            <svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+            <svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
               <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
             </svg>
           </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/error-boundary.tsx` around lines 41 - 55, The error fallback UI is
missing accessibility attributes; update the fallback container (the div with
className "flex min-h-[200px] ... text-center" returned by the ErrorBoundary
render) to include role="alert" and aria-live="assertive" so screen readers
announce the error immediately, and ensure the interactive Button remains
focusable (onClick={this.handleReset})—add these attributes directly on that
container element in the ErrorBoundary component to make the error state
perceivable to assistive tech.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/business/page.tsx`:
- Line 16: The card entry with id 'sme' currently points to the wrong route;
update its href value from '/sme' to the correct nested path '/business/sme' in
the array/object that contains { id: 'sme', title: 'SME Services', ... } inside
app/business/page.tsx so the card links to the existing page at
app/business/sme/page.tsx.

In `@app/error.tsx`:
- Around line 14-16: The current useEffect uses logger.error('Application
error:', error) which is inconsistent with other error boundaries; replace this
call with errorReporter.reportError and pass structured metadata similar to the
other pages (include page: 'app' or an appropriate page identifier and a digest
computed from the error/stack), i.e., locate the useEffect that references
logger.error and swap it to call errorReporter.reportError(error, { page: 'app',
digest: <computedDigest> }); ensure you import/use the same errorReporter
utility and compute the digest the same way other files do so the format matches
existing reports.

In `@app/globals.css`:
- Line 48: The CSS uses color-mix() for --status-neutral-border which may not be
available for all target browsers; add a safe fallback and a feature check:
declare a fallback value for --status-neutral-border (e.g., a precomputed hex or
rgba blend of --secondary and --border) before the color-mix() line, and wrap
the color-mix() assignment in an `@supports` (color-mix(in oklab, 0%)) {
--status-neutral-border: color-mix(in oklab, var(--secondary) 70%,
var(--border)); } so browsers without color-mix() use the fallback while capable
browsers get the mixed color. Ensure you update both instances where
--status-neutral-border is defined (the two occurrences using --secondary and
--border).

In `@app/layout.tsx`:
- Around line 70-76: RootLayout currently returns children without the required
top-level <html> and <body> elements; move the document root markup from the
nested locale layout into RootLayout by wrapping the returned children inside
the <html lang={...}> and <body> tags (preserve async RootLayout and the
children prop), and remove those tags from app/[locale]/layout.tsx so nested
layouts only render content; ensure any metadata, classNames, or providers that
were in the locale layout are transferred into RootLayout and language selection
is still passed down to children.

In `@app/mint/page.tsx`:
- Around line 582-589: The JSX mapping over rateRows (rateRows.map) uses
key={r.currency ?? r.rate}, which can produce non-unique keys when currency is
missing; change the key to include a stable fallback (e.g., key={`${r.currency
?? r.rate ?? index}`}) by using the map index as the final fallback so each Card
(Card component rendering AC BU/{r.currency ?? 'Rate'}) has a unique key; update
the map callback signature to (r, index) => ... and use that composite key to
avoid React reconciliation issues.

In `@app/savings/deposit/page.tsx`:
- Around line 69-70: The error paragraph rendering ({error}) is not in an
accessible live region; update the JSX that renders the error (the <p
className="text-destructive text-sm">{error}</p> instance) to be an ARIA live
alert by adding role="alert" or aria-live="assertive" (and aria-atomic="true")
so screen readers announce submit failures when the error appears; keep the same
styling and conditional rendering around the error variable.

In `@app/savings/withdraw/page.tsx`:
- Around line 77-78: The error message paragraph rendering the withdraw failure
(the JSX that shows {error} in the withdraw page component) should be marked as
a live region so assistive tech is notified; update the <p> that renders {error}
to include an accessibility attribute such as role="alert" or
aria-live="assertive" (and keep the existing classes and text content) so submit
failures are announced to screen readers.

In `@app/send/`[id]/page.tsx:
- Around line 223-230: The time element is still using new
Date(createdAt).toISOString(), which can produce "Invalid Date" for malformed
input; replace that direct call with the same safe approach used for display:
compute a validated ISO string (e.g., via the existing safeFormatDate helper or
a new safeToISOString utility) and use that for the dateTime attribute so
invalid dates become empty or a safe fallback; apply the same change for the
completedAt usage as well (update the <time> dateTime assignments that reference
createdAt and completedAt).

In `@contexts/auth-context.tsx`:
- Around line 22-24: The public auth API signature for login changed to
login(apiKey, userId, stellarAddress) but callers still pass (userId,
stellarAddress), causing setAuth to persist swapped values; update every call
site that invokes login (notably the sign-in and 2FA/verification handlers) to
pass result.api_key first, then result.user_id, then result.stellar_address
(i.e., login(result.api_key, result.user_id, result.stellar_address)); verify
any helper wrappers that forward parameters to login and update them similarly
so setAuth receives correct values.

In `@i18n/messages/en-KE.json`:
- Around line 2-14: The en-KE messages are missing the home.approx_usd key
present in the base English bundle; add a "approx_usd" entry under the "home"
object (matching the base English meaning, e.g. "Approx. USD") so that the
home.approx_usd lookup succeeds for the en-KE locale; update the "home" object
in the en-KE JSON to include "approx_usd": "<appropriate English text>" (use the
exact string from the base i18n if available).

In `@lib/logger.ts`:
- Around line 5-31: The logger currently uses the forbidden any type; introduce
a LogData type alias (e.g., type LogData = unknown) and replace all occurrences
of any with LogData: update the logMessage signature to logMessage(level:
LogLevel, message: string, data?: LogData), update the logger API methods
(info/warn/error/debug) to accept data?: LogData, and keep the existing logEntry
construction and console branches unchanged so the runtime behavior is identical
while satisfying `@typescript-eslint/no-explicit-any`.
- Around line 5-24: logMessage currently uses JSON.stringify directly and
accepts data: any, which fails on circular structures and strips Error details;
change the signature to use data?: unknown and implement a fail-safe serializer
(e.g., safeStringify) that (1) converts Error instances to plain objects
preserving name, message, stack and enumerable properties, (2) guards against
circular refs using a seen Set (or use util.inspect as fallback) and returns a
stable JSON string, and (3) never throws; then replace all
JSON.stringify(logEntry) calls in logMessage with safeStringify(logEntry) so
logging never fails and error stacks are preserved.

In `@package.json`:
- Around line 73-82: The devDependency versions in package.json are mismatched:
"@eslint/js" is ^10.0.1 while "eslint" is ^9.7.0; update one to align majors.
Either bump "eslint" to a v10 release (e.g., change the "eslint" entry to a
^10.x version) to match "@eslint/js@^10", or downgrade "@eslint/js" to a ^9.x
release so it matches the existing "eslint@^9.7.0"; update the package.json
devDependencies accordingly and run your package manager to refresh lockfiles.

---

Outside diff comments:
In `@app/`[locale]/page.tsx:
- Around line 310-354: The map callback parameter `t` in the transactions
rendering shadows the `t` translation function from useTranslations; rename the
callback parameter to `tx` in the transactions.slice(0, 5).map(...) callback and
update every reference inside that callback (key, href, tx.type,
tx.transaction_id, tx.created_at, tx.acbu_amount_burned, tx.amount_acbu,
tx.local_currency, tx.local_amount, tx.status, and any other tx.* uses) so
translation `t` remains available and no shadowing occurs while keeping the
existing formatting helpers (formatDate, formatAcbu, formatAmount) and
Badge/Link usage unchanged.

In `@app/burn/page.tsx`:
- Around line 145-221: The onSubmit handler uses an undefined identifier data;
replace all usages of data with the function parameter values (e.g., when
building recipientAccount.account_number/bank_code/account_name, passing
amount/currency to submitBurnRedeemSingleClient and burnApi.burnAcbu, and when
calling form.reset) so the function uses the passed BurnFormValues; verify
occurrences around recipientAccount construction, the two
submitBurnRedeemSingleClient calls, the burnApi.burnAcbu call,
setTxId/form.reset lines, and ensure variable names like
getWalletSecretAnyLocal, submitBurnRedeemSingleClient, burnApi.burnAcbu,
setTxId, and form.reset remain unchanged.

---

Nitpick comments:
In `@app/`[locale]/page.tsx:
- Around line 187-188: Replace the hardcoded header strings in
app/[locale]/page.tsx with translation keys (e.g., use t('welcome_back') for the
<h1> and t('manage_finances') for the <p>) so they follow the same localization
approach as the rest of the page; update your locale JSON files to include the
new keys and corresponding translations, and ensure the component is
importing/using the same translation helper (e.g., t or useTranslations) already
used elsewhere in this file.

In `@app/burn/page.tsx`:
- Line 43: Replace the explicit any types in the superRefine callback with Zod
refinement types: import z and RefinementCtx from "zod", change the callback
signature from (data: any, ctx: any) to (data: z.infer<typeof
<yourSchemaVariable>>, ctx: RefinementCtx) where <yourSchemaVariable> is the
schema you called .superRefine on (or let TypeScript infer by using z.infer on
that schema), and update any usages accordingly so ESLint no longer sees
explicit any.
- Around line 222-245: The catch currently types the exception as any and
processes e.details loosely; import and use the ApiError type from
lib/api/client.ts, change the catch parameter to unknown and add a type-guard
function isApiError(err: unknown): err is ApiError to validate shape (status,
details, details.errors), then narrow e with that guard before accessing details
and errors; when iterating errors ensure the value is string (or map object to
string) before passing to form.setError (referencing form.setError and
setApiError) and fallback to setApiError(e) for non-ApiError cases.

In `@app/mint/page.tsx`:
- Around line 234-309: handleExecuteBurn duplicates wallet-secret retrieval,
address verification, wallet-modal flow and submitBurnRedeemSingleClient logic
found in app/burn/page.tsx; extract that shared flow into a utility (e.g.,
lib/stellar/wallet-sign.ts) that exposes a function (e.g.,
signAndSubmitBurnOrRedeem) which accepts parameters used here (userId,
stellarAddress, burnAmount, selectedFiatCurrency, kit, opts) and internally uses
getWalletSecretAnyLocal, Keypair.fromSecret checks, kit.openModal wallet
selection, and calls submitBurnRedeemSingleClient (or returns transactionHash)
so both handleExecuteBurn and the onSubmit in app/burn/page.tsx call the same
helper and then call fiatApi.postOffRamp/handle UI state; update both callers to
use the new helper and remove the duplicated logic.

In `@app/send/`[id]/page.tsx:
- Around line 204-214: The span rendering the amount (the element with id
"amount-value" inside the outer span having aria-labelledby="amount-label")
currently includes aria-describedby pointing to its own child, which is
redundant and can confuse screen readers; remove the
aria-describedby="amount-value" attribute from the outer span (or from whichever
element references the same id) so the outer element only uses
aria-labelledby="amount-label" and the inner span keeps id="amount-value" for
any required reference elsewhere.
- Around line 263-267: The sr-only live region currently renders on every render
because it directly reads the current status; change this so it only mounts when
status actually changes by tracking the previous status and an announce flag
(e.g., add a useState like announce and prevStatus or useRef prevStatus, then in
a useEffect compare prevStatus.current to status and set announce true when they
differ, update prevStatus.current = status, and optionally clear announce after
a short timeout); then render the <div role="status" aria-live="polite"
aria-atomic="true"> only when announce is true so the screen reader only
receives announcements on transitions, and ensure you clear the announce flag
afterwards to avoid repeated announcements.

In `@app/send/page.tsx`:
- Around line 199-210: The getStatusBadgeClassName function currently maps
"completed", "pending", "failed", and a default but omits the "error" status so
"error" falls back to neutral styling; update getStatusBadgeClassName to include
a case for "error" (treating it the same as "failed" or using the destructive
classes) so API-returned "error" uses the destructive badge styling instead of
the neutral default.
- Around line 89-103: Both loadTransfers and loadContacts use Promise chains
instead of async/await; convert each to an async function (keep the useCallback
wrappers) and replace .then/.catch/.finally with try { const data = await
transfersApi.getTransfers(opts); setTransfers(data.transfers ?? []);
setLoadError(""); } catch (e) { setLoadError(e instanceof Error ? e.message :
'Failed to load transfers'); } finally { setLoadingTransfers(false); } (and
similarly for loadContacts: await userApi.getContacts(opts), setContacts(...),
setLoadingContacts(false)); keep the same state updates and error messages but
use try/catch/finally for consistency.

In `@components/error-boundary.tsx`:
- Around line 41-55: The error fallback UI is missing accessibility attributes;
update the fallback container (the div with className "flex min-h-[200px] ...
text-center" returned by the ErrorBoundary render) to include role="alert" and
aria-live="assertive" so screen readers announce the error immediately, and
ensure the interactive Button remains focusable (onClick={this.handleReset})—add
these attributes directly on that container element in the ErrorBoundary
component to make the error state perceivable to assistive tech.

In `@i18n/request.ts`:
- Around line 8-11: Replace the blanket ignore and the use of `any` by
tightening the types around `locale`: remove `// `@ts-ignore``, use `//
`@ts-expect-error`` only where a known compiler quirk demands it, and change the
`includes` check to use a properly typed guard (e.g., cast `locale` to the
union/explicit type used by your `locales` array or run a type-safe predicate)
so you call `notFound()` only when locale is not a valid member; update the
`getRequestConfig` callback signature or perform a narrow type check against the
`locales` symbol to avoid `any` while keeping the `notFound()` behavior intact
(refer to getRequestConfig, locales, and notFound).

In `@test-results/.last-run.json`:
- Around line 1-4: Remove the committed Playwright run state file
`.last-run.json` from source control and add it to the repository ignore list:
stop tracking the file (git rm --cached .last-run.json), commit that removal,
and add `.last-run.json` to `.gitignore` (or the project’s appropriate ignore
file) so future runs don’t produce noisy diffs or merge conflicts.

In `@tests/accessibility.spec.ts`:
- Around line 121-127: The current block silently ignores missing elements
(fiatSelect, amountInput) so tests can pass without asserting behavior; update
the test to explicitly fail or mark as skipped when these locators are not
found: after obtaining fiatSelect and amountInput (the locator variables used in
this snippet), assert their presence with Playwright assertions (e.g., use
expect(fiatSelect).not.toBeNull()/toBeVisible() or call test.skip() when
intentionally optional) or use expect.soft() to record a soft failure; ensure
you also handle the options count via fiatSelect.locator('option').count() only
after asserting fiatSelect exists so the test fails/records when elements are
missing rather than silently continuing.
- Line 17: The test uses an arbitrary pause via page.waitForTimeout(1000) which
makes the spec flaky; replace that call with a targeted wait such as waiting for
a specific element or network/state transition (e.g.,
page.waitForSelector('#selector'), page.waitForLoadState('networkidle'), or
page.waitForResponse(...) ) inside the same test function so the test proceeds
only when the required condition is met; locate and update the
page.waitForTimeout usage in tests/accessibility.spec.ts (search for
page.waitForTimeout) and swap each occurrence with an appropriate conditional
wait tied to the element or event that the subsequent assertions expect.
- Line 6: Add explicit TypeScript parameter types for the test helpers: annotate
the waitForPageReady(page) parameter with the Playwright Page type (e.g., page:
Page) and likewise annotate mockAuth's parameter(s) (the helper at line 28) with
their appropriate types; import the Page type from your test library (e.g.,
'@playwright/test' or 'playwright') at the top and update the function
signatures for waitForPageReady and mockAuth to use those types to restore type
safety.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6a65d32-ef14-457c-bfbc-4ac1ecf2b53a

📥 Commits

Reviewing files that changed from the base of the PR and between 84c1941 and 636f123.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (38)
  • .gitignore
  • app/[locale]/activity/page.tsx
  • app/[locale]/auth/2fa/page.tsx
  • app/[locale]/auth/error.tsx
  • app/[locale]/auth/signin/page.tsx
  • app/[locale]/auth/signup/page.tsx
  • app/[locale]/auth/wallet-setup/page.tsx
  • app/[locale]/layout.tsx
  • app/[locale]/page.tsx
  • app/burn/page.tsx
  • app/business/page.tsx
  • app/error.tsx
  • app/globals.css
  • app/help/page.tsx
  • app/layout.tsx
  • app/mint/page.tsx
  • app/savings/deposit/page.tsx
  • app/savings/withdraw/page.tsx
  • app/send/[id]/page.tsx
  • app/send/page.tsx
  • components/error-boundary.tsx
  • components/mobile-nav.tsx
  • contexts/auth-context.tsx
  • global.d.ts
  • i18n/messages/en-KE.json
  • i18n/messages/en-NG.json
  • i18n/messages/en.json
  • i18n/request.ts
  • lib/logger.ts
  • lib/stellar-wallets-kit.ts
  • lib/stellar/trustlines.ts
  • middleware.ts
  • next.config.mjs
  • package.json
  • playwright-report/index.html
  • playwright.config.ts
  • test-results/.last-run.json
  • tests/accessibility.spec.ts

Comment thread app/business/page.tsx

const businessServices = [
{ id: 'sme', title: 'SME Services', description: 'Business accounts, transfers & statements', icon: Briefcase, badge: 'Pro', href: '/business/sme' },
{ id: 'sme', title: 'SME Services', description: 'Business accounts, transfers & statements', icon: Briefcase, badge: 'Pro', href: '/sme' },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restore the SME route to /business/sme.

The SME page in this PR context lives at app/business/sme/page.tsx, so pointing the card at /sme sends users to a non-existent top-level route instead of the actual business page.

🛠️ Proposed fix
-  { id: 'sme', title: 'SME Services', description: 'Business accounts, transfers & statements', icon: Briefcase, badge: 'Pro', href: '/sme' },
+  { id: 'sme', title: 'SME Services', description: 'Business accounts, transfers & statements', icon: Briefcase, badge: 'Pro', href: '/business/sme' },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{ id: 'sme', title: 'SME Services', description: 'Business accounts, transfers & statements', icon: Briefcase, badge: 'Pro', href: '/sme' },
{ id: 'sme', title: 'SME Services', description: 'Business accounts, transfers & statements', icon: Briefcase, badge: 'Pro', href: '/business/sme' },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/business/page.tsx` at line 16, The card entry with id 'sme' currently
points to the wrong route; update its href value from '/sme' to the correct
nested path '/business/sme' in the array/object that contains { id: 'sme',
title: 'SME Services', ... } inside app/business/page.tsx so the card links to
the existing page at app/business/sme/page.tsx.

Comment thread app/error.tsx
Comment on lines 14 to 16
useEffect(() => {
errorReporter.reportError(error, {
level: 'page',
context: {
digest: error.digest,
type: 'route-error'
}
});
logger.error('Application error:', error);
}, [error]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all error.tsx files and check which error handling pattern they use
echo "=== Files using errorReporter ==="
rg -l 'errorReporter' --glob '**/error.tsx'

echo ""
echo "=== Files using logger ==="
rg -l "logger\.(error|warn)" --glob '**/error.tsx'

echo ""
echo "=== Error handling patterns in error.tsx files ==="
rg -n '(errorReporter\.reportError|logger\.error)' --glob '**/error.tsx' -A 5

Repository: Pi-Defi-world/acbu-frontend

Length of output: 2728


Inconsistent error handling pattern across error boundaries.

This file uses logger.error() while 7 other error boundary files (app/[locale]/auth/error.tsx, app/send/error.tsx, app/transactions/error.tsx, app/wallet/error.tsx, app/me/error.tsx, app/bills/error.tsx, app/savings/error.tsx) use errorReporter.reportError() with structured context including page and digest. This inconsistency complicates error tracking and monitoring.

Align with the existing pattern by either:

  1. Updating this file to use errorReporter with appropriate metadata, or
  2. Refactoring all other error pages to use logger if this represents a new pattern.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/error.tsx` around lines 14 - 16, The current useEffect uses
logger.error('Application error:', error) which is inconsistent with other error
boundaries; replace this call with errorReporter.reportError and pass structured
metadata similar to the other pages (include page: 'app' or an appropriate page
identifier and a digest computed from the error/stack), i.e., locate the
useEffect that references logger.error and swap it to call
errorReporter.reportError(error, { page: 'app', digest: <computedDigest> });
ensure you import/use the same errorReporter utility and compute the digest the
same way other files do so the format matches existing reports.

Comment thread app/globals.css
--status-warning-border: oklch(0.88 0.06 95);
--status-neutral: var(--secondary);
--status-neutral-foreground: var(--secondary-foreground);
--status-neutral-border: color-mix(in oklab, var(--secondary) 70%, var(--border));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

color-mix CSS browser support 2026

💡 Result:

As of April 2026, the CSS color-mix function has excellent browser support across all modern browsers and versions in current use. It has been widely available since mid-2023. Key support details: - Chrome/Edge: 111+ (current: 146+) - Firefox: 113+ (current: 151+) - Safari/iOS Safari: 16.2+ (current: 26+) - Mobile browsers (Chrome/Firefox/Samsung Internet for Android): Fully supported in latest versions No support in IE or very old browsers (pre-2020). Global usage is near 98%+ for browsers released after 2021. MDN confirms it is "baseline widely available" across devices.

Citations:


Verify color-mix() browser support for your target audience.

color-mix() is used for the neutral border (lines 48, 92). Browser support includes Chrome 111+, Firefox 113+, and Safari 16.2+. As of April 2026, color-mix() is baseline widely available with 98%+ coverage across browsers released after 2021. Confirm this aligns with your application's browser support requirements.

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

In `@app/globals.css` at line 48, The CSS uses color-mix() for
--status-neutral-border which may not be available for all target browsers; add
a safe fallback and a feature check: declare a fallback value for
--status-neutral-border (e.g., a precomputed hex or rgba blend of --secondary
and --border) before the color-mix() line, and wrap the color-mix() assignment
in an `@supports` (color-mix(in oklab, 0%)) { --status-neutral-border:
color-mix(in oklab, var(--secondary) 70%, var(--border)); } so browsers without
color-mix() use the fallback while capable browsers get the mixed color. Ensure
you update both instances where --status-neutral-border is defined (the two
occurrences using --secondary and --border).

Comment thread app/layout.tsx
Comment on lines 70 to 76
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const lang = "en";
// Read the nonce injected by middleware so Next.js can apply it to
// inline scripts/styles it generates (e.g. __NEXT_DATA__).
const headersList = await headers();
const nonce = headersList.get('x-nonce') ?? undefined;

return (
<html lang={lang}>
<body className={`font-sans antialiased`}>
<GlobalErrorHandler />
<ErrorBoundary level="app">
<AuthProvider>
<AuthGuard>
<AppLayout>{children}</AppLayout>
</AuthGuard>
<WalletSetupModal />
<Toaster />
<Analytics nonce={nonce} />
</AuthProvider>
</ErrorBoundary>
</body>
</html>
)
}: {
children: React.ReactNode;
}) {
return children;
} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

git ls-files app/layout.tsx

Repository: Pi-Defi-world/acbu-frontend

Length of output: 86


🏁 Script executed:

fd -type f -name "layout.tsx" app/

Repository: Pi-Defi-world/acbu-frontend

Length of output: 242


🏁 Script executed:

fd . app/ --max-depth 2 -type f -name "*.tsx" | head -20

Repository: Pi-Defi-world/acbu-frontend

Length of output: 242


🏁 Script executed:

cat -n app/layout.tsx

Repository: Pi-Defi-world/acbu-frontend

Length of output: 2805


🏁 Script executed:

find app -name "layout.tsx" -o -name "layout.ts"

Repository: Pi-Defi-world/acbu-frontend

Length of output: 110


🏁 Script executed:

ls -la app/

Repository: Pi-Defi-world/acbu-frontend

Length of output: 1522


🏁 Script executed:

cat -n app/[locale]/layout.tsx

Repository: Pi-Defi-world/acbu-frontend

Length of output: 2404


🌐 Web query:

Next.js App Router root layout html body tags requirement where to define

💡 Result:

In Next.js App Router, the root layout (app/layout.tsx or app/layout.js) is required and must define the <html> and <body> tags. These tags wrap the shared UI and children (pages or nested layouts). Nested layouts do not include <html> or <body> tags; they only add wrapping elements. Example from official docs: export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body>{children}</body> </html> ) } Do not manually add <head> tags like <title> or <meta>; use the Metadata API instead for handling title, meta, etc., which supports streaming and deduplication. The root layout is the top-most layout in the app directory and applies to all routes.

Citations:


Move the <html> and <body> tags to app/layout.tsx.

Next.js requires the root layout to define these tags. The current implementation violates this contract—nested layouts must not include them. The structure currently in app/[locale]/layout.tsx needs to move back to the root layout.

Suggested fix
 export default async function RootLayout({
   children,
 }: {
   children: React.ReactNode;
 }) {
-  return children;
+  return (
+    <html>
+      <body>{children}</body>
+    </html>
+  );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/layout.tsx` around lines 70 - 76, RootLayout currently returns children
without the required top-level <html> and <body> elements; move the document
root markup from the nested locale layout into RootLayout by wrapping the
returned children inside the <html lang={...}> and <body> tags (preserve async
RootLayout and the children prop), and remove those tags from
app/[locale]/layout.tsx so nested layouts only render content; ensure any
metadata, classNames, or providers that were in the locale layout are
transferred into RootLayout and language selection is still passed down to
children.

Comment thread app/mint/page.tsx
Comment on lines +582 to 589
rateRows.map((r: { currency?: string; rate?: number }) => (
<Card key={r.currency ?? r.rate} className="border-border p-4">
<div className="flex justify-between">
<p className="font-semibold text-foreground">ACBU/{r.currency}</p>
<p className="text-lg font-bold text-primary">{formatRate(r.rate)}</p>
<p className="font-semibold text-foreground">ACBU/{r.currency ?? 'Rate'}</p>
<p className="text-lg font-bold text-primary">{r.rate != null ? String(r.rate) : '—'}</p>
</div>
</Card>
))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-unique React keys when currency is missing.

If multiple rate rows have missing currency values, they'll share the same key (r.rate), potentially causing React reconciliation issues.

🐛 Use index as fallback key
-              rateRows.map((r: { currency?: string; rate?: number }) => (
-                  <Card key={r.currency ?? r.rate} className="border-border p-4">
+              rateRows.map((r: { currency?: string; rate?: number }, idx: number) => (
+                  <Card key={r.currency ?? `rate-${idx}`} className="border-border p-4">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rateRows.map((r: { currency?: string; rate?: number }) => (
<Card key={r.currency ?? r.rate} className="border-border p-4">
<div className="flex justify-between">
<p className="font-semibold text-foreground">ACBU/{r.currency}</p>
<p className="text-lg font-bold text-primary">{formatRate(r.rate)}</p>
<p className="font-semibold text-foreground">ACBU/{r.currency ?? 'Rate'}</p>
<p className="text-lg font-bold text-primary">{r.rate != null ? String(r.rate) : '—'}</p>
</div>
</Card>
))
rateRows.map((r: { currency?: string; rate?: number }, idx: number) => (
<Card key={r.currency ?? `rate-${idx}`} className="border-border p-4">
<div className="flex justify-between">
<p className="font-semibold text-foreground">ACBU/{r.currency ?? 'Rate'}</p>
<p className="text-lg font-bold text-primary">{r.rate != null ? String(r.rate) : '—'}</p>
</div>
</Card>
))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/mint/page.tsx` around lines 582 - 589, The JSX mapping over rateRows
(rateRows.map) uses key={r.currency ?? r.rate}, which can produce non-unique
keys when currency is missing; change the key to include a stable fallback
(e.g., key={`${r.currency ?? r.rate ?? index}`}) by using the map index as the
final fallback so each Card (Card component rendering AC BU/{r.currency ??
'Rate'}) has a unique key; update the map callback signature to (r, index) =>
... and use that composite key to avoid React reconciliation issues.

Comment thread contexts/auth-context.tsx
Comment on lines +22 to +24
login: (apiKey: string, userId: string, stellarAddress?: string | null) => void;
logout: () => Promise<void>;
setAuth: (userId: string | null, stellarAddress?: string | null) => void;
setAuth: (apiKey: string | null, userId: string | null, stellarAddress?: string | null) => void;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Update the known login callers in the same change.

This public API now expects (apiKey, userId, stellarAddress), but the provided sign-in and 2FA call sites still pass (userId, stellarAddress). That means setAuth will persist the user id as acbu_api_key and the stellar address as acbu_user_id, which breaks auth immediately after login.

🛠️ Required caller shape
login(result.api_key, result.user_id, result.stellar_address);

Also applies to: 111-114

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

In `@contexts/auth-context.tsx` around lines 22 - 24, The public auth API
signature for login changed to login(apiKey, userId, stellarAddress) but callers
still pass (userId, stellarAddress), causing setAuth to persist swapped values;
update every call site that invokes login (notably the sign-in and
2FA/verification handlers) to pass result.api_key first, then result.user_id,
then result.stellar_address (i.e., login(result.api_key, result.user_id,
result.stellar_address)); verify any helper wrappers that forward parameters to
login and update them similarly so setAuth receives correct values.

Comment thread i18n/messages/en-KE.json
Comment on lines +2 to +14
"home": {
"acbu": "ACBU",
"wallet_balance": "Wallet balance",
"fiat": "Fiat",
"simulated_usd_equivalent": "Simulated · USD equivalent",
"add_demo_funds": "Add demo funds",
"recent_activity": "Recent Activity",
"view_all": "View all",
"no_recent_activity": "No recent activity",
"send_money": "Send money",
"some_currencies_missing_rate": "Some currencies missing a rate",
"usd": "USD"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep en-KE in sync with the base English bundle.

home.approx_usd exists in i18n/messages/en.json but is missing here. If the localized home page requests that key under en-KE, you'll get a missing-message fallback or runtime error depending on the next-intl config.

🌍 Proposed fix
   "home": {
     "acbu": "ACBU",
     "wallet_balance": "Wallet balance",
     "fiat": "Fiat",
     "simulated_usd_equivalent": "Simulated · USD equivalent",
     "add_demo_funds": "Add demo funds",
     "recent_activity": "Recent Activity",
     "view_all": "View all",
     "no_recent_activity": "No recent activity",
     "send_money": "Send money",
     "some_currencies_missing_rate": "Some currencies missing a rate",
-    "usd": "USD"
+    "usd": "USD",
+    "approx_usd": "≈ USD"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"home": {
"acbu": "ACBU",
"wallet_balance": "Wallet balance",
"fiat": "Fiat",
"simulated_usd_equivalent": "Simulated · USD equivalent",
"add_demo_funds": "Add demo funds",
"recent_activity": "Recent Activity",
"view_all": "View all",
"no_recent_activity": "No recent activity",
"send_money": "Send money",
"some_currencies_missing_rate": "Some currencies missing a rate",
"usd": "USD"
},
"home": {
"acbu": "ACBU",
"wallet_balance": "Wallet balance",
"fiat": "Fiat",
"simulated_usd_equivalent": "Simulated · USD equivalent",
"add_demo_funds": "Add demo funds",
"recent_activity": "Recent Activity",
"view_all": "View all",
"no_recent_activity": "No recent activity",
"send_money": "Send money",
"some_currencies_missing_rate": "Some currencies missing a rate",
"usd": "USD",
"approx_usd": "≈ USD"
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@i18n/messages/en-KE.json` around lines 2 - 14, The en-KE messages are missing
the home.approx_usd key present in the base English bundle; add a "approx_usd"
entry under the "home" object (matching the base English meaning, e.g. "Approx.
USD") so that the home.approx_usd lookup succeeds for the en-KE locale; update
the "home" object in the en-KE JSON to include "approx_usd": "<appropriate
English text>" (use the exact string from the base i18n if available).

Comment thread lib/logger.ts
Comment on lines +5 to +31
function logMessage(level: LogLevel, message: string, data?: any) {
if (!isDebug && level !== 'error') return;

const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...(data !== undefined && { data })
};

// In debug mode, or for errors, output structured logs to console
if (level === 'error') {
console.error(JSON.stringify(logEntry));
} else if (level === 'warn') {
console.warn(JSON.stringify(logEntry));
} else if (level === 'info') {
console.info(JSON.stringify(logEntry));
} else {
console.log(JSON.stringify(logEntry));
}
}

export const logger = {
info: (message: string, data?: any) => logMessage('info', message, data),
warn: (message: string, data?: any) => logMessage('warn', message, data),
error: (message: string, data?: any) => logMessage('error', message, data),
debug: (message: string, data?: any) => logMessage('debug', message, data),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and its contents
git ls-files lib/logger.ts

Repository: Pi-Defi-world/acbu-frontend

Length of output: 85


🏁 Script executed:

# Search for ESLint configuration files
fd -t f '\.eslintrc.*|eslint\.config\.(js|mjs|cjs)' .

Repository: Pi-Defi-world/acbu-frontend

Length of output: 90


🏁 Script executed:

# Check for TypeScript configuration
git ls-files tsconfig.json

Repository: Pi-Defi-world/acbu-frontend

Length of output: 85


🏁 Script executed:

# Read the logger file to verify its contents
cat -n lib/logger.ts

Repository: Pi-Defi-world/acbu-frontend

Length of output: 1386


🏁 Script executed:

# Read ESLint configuration
cat -n eslint.config.js

Repository: Pi-Defi-world/acbu-frontend

Length of output: 527


🏁 Script executed:

# Read TypeScript configuration
cat -n tsconfig.json

Repository: Pi-Defi-world/acbu-frontend

Length of output: 1053


Replace any in the logger API.

The file uses any annotations that violate the configured @typescript-eslint/no-explicit-any rule. Replace with unknown type and create a LogData type alias for consistency:

Suggested fix
 type LogLevel = 'info' | 'warn' | 'error' | 'debug';
+type LogData = unknown;
 
-function logMessage(level: LogLevel, message: string, data?: any) {
+function logMessage(level: LogLevel, message: string, data?: LogData) {
@@
 export const logger = {
-  info: (message: string, data?: any) => logMessage('info', message, data),
-  warn: (message: string, data?: any) => logMessage('warn', message, data),
-  error: (message: string, data?: any) => logMessage('error', message, data),
-  debug: (message: string, data?: any) => logMessage('debug', message, data),
+  info: (message: string, data?: LogData) => logMessage('info', message, data),
+  warn: (message: string, data?: LogData) => logMessage('warn', message, data),
+  error: (message: string, data?: LogData) => logMessage('error', message, data),
+  debug: (message: string, data?: LogData) => logMessage('debug', message, data),
 };
🧰 Tools
🪛 ESLint

[error] 5-5: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 28-28: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 29-29: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 30-30: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 31-31: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

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

In `@lib/logger.ts` around lines 5 - 31, The logger currently uses the forbidden
any type; introduce a LogData type alias (e.g., type LogData = unknown) and
replace all occurrences of any with LogData: update the logMessage signature to
logMessage(level: LogLevel, message: string, data?: LogData), update the logger
API methods (info/warn/error/debug) to accept data?: LogData, and keep the
existing logEntry construction and console branches unchanged so the runtime
behavior is identical while satisfying `@typescript-eslint/no-explicit-any`.

Comment thread lib/logger.ts
Comment on lines +5 to +24
function logMessage(level: LogLevel, message: string, data?: any) {
if (!isDebug && level !== 'error') return;

const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...(data !== undefined && { data })
};

// In debug mode, or for errors, output structured logs to console
if (level === 'error') {
console.error(JSON.stringify(logEntry));
} else if (level === 'warn') {
console.warn(JSON.stringify(logEntry));
} else if (level === 'info') {
console.info(JSON.stringify(logEntry));
} else {
console.log(JSON.stringify(logEntry));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "logger.ts" --full-path

Repository: Pi-Defi-world/acbu-frontend

Length of output: 85


🏁 Script executed:

cat -n lib/logger.ts

Repository: Pi-Defi-world/acbu-frontend

Length of output: 1386


🏁 Script executed:

fd -t f -e json -e js -e ts "eslint" --full-path | head -20

Repository: Pi-Defi-world/acbu-frontend

Length of output: 88


🏁 Script executed:

fd -t f "eslintrc*" --full-path

Repository: Pi-Defi-world/acbu-frontend

Length of output: 53


🏁 Script executed:

fd -t f "eslint.config.*" --full-path

Repository: Pi-Defi-world/acbu-frontend

Length of output: 88


🏁 Script executed:

cat -n eslint.config.js

Repository: Pi-Defi-world/acbu-frontend

Length of output: 527


🏁 Script executed:

rg -t ts -t js "serialize|JSON\.stringify" --max-count=20

Repository: Pi-Defi-world/acbu-frontend

Length of output: 941


🏁 Script executed:

cat -n lib/error-reporting.ts | head -50

Repository: Pi-Defi-world/acbu-frontend

Length of output: 1722


🏁 Script executed:

node <<'EOF'
const tseslint = require('typescript-eslint');
const config = tseslint.configs.recommended;
// Check if no-explicit-any is in the recommended rules
const rules = config[config.length - 1]?.rules || {};
console.log('Rules with "any":', Object.keys(rules).filter(k => k.includes('any')));
console.log('@typescript-eslint/no-explicit-any:', rules['@typescript-eslint/no-explicit-any']);
EOF

Repository: Pi-Defi-world/acbu-frontend

Length of output: 172


🏁 Script executed:

node <<'EOF'
// Test JSON.stringify with Error objects
const testError = new Error('Test error');
console.log('Error serialized:', JSON.stringify({ error: testError }));

// Test with circular reference
const circular = { a: 1 };
circular.self = circular;
try {
  console.log('Circular:', JSON.stringify(circular));
} catch (e) {
  console.log('Circular throws:', e.message);
}

// Test with circular in nested object
const obj = { data: {} };
obj.data.parent = obj;
try {
  console.log('Nested circular:', JSON.stringify(obj));
} catch (e) {
  console.log('Nested circular throws:', e.message);
}
EOF

Repository: Pi-Defi-world/acbu-frontend

Length of output: 473


Make log serialization fail-safe.

JSON.stringify(logEntry) throws on circular payloads (e.g., Converting circular structure to JSON) and collapses Error instances to {}, stripping stack traces. This causes logging calls to fail inside error handlers and lose the diagnostic information you are trying to capture.

The any type annotations also violate the @typescript-eslint/no-explicit-any rule (enabled as error in your ESLint config).

Suggested fix
+function safeSerialize(value: unknown) {
+  const seen = new WeakSet<object>();
+
+  return JSON.stringify(value, (_key, current) => {
+    if (current instanceof Error) {
+      return {
+        name: current.name,
+        message: current.message,
+        stack: current.stack,
+      };
+    }
+
+    if (typeof current === 'object' && current !== null) {
+      if (seen.has(current)) return '[Circular]';
+      seen.add(current);
+    }
+
+    return current;
+  });
+}
+
 function logMessage(level: LogLevel, message: string, data?: any) {
   if (!isDebug && level !== 'error') return;
@@
   // In debug mode, or for errors, output structured logs to console
   if (level === 'error') {
-    console.error(JSON.stringify(logEntry));
+    console.error(safeSerialize(logEntry));
   } else if (level === 'warn') {
-    console.warn(JSON.stringify(logEntry));
+    console.warn(safeSerialize(logEntry));
   } else if (level === 'info') {
-    console.info(JSON.stringify(logEntry));
+    console.info(safeSerialize(logEntry));
   } else {
-    console.log(JSON.stringify(logEntry));
+    console.log(safeSerialize(logEntry));
   }
 }
🧰 Tools
🪛 ESLint

[error] 5-5: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

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

In `@lib/logger.ts` around lines 5 - 24, logMessage currently uses JSON.stringify
directly and accepts data: any, which fails on circular structures and strips
Error details; change the signature to use data?: unknown and implement a
fail-safe serializer (e.g., safeStringify) that (1) converts Error instances to
plain objects preserving name, message, stack and enumerable properties, (2)
guards against circular refs using a seen Set (or use util.inspect as fallback)
and returns a stable JSON string, and (3) never throws; then replace all
JSON.stringify(logEntry) calls in logMessage with safeStringify(logEntry) so
logging never fails and error stacks are preserved.

Comment thread package.json
Comment on lines +73 to +82
"devDependencies": {
"@axe-core/playwright": "^4.11.2",
"@eslint/js": "^10.0.1",
"@playwright/test": "^1.59.1",
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9.7.0",
"eslint-plugin-react": "^7.37.5",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

ESLint @eslint/js version compatibility eslint v9 v10

💡 Result:

ESLint v9 is compatible with @eslint/js ^9.x versions. ESLint v10 is compatible with @eslint/js ^10.x versions. @eslint/js v10.x explicitly supports ESLint ^10.0.0 as an optional peer dependency, as added in the ESLint v10 release process. The official migration guide for v10 mentions using @eslint/js v10.x, particularly noting restored 'name' properties in core configs and compatibility notes with @eslint/eslintrc FlatCompat. For v9, official docs and examples use @eslint/js ^9.x (e.g., v9.35.0+ shown in npm and migration guides). npm data shows @eslint/js latest is v10.0.1 (for ESLint v10), with v9.39.4 as the latest 9.x maintenance version (still actively downloaded). No cross-version compatibility issues are noted in official docs; each major ESLint version aligns with corresponding @eslint/js major versions. Ecosystem plugins may lag v10 support, but @eslint/js itself is designed for version matching.

Citations:


🌐 Web query:

ESLint v10 migration guide breaking changes @eslint/js``

💡 Result:

ESLint v10.0.0 migration guide and breaking changes: Official migration guide: https://eslint.org/docs/latest/use/migrate-to-10.0.0 Key breaking changes for users: - Node.js versions below v20.19.0, v21.x, v23.x no longer supported. Requires Node.js ^20.19.0 || ^22.13.0 || >=24.0.0. Upgrade Node.js if necessary. - Old eslintrc configuration format (.eslintrc.*, package.json eslintConfig) completely removed. Must use flat config (eslint.config.js). Migrate using npx @eslint/migrate-config .eslintrc.json. Deprecated APIs FlatESLint and LegacyESLint removed; use ESLint class. - New default configuration file lookup: starts from linted file directory upward (previously cwd-based). Remove v10_config_lookup_from_file flag if used. - eslint:recommended updated with new rules. Re-run lint after migration. - eslint-env comments now reported as errors. Remove them from code. - Jiti < v2.2.0 no longer supported for TS configs. - JSX references now tracked by default, may cause new warnings (e.g., no-unused-vars). - stylish formatter uses native styleText instead of chalk. - Rule changes: radix rule deprecated "always"/"as-needed" options; no-shadow-restricted-names reports globalThis; func-names schema stricter; no-invalid-regexp allowConstructorFlags requires unique items. - name property added back to core configs from @eslint/js (v10 compatible). If using FlatCompat, upgrade @eslint/eslintrc. For @eslint/js in flat config (eslint.config.js): Install: npm install --save-dev @eslint/js Example: import js from "@eslint/js"; import { defineConfig } from "eslint/config"; // or "eslint/config" if available export default defineConfig([ js.configs.recommended, // or { plugins: { js }, extends: ["js/recommended"] }, { rules: { // overrides } } ]); Other changes for plugin/integration devs: removal of deprecated APIs/methods (SourceCode methods, context members, nodeType in LintMessage, etc.), Program AST range spans full text, stricter RuleTester, etc. ESLint v10 released Feb 2026. Most users can upgrade directly if on flat config from v9; otherwise migrate config first. Migration steps: 1. Upgrade Node.js. 2. npm install eslint@latest @eslint/js@latest 3. Migrate config if needed. 4. Remove eslint-env comments, legacy files. 5. Test lint run. 6. Update editor integrations (e.g., VSCode eslint.useFlatConfig default true).

Citations:


🌐 Web query:

@eslint/js version 10 eslint version 9 compatibility

💡 Result:

@eslint/js version 10 is not compatible with ESLint version 9. Use @eslint/js version 9 instead. @eslint/js v10.0.0 was initially released by accident and marked as deprecated/avoid (Sep 2024), but a proper v10.0.1 was later released (as of 2026-04). However, @eslint/js v10 has a peer dependency on eslint ^10.0.0 (added intentionally after ESLint v10 release in Feb 2026), which prevents installation with ESLint v9 due to strict peer dep checking in npm. The official ESLint v10 migration guide mentions using @eslint/js v10.x only in the context of ESLint v10, and notes potential issues with legacy compat tools that require updating @eslint/eslintrc. No sources indicate backward compatibility of @eslint/js v10 with ESLint v9. For ESLint v9, stick to @eslint/js ^9.x (latest around 9.39.x as of early 2026). ESLint v9 remains in maintenance.

Citations:


Align @eslint/js major version with eslint.

@eslint/js@^10.0.1 requires eslint@^10.0.0, but the current setup pairs it with eslint@^9.7.0. This version mismatch will cause peer dependency conflicts. Either upgrade eslint to v10 and use @eslint/js@^10, or downgrade @eslint/js to ^9.x to match eslint@^9.7.0.

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

In `@package.json` around lines 73 - 82, The devDependency versions in
package.json are mismatched: "@eslint/js" is ^10.0.1 while "eslint" is ^9.7.0;
update one to align majors. Either bump "eslint" to a v10 release (e.g., change
the "eslint" entry to a ^10.x version) to match "@eslint/js@^10", or downgrade
"@eslint/js" to a ^9.x release so it matches the existing "eslint@^9.7.0";
update the package.json devDependencies accordingly and run your package manager
to refresh lockfiles.

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.

F-047 — Mobile nav icon-only links lack aria-label

8 participants