Skip to content

Feat/fro 3 glob change#4

Closed
midhun123758 wants to merge 9 commits into
mainfrom
feat/FRO-3-glob-change
Closed

Feat/fro 3 glob change#4
midhun123758 wants to merge 9 commits into
mainfrom
feat/FRO-3-glob-change

Conversation

@midhun123758

@midhun123758 midhun123758 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added a 3D globe landing experience with interactive country exploration.
    • Added account registration, login, email verification, Google sign-in, password recovery, and reset flows.
    • Added protected dashboard access with profile details and logout.
    • Added automatic session refresh and authentication state handling.
    • Added responsive authentication layouts, visual effects, and accessibility enhancements.
  • Documentation

    • Added setup guidance and environment variable documentation.
  • Chores

    • Added automated quality checks, testing, coverage reporting, and production build validation.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR establishes a Vite React frontend for the AI Trading Copilot, including authentication and recovery flows, protected routing, a globe-based landing page, dashboard, shared styling, typed API access, automated tests, and GitHub Actions CI.

Changes

Frontend foundation

Layer / File(s) Summary
Project tooling and configuration
.env.example, package.json, tsconfig*.json, vite.config.ts, vitest.config.ts, .prettierrc.json, .oxlintrc.json, .vscode/*
Adds Node 20 configuration, Vite/TypeScript/Vitest tooling, environment declarations, formatting and lint rules, editor settings, and project scripts.
CI and login validation
.github/workflows/ci.yml, src/test/*
Adds CI checks for type safety, linting, formatting, coverage, and production builds, with Login component tests and DOM matcher setup.

Authentication and application flows

Layer / File(s) Summary
Authentication contracts and session API
src/features/auth/auth.types.ts, src/features/auth/auth.service.ts, src/lib/api.client.ts, src/stores/userStore.ts
Adds typed authentication endpoints, bearer-token injection, refresh-token retry handling, persisted user state, and logout/session initialization actions.
Authentication pages and routing
src/App.tsx, src/components/*, src/constants/routes.constants.ts, src/features/auth/AuthGuard.tsx
Adds routed login, registration, password recovery, email verification, OAuth callback, success, and protected dashboard flows.
Application rendering and shared UI
index.html, src/main.tsx, src/components/ui/*, src/features/dashboard/*, src/styles/*
Adds the browser entrypoint, error boundary, authentication layout, dashboard view, reset styles, design tokens, and component styling.

Globe landing experience

Layer / File(s) Summary
Globe visualization and landing page
src/features/globe/*, src/index.css
Adds nation data, globe configuration, globe.gl lifecycle and controls, remote country polygons, interactive navigation, animated star backgrounds, and landing-page layout styling.

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

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant App
  participant AuthGuard
  participant authService
  participant API
  Visitor->>App: Open application
  App->>authService: Initialize session
  authService->>API: GET /auth/me
  API-->>authService: User profile or unauthorized
  App-->>Visitor: Render landing or authentication flow
  Visitor->>authService: Submit credentials
  authService->>API: POST /auth/login
  API-->>authService: Access token
  authService->>API: GET /auth/me
  API-->>authService: User profile
  AuthGuard-->>Visitor: Render protected dashboard
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too vague and typo-ridden to clearly describe the PR's main changes. Rename it to a concise, specific summary of the change, such as adding the Globe landing page and auth frontend setup.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/FRO-3-glob-change

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

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

⚠️ Outside diff range comments (2)
src/features/globe/GlobeView.tsx (1)

1-129: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Prettier formatting failure.

CI reports a Prettier --check failure on this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/globe/GlobeView.tsx` around lines 1 - 129, Run Prettier on the
GlobeView component and apply its formatting changes throughout the file,
including JSX spacing, quote style, indentation, and trailing whitespace, so it
passes the repository’s Prettier --check.

Source: Pipeline failures

src/features/globe/useGlobe.ts (1)

1-196: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Prettier formatting failure.

CI reports a Prettier --check failure on this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/globe/useGlobe.ts` around lines 1 - 196, Format the entire
useGlobe hook with the repository’s Prettier configuration, correcting
whitespace, indentation, line wrapping, and trailing formatting without changing
behavior. Ensure the resulting file passes the Prettier check.

Source: Pipeline failures

🧹 Nitpick comments (13)
src/features/globe/GlobeView.tsx (1)

74-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Search input has no accessible label.

The Ctrl+K search <input> relies solely on placeholder text, which disappears on focus/input and isn't a substitute for a label for screen-reader users.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/globe/GlobeView.tsx` around lines 74 - 77, Add an accessible,
persistent label to the search input within the nav-search container,
associating it with the input through a unique id and label mechanism. Preserve
the existing placeholder and Ctrl+K search behavior.
src/features/globe/useGlobe.ts (2)

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

Doc comment overstates the implementation; unused imports/config confirm an unfinished feature.

The JSDoc claims responsibility for
"Runs the animation loop for waving stock chart paths" (line 12), but line 92 states no animation loop exists. Combined with the unused PathData/LabelData/Coordinate type imports (flagged by static analysis) and the unused PATH_*/LABEL_*/ANIMATION_SPEED fields in globe.constants.ts, this looks like a partially-removed or not-yet-wired chart-path/label feature left behind as dead references. Recommend either removing the stale imports/doc claim, or tracking the missing implementation explicitly.

♻️ Suggested cleanup
-import type { GeoJSON, GeoFeature, Coordinate, PathData, LabelData } from '`@/types/globe.types`';
+import type { GeoJSON, GeoFeature } from '`@/types/globe.types`';

And update the JSDoc at line 12 to drop the "waving stock chart paths" claim, or file a follow-up to implement pathsData/labelsData using the existing PATH_*/LABEL_* config.

Also applies to: 92-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/globe/useGlobe.ts` around lines 1 - 18, Remove the stale
PathData, LabelData, and Coordinate imports from useGlobe and update its JSDoc
responsibilities to omit the claim that it runs an animation loop for waving
stock chart paths. Leave the existing globe behavior unchanged; do not add the
unfinished pathsData/labelsData feature.

Source: Linters/SAST tools


51-53: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Textures/backgrounds loaded from unpkg CDN with no error handling.

globeImageUrl/bumpImageUrl/backgroundImageUrl point at unpkg.com (same CDN the GeoJSON TODO already flags for local hosting) but have no onError/fallback if the CDN is slow or unavailable, unlike the GeoJSON fetch which at least logs failures. Consider bundling these assets locally (as already planned for the GeoJSON) or adding basic failure handling/fallback color.

Also applies to: 140-140

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/globe/useGlobe.ts` around lines 51 - 53, Update the texture
configuration in useGlobe to avoid relying on unhandled unpkg CDN failures:
preferably replace the globeImageUrl, bumpImageUrl, and backgroundImageUrl
remote URLs with bundled local assets, or add the library-supported load-error
handling with a visible fallback color. Apply the same handling to the related
texture configuration referenced by the comment.
src/index.css (1)

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

Duplicate overlay/country-list rules now exist in three files.

These rules (#ui-overlay, .terminal-enter-btn, .country-list, .country-item*, .resume-rotate-btn) duplicate — with a different green color scheme — the newer white-themed versions in src/styles/components/overlay.css and src/styles/components/country-list.css. Once the overlay/country-list markup is wired up in GlobeView.tsx, having the same class names defined in multiple stylesheets makes the resulting style dependent on CSS import order rather than intent. Recommend removing this legacy block now that dedicated component stylesheets exist.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.css` around lines 20 - 207, Remove the legacy overlay and
country-list style rules from src/index.css, including `#ui-overlay`,
.terminal-enter-btn, .country-list, .country-item and its state rules, and
.resume-rotate-btn. Keep the dedicated component styles in overlay.css and
country-list.css as the sole definitions for these selectors.
src/test/Login.test.tsx (1)

64-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer vi.mocked() over as any casts.

(authService.login as any).mockResolvedValueOnce(...) loses type-checking on the mock's resolved value shape. vi.mocked(authService.login).mockResolvedValueOnce(...) gets the same runtime behavior with type safety.

Also applies to: 96-96

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/Login.test.tsx` around lines 64 - 72, Replace the as any casts on
authService.login and authService.getMe mock setup with vi.mocked(...),
including the additional occurrence, while preserving the existing
mockResolvedValueOnce responses and behavior.
src/components/Login.tsx (2)

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

Duplicate API_URL fallback logic.

Same import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000' fallback is defined in src/lib/api.client.ts. Extracting a shared constant avoids the two copies drifting apart.

♻️ Proposed fix
+// src/config/env.ts
+export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
-          onClick={() => {
-            const API_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000';
-            window.location.href = `${API_URL}/auth/oauth2/google/login`;
-          }}
+          onClick={() => {
+            window.location.href = `${API_BASE_URL}/auth/oauth2/google/login`;
+          }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Login.tsx` around lines 241 - 243, Extract the shared API base
URL fallback used by the Login component’s OAuth redirect into a reusable
constant, then import and use that constant in both Login and the existing
api.client implementation. Remove the duplicate inline
`import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'` expression while
preserving the current redirect behavior.

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

Repeated inline error-banner/spinner styles across auth forms.

The same error-alert style={{...}} block (and the spinner button pattern) is duplicated across Login.tsx, ForgotPassword.tsx, Register.tsx, and ResetPassword.tsx. Extracting shared CSS classes (similar to the existing auth-input-field/auth-submit-btn/back-link classes already used) would reduce drift and ease future theming.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Login.tsx` around lines 83 - 145, Extract the duplicated auth
error-banner and spinner-button styling from Login.tsx, ForgotPassword.tsx,
Register.tsx, and ResetPassword.tsx into shared CSS classes, following the
existing auth-input-field, auth-submit-btn, and back-link pattern. Replace the
corresponding inline style objects and spinner button styles in each form with
the shared classes while preserving their current layout, states, and behavior.
src/features/auth/auth.service.ts (1)

68-74: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Type register's payload instead of any.

Every other method in this file uses a precise request/response type; data: any drops that safety net even though the only caller passes a fixed { username, email, password } shape.

♻️ Proposed fix
+export interface RegisterRequest {
+  username: string;
+  email: string;
+  password: string;
+}
+
   /**
    * Register a new user.
    */
-  async register(data: any): Promise<UserResponse> {
+  async register(data: RegisterRequest): Promise<UserResponse> {
     const response = await apiClient.post<UserResponse>('/auth/register', data);
     return response.data;
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/auth/auth.service.ts` around lines 68 - 74, Update the register
method’s data parameter to use a precise request payload type instead of any,
matching the fixed username, email, and password shape used by its caller and
the existing request-type conventions in auth.service.ts. Preserve the
UserResponse return type and API call behavior.
src/components/ui/ErrorBoundary/ErrorBoundary.tsx (1)

35-39: 📐 Maintainability & Code Quality | 🔵 Trivial

TODO: wire up real error tracking (e.g. Sentry) before production.

Currently only console.error logs; caught runtime errors won't be visible in production monitoring.

Want me to draft a Sentry (or similar) integration for componentDidCatch, or open a tracking issue for this TODO?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/ErrorBoundary/ErrorBoundary.tsx` around lines 35 - 39,
Replace the console-only logging in ErrorBoundary.componentDidCatch with the
project’s real production error-tracking integration, such as the configured
Sentry client, and report both the caught error and component stack context.
Remove the TODO and retain the boundary’s existing error capture behavior.
src/App.tsx (1)

45-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded slice length depends on the route path staying exactly /reset-password/.

pathname.substring(16) relies on the literal string length; if ROUTES.RESET_PASSWORD's underlying path segment ever changes, this silently breaks (extracting a malformed token) without any compiler warning.

♻️ Suggested fix using a fixed prefix constant/regex
-    if (window.location.pathname.startsWith('/reset-password/')) {
-      const token = window.location.pathname.substring(16); // '/reset-password/'.length === 16
+    const RESET_PASSWORD_PREFIX = '/reset-password/';
+    if (window.location.pathname.startsWith(RESET_PASSWORD_PREFIX)) {
+      const token = window.location.pathname.slice(RESET_PASSWORD_PREFIX.length);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/App.tsx` around lines 45 - 49, Update the reset-password redirect logic
in App so token extraction derives the prefix length from the same route pattern
used for matching, rather than the hardcoded substring index 16. Reuse the
existing ROUTES.RESET_PASSWORD symbol or define a shared prefix constant,
preserving the current redirect behavior and token value.
src/components/SuccessPage.tsx (2)

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

Prop name onNavigateToDashboard doesn't match its actual behavior/button label.

Per App.tsx's wiring (onNavigateToDashboard={() => navigate(ROUTES.LOGIN)}), this callback actually redirects to login, not the dashboard — matching the button's own label "Go to Sign In". The prop name is misleading for future maintainers and already required a clarifying comment at the call site. Consider renaming to onGoToSignIn (or similar) to match actual behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/SuccessPage.tsx` around lines 49 - 56, Rename the SuccessPage
callback prop and its usages from onNavigateToDashboard to onGoToSignIn,
including the App.tsx wiring and the button’s onClick handler, while preserving
the existing redirect to ROUTES.LOGIN and the “Go to Sign In” label.

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

Extract repeated inline style blocks into CSS classes.

The icon-circle wrapper (lines 20-31) and button-container (lines 40-48) style objects are duplicated verbatim in VerifyEmail.tsx (icon circle at lines 96-107/143-154, button container at lines 119-127/164-172). Both files already import auth-layout.css and use its classes (auth-form-card, form-header, auth-submit-btn), so extracting these into named classes (e.g. .status-icon-circle, .status-icon-circle--error, .auth-action-group) would remove the duplication and centralize the styling.

♻️ Proposed refactor
+.status-icon-circle {
+  width: 64px;
+  height: 64px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8px;
+}
+.status-icon-circle--success { background-color: `#f0fdf4`; }
+.status-icon-circle--error { background-color: `#fef2f2`; }
+
+.auth-action-group {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+  margin-top: 16px;
+}
-        <div
-          style={{
-            width: '64px',
-            height: '64px',
-            borderRadius: '50%',
-            backgroundColor: '`#f0fdf4`',
-            display: 'flex',
-            alignItems: 'center',
-            justifyContent: 'center',
-            marginBottom: '8px',
-          }}
-        >
+        <div className="status-icon-circle status-icon-circle--success">

Also applies to: 40-48

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/SuccessPage.tsx` around lines 20 - 33, Extract the duplicated
inline styles for the icon-circle wrapper and button-container from
SuccessPage.tsx and the corresponding elements in VerifyEmail.tsx into shared
classes in auth-layout.css, such as status-icon-circle,
status-icon-circle--error, and auth-action-group. Replace the inline style
objects with those classes while preserving the existing success/error visual
variants and layout.
src/components/ui/AuthLayout/AuthLayout.tsx (1)

18-31: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing Space-key activation for the role="button" element.

Only Enter is handled in onKeyDown; native/ARIA button semantics also expect Space to activate. Consider using a real <button> element (styled via CSS) instead of a div with manually-managed ARIA semantics — it gets correct keyboard handling for free.

♻️ Proposed fix
-        <div
-          className="brand-header"
-          onClick={() => navigate(ROUTES.HOME)}
-          role="button"
-          tabIndex={0}
-          onKeyDown={(e) => e.key === 'Enter' && navigate(ROUTES.HOME)}
-          style={{ cursor: 'pointer' }}
-          aria-label="Go back to home"
-        >
+        <button
+          type="button"
+          className="brand-header"
+          onClick={() => navigate(ROUTES.HOME)}
+          aria-label="Go back to home"
+        >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/AuthLayout/AuthLayout.tsx` around lines 18 - 31, Update the
brand navigation element in AuthLayout to use a native button instead of a div
with role="button", preserving its click navigation to ROUTES.HOME, accessible
label, logo content, and visual styling; remove the manual keyboard handling so
native Enter and Space activation are provided automatically.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 15-16: Update the actions/checkout@v4 step in the CI workflow to
disable persisted credentials by setting persist-credentials to false; leave the
checkout behavior otherwise unchanged.

In @.gitignore:
- Around line 15-18: Update the environment-file ignore patterns in .gitignore
to ignore all .env variants, including files such as .env.production and
.env.staging, while explicitly keeping the example template unignored.

In `@package.json`:
- Around line 7-9: Update the package.json engines.node constraint to require
Node >=20.19.0 (or the supported >=22.12.0 alternative), while preserving the
existing npm engine requirement, so older Node 20 releases no longer satisfy the
manifest.

In `@src/features/globe/GlobeView.tsx`:
- Around line 1-20: Update GlobeView to retain the object returned by
useGlobe(containerRef) and render the missing home overlay: create country-list
buttons for each NATIONS entry that call flyTo with its name, latitude, and
longitude, and render the resume-rotate button calling resumeRotation when
activeCountry is set. Use the existing overlay class names and preserve the
globe container rendering.

In `@src/features/globe/useGlobe.ts`:
- Around line 45-192: Track both timeout handles created for the initial
altitude and brightness updates in the useEffect setup, then clear them during
the effect cleanup before tearing down the globe. Ensure cleanup prevents either
delayed callback from accessing the destroyed world instance during unmount or
remount.
- Around line 102-132: Normalize scrollAngle to a bounded longitude range
immediately before passing it as lng in world.pointOfView within handleWheel,
while preserving the existing rotation direction and zoom behavior.

In `@src/index.css`:
- Around line 1-565: Resolve the Prettier formatting failure across the
stylesheet by running Prettier on src/index.css and applying its generated
formatting, including spacing, indentation, declaration layout, and comment
formatting. Preserve all selectors, values, and visual behavior.
- Line 183: Update the box-shadow declaration using currentColor to use the
lowercase currentcolor spelling required by the value-keyword-case stylelint
rule, while preserving the existing shadow values.
- Around line 538-540: Replace the display:none rule for input[type="radio"]
with the project's visually-hidden/sr-only pattern, keeping the inputs focusable
and available to assistive technologies while preserving .radio-custom as the
visual indicator.

In `@src/lib/api.client.ts`:
- Around line 6-9: Update the axios configuration in apiClient to define a
finite request timeout, using the project’s established timeout configuration or
constant if available, so hung backend requests terminate instead of remaining
pending indefinitely.
- Around line 21-58: Introduce a shared in-flight refresh promise for the
response interceptor so concurrent 401 handlers reuse one POST /auth/refresh
request instead of starting independent refreshes. Update the refresh flow
around apiClient.interceptors.response.use and preserve the existing token
update and request retry behavior; clear the shared promise after completion,
and only logout when the shared refresh operation actually fails or the refresh
endpoint itself returns 401.

In `@src/stores/userStore.ts`:
- Around line 49-61: Update initAuth to handle the caught error instead of
silently discarding it: remove the unused catch parameter if no error reporting
is required, or log/propagate the error using the project’s established
mechanism while preserving the existing unauthenticated state reset. Ensure
genuine refresh or network failures remain distinguishable from a normal
unauthenticated result.

In `@src/styles/components/country-list.css`:
- Line 89: Update the box-shadow declaration in the country-list styles to use
the casing required by the stylelint value-keyword-case rule for the current
color keyword, matching the equivalent correction in index.css.

In `@src/styles/reset.css`:
- Line 22: Update the text-rendering declaration in the reset stylesheet so its
optimizeLegibility value satisfies Stylelint’s value-keyword-case rule,
preferably by using lowercase casing; preserve the existing rendering behavior.

In `@src/styles/tokens.css`:
- Around line 33-35: Update the font-family declarations in --font-family-base
to satisfy Stylelint value-keyword-case by quoting the flagged font names,
matching the existing quoted names in --font-family-mono; preserve the current
fallback order and values.

In `@src/test/setup.ts`:
- Around line 1-5: Update the Vitest global setup import in src/test/setup.ts to
use the Vitest-specific `@testing-library/jest-dom/vitest` entrypoint instead of
the generic jest-dom entrypoint, preserving the existing setup behavior.

---

Outside diff comments:
In `@src/features/globe/GlobeView.tsx`:
- Around line 1-129: Run Prettier on the GlobeView component and apply its
formatting changes throughout the file, including JSX spacing, quote style,
indentation, and trailing whitespace, so it passes the repository’s Prettier
--check.

In `@src/features/globe/useGlobe.ts`:
- Around line 1-196: Format the entire useGlobe hook with the repository’s
Prettier configuration, correcting whitespace, indentation, line wrapping, and
trailing formatting without changing behavior. Ensure the resulting file passes
the Prettier check.

---

Nitpick comments:
In `@src/App.tsx`:
- Around line 45-49: Update the reset-password redirect logic in App so token
extraction derives the prefix length from the same route pattern used for
matching, rather than the hardcoded substring index 16. Reuse the existing
ROUTES.RESET_PASSWORD symbol or define a shared prefix constant, preserving the
current redirect behavior and token value.

In `@src/components/Login.tsx`:
- Around line 241-243: Extract the shared API base URL fallback used by the
Login component’s OAuth redirect into a reusable constant, then import and use
that constant in both Login and the existing api.client implementation. Remove
the duplicate inline `import.meta.env.VITE_API_BASE_URL ||
'http://localhost:8000'` expression while preserving the current redirect
behavior.
- Around line 83-145: Extract the duplicated auth error-banner and
spinner-button styling from Login.tsx, ForgotPassword.tsx, Register.tsx, and
ResetPassword.tsx into shared CSS classes, following the existing
auth-input-field, auth-submit-btn, and back-link pattern. Replace the
corresponding inline style objects and spinner button styles in each form with
the shared classes while preserving their current layout, states, and behavior.

In `@src/components/SuccessPage.tsx`:
- Around line 49-56: Rename the SuccessPage callback prop and its usages from
onNavigateToDashboard to onGoToSignIn, including the App.tsx wiring and the
button’s onClick handler, while preserving the existing redirect to ROUTES.LOGIN
and the “Go to Sign In” label.
- Around line 20-33: Extract the duplicated inline styles for the icon-circle
wrapper and button-container from SuccessPage.tsx and the corresponding elements
in VerifyEmail.tsx into shared classes in auth-layout.css, such as
status-icon-circle, status-icon-circle--error, and auth-action-group. Replace
the inline style objects with those classes while preserving the existing
success/error visual variants and layout.

In `@src/components/ui/AuthLayout/AuthLayout.tsx`:
- Around line 18-31: Update the brand navigation element in AuthLayout to use a
native button instead of a div with role="button", preserving its click
navigation to ROUTES.HOME, accessible label, logo content, and visual styling;
remove the manual keyboard handling so native Enter and Space activation are
provided automatically.

In `@src/components/ui/ErrorBoundary/ErrorBoundary.tsx`:
- Around line 35-39: Replace the console-only logging in
ErrorBoundary.componentDidCatch with the project’s real production
error-tracking integration, such as the configured Sentry client, and report
both the caught error and component stack context. Remove the TODO and retain
the boundary’s existing error capture behavior.

In `@src/features/auth/auth.service.ts`:
- Around line 68-74: Update the register method’s data parameter to use a
precise request payload type instead of any, matching the fixed username, email,
and password shape used by its caller and the existing request-type conventions
in auth.service.ts. Preserve the UserResponse return type and API call behavior.

In `@src/features/globe/GlobeView.tsx`:
- Around line 74-77: Add an accessible, persistent label to the search input
within the nav-search container, associating it with the input through a unique
id and label mechanism. Preserve the existing placeholder and Ctrl+K search
behavior.

In `@src/features/globe/useGlobe.ts`:
- Around line 1-18: Remove the stale PathData, LabelData, and Coordinate imports
from useGlobe and update its JSDoc responsibilities to omit the claim that it
runs an animation loop for waving stock chart paths. Leave the existing globe
behavior unchanged; do not add the unfinished pathsData/labelsData feature.
- Around line 51-53: Update the texture configuration in useGlobe to avoid
relying on unhandled unpkg CDN failures: preferably replace the globeImageUrl,
bumpImageUrl, and backgroundImageUrl remote URLs with bundled local assets, or
add the library-supported load-error handling with a visible fallback color.
Apply the same handling to the related texture configuration referenced by the
comment.

In `@src/index.css`:
- Around line 20-207: Remove the legacy overlay and country-list style rules
from src/index.css, including `#ui-overlay`, .terminal-enter-btn, .country-list,
.country-item and its state rules, and .resume-rotate-btn. Keep the dedicated
component styles in overlay.css and country-list.css as the sole definitions for
these selectors.

In `@src/test/Login.test.tsx`:
- Around line 64-72: Replace the as any casts on authService.login and
authService.getMe mock setup with vi.mocked(...), including the additional
occurrence, while preserving the existing mockResolvedValueOnce responses and
behavior.
🪄 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 Plus

Run ID: cda31f5d-8b5a-4321-a311-e4d4d2958d67

📥 Commits

Reviewing files that changed from the base of the PR and between 427d4d3 and 47a30cc.

⛔ Files ignored due to path filters (4)
  • package-lock.json is excluded by !**/package-lock.json
  • public/favicon.svg is excluded by !**/*.svg
  • public/icons.svg is excluded by !**/*.svg
  • public/trading_banner.png is excluded by !**/*.png
📒 Files selected for processing (53)
  • .env.example
  • .github/workflows/ci.yml
  • .gitignore
  • .node-version
  • .nvmrc
  • .oxlintrc.json
  • .prettierignore
  • .prettierrc.json
  • .vscode/extensions.json
  • .vscode/settings.json
  • README.md
  • index.html
  • package.json
  • src/App.tsx
  • src/components/AuthCallback.tsx
  • src/components/ForgotPassword.tsx
  • src/components/Login.tsx
  • src/components/Register.tsx
  • src/components/ResetPassword.tsx
  • src/components/SuccessPage.tsx
  • src/components/VerifyEmail.tsx
  • src/components/ui/AuthLayout/AuthLayout.tsx
  • src/components/ui/ErrorBoundary/ErrorBoundary.tsx
  • src/components/ui/ErrorBoundary/index.ts
  • src/constants/routes.constants.ts
  • src/features/auth/AuthGuard.tsx
  • src/features/auth/auth.service.ts
  • src/features/auth/auth.types.ts
  • src/features/dashboard/DashboardPage.tsx
  • src/features/globe/GlobeView.tsx
  • src/features/globe/globe.constants.ts
  • src/features/globe/nations.constants.ts
  • src/features/globe/useGlobe.ts
  • src/index.css
  • src/lib/api.client.ts
  • src/main.tsx
  • src/stores/userStore.ts
  • src/styles/components/auth-layout.css
  • src/styles/components/country-list.css
  • src/styles/components/dashboard.css
  • src/styles/components/overlay.css
  • src/styles/reset.css
  • src/styles/tokens.css
  • src/test/Login.test.tsx
  • src/test/setup.ts
  • src/types.d.ts
  • src/types/env.d.ts
  • src/types/globe.types.ts
  • tsconfig.app.json
  • tsconfig.json
  • tsconfig.node.json
  • vite.config.ts
  • vitest.config.ts

Comment thread .github/workflows/ci.yml
Comment on lines +15 to +16
- name: Checkout code
uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Checkout persists credentials by default.

actions/checkout@v4 persists the GITHUB_TOKEN in the local git config by default, which can be exfiltrated by any step running after checkout (including third-party actions or scripts). Since this job doesn't need to push commits, disable persistence.

🔒 Suggested fix
       - name: Checkout code
         uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 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
- name: Checkout code
uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 15-16: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 15 - 16, Update the
actions/checkout@v4 step in the CI workflow to disable persisted credentials by
setting persist-credentials to false; leave the checkout behavior otherwise
unchanged.

Source: Linters/SAST tools

Comment thread .gitignore
Comment on lines +15 to +18
# Environment variables — NEVER commit real .env files
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/

# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/

# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Temporary file for partial code execution
tempCodeRunnerFile.py

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/

# Streamlit
.streamlit/secrets.toml
.env.local
.env.*.local

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Ignore all environment files except the example template.

The current patterns leave files such as .env.production and .env.staging unignored, contradicting the safety comment and allowing credentials or private configuration to be committed.

Proposed fix
 # Environment variables — NEVER commit real .env files
-.env
-.env.local
-.env.*.local
+.env*
+!.env.example
📝 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
# Environment variables — NEVER commit real .env files
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Temporary file for partial code execution
tempCodeRunnerFile.py
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml
.env.local
.env.*.local
# Environment variables — NEVER commit real .env files
.env*
!.env.example
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore around lines 15 - 18, Update the environment-file ignore patterns
in .gitignore to ignore all .env variants, including files such as
.env.production and .env.staging, while explicitly keeping the example template
unignored.

Comment thread package.json
Comment on lines +7 to +9
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
npm view vite@8.1.1 engines

Repository: TradeDisciplineAI/ai-trading-discipline-copilot-frontned

Length of output: 2064


🌐 Web query:

Vite 8.1.1 engines node 20.19 22.12 npmjs

💡 Result:

As of July 13, 2026, Vite 8.1.1 requires Node.js version 20.19.0 or higher, or 22.12.0 or higher [1][2]. These specific version requirements were introduced to ensure that Node.js supports require(esm) without requiring additional flags [3]. You can verify this in the package.json file of the Vite 8.1.1 release [1][4].

Citations:


Raise the Node floor for Vite 8. engines.node should be >=20.19.0 (or >=22.12.0) so older Node 20 releases don’t satisfy the manifest and then fail when Vite 8.1.1 runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 7 - 9, Update the package.json engines.node
constraint to require Node >=20.19.0 (or the supported >=22.12.0 alternative),
while preserving the existing npm engine requirement, so older Node 20 releases
no longer satisfy the manifest.

Comment on lines +1 to +20
import React, { useRef } from 'react';
import { useGlobe } from './useGlobe';
import { NATIONS } from './nations.constants';
import '@/styles/components/overlay.css';
import '@/styles/components/country-list.css';

interface GlobeViewProps {
onEnterApp: () => void;
}

/**
* GlobeView — The Home/Landing page component.
*
* This is intentionally kept as a thin layout shell.
* All globe.gl logic lives in the useGlobe() custom hook.
*/
export const GlobeView: React.FC<GlobeViewProps> = ({ onEnterApp }) => {
const containerRef = useRef<HTMLDivElement | null>(null);
useGlobe(containerRef);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Country-list/overlay feature is built but never rendered — useGlobe's return value is discarded.

useGlobe returns activeCountry, flyTo, and resumeRotation (see useGlobe.ts lines 19-43, 194), and dedicated stylesheets (overlay.css, country-list.css) implement .home-overlay, .terminal-enter-btn, .resume-rotate-btn, .country-list/.country-item — but none of this is used here. useGlobe(containerRef); throws away the return value, NATIONS is imported but unused, and no JSX renders the overlay or country list. As shipped, users can never trigger flyTo/resumeRotation, and the imported CSS for those classes has no matching markup.

🐛 Sketch of the missing wiring
-export const GlobeView: React.FC<GlobeViewProps> = ({ onEnterApp }) => {
-  const containerRef = useRef<HTMLDivElement | null>(null);
-  useGlobe(containerRef);
+export const GlobeView: React.FC<GlobeViewProps> = ({ onEnterApp }) => {
+  const containerRef = useRef<HTMLDivElement | null>(null);
+  const { activeCountry, flyTo, resumeRotation } = useGlobe(containerRef);

Then render a .home-overlay block with a .country-list of buttons (one per NATIONS entry) calling flyTo(n.name, n.lat, n.lng), plus a .resume-rotate-btn calling resumeRotation() when activeCountry is set — or remove the unused hook state/CSS imports if this feature is intentionally deferred.

🧰 Tools
🪛 GitHub Actions: CI / 0_Type-check · Lint · Format · Test · Build.txt

[warning] 1-1: Prettier --check reported a formatting/style issue in this file.

🪛 GitHub Actions: CI / Type-check · Lint · Format · Test · Build

[warning] 1-1: Prettier --check reported formatting issues in this file.

🪛 GitHub Check: Type-check · Lint · Format · Test · Build

[warning] 3-3: eslint(no-unused-vars)
Identifier 'NATIONS' is imported but never used.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/globe/GlobeView.tsx` around lines 1 - 20, Update GlobeView to
retain the object returned by useGlobe(containerRef) and render the missing home
overlay: create country-list buttons for each NATIONS entry that call flyTo with
its name, latitude, and longitude, and render the resume-rotate button calling
resumeRotation when activeCountry is set. Use the existing overlay class names
and preserve the globe container rendering.

Comment on lines +45 to +192
useEffect(() => {
const container = containerRef.current;
if (!container) return;

// ── 1. Initialise globe — globe.gl uses a class constructor ────────────
const world = new Globe(container)
.globeImageUrl('https://unpkg.com/three-globe/example/img/earth-night.jpg')
.bumpImageUrl('https://unpkg.com/three-globe/example/img/earth-topology.png')
.backgroundImageUrl('https://unpkg.com/three-globe/example/img/night-sky.png')
.ringsData(
NATIONS.map((n) => ({
lat: n.lat,
lng: n.lng,
})),
)
.ringColor(() => '#3b82f6') // Blue
.ringMaxRadius(3) // Size of the blink
.ringPropagationSpeed(2) // Speed of the pulse
.ringRepeatPeriod(1000); // Blinks every 1 second

world.controls().autoRotate = true;
world.controls().autoRotateSpeed = GLOBE_CONFIG.AUTO_ROTATE_SPEED;
world.controls().enableZoom = false; // Disabled to prevent scroll trapping
globeInstanceRef.current = world;

// Initialize globe as small (altitude 4.0)
setTimeout(() => {
if (world) {
const pov = world.pointOfView();
world.pointOfView({ ...pov, altitude: 4.0 });
}
}, 50);

// ── 2. Increase Globe Brightness ──────────────────────────────────────
setTimeout(() => {
if (!world) return;
const scene = world.scene();
scene.children.forEach((child: any) => {
if (child.type === 'AmbientLight') {
child.intensity = 4.5; // Significantly brighter ambient light
}
if (child.type === 'DirectionalLight') {
child.intensity = 3.0; // Significantly brighter directional light
}
});
}, 100);

// No external animation loop is needed since blinking is handled via CSS.

// ── 3. Resize handler ─────────────────────────────────────────────────
const handleResize = () => {
world.width(container.clientWidth).height(container.clientHeight);
};
window.addEventListener('resize', handleResize);
handleResize();

// ── 4. Smart Scroll: Zoom + Rotate + Page Scroll Handoff ──────────────
let scrollAngle = 0;
const handleWheel = (e: WheelEvent) => {
if (!world) return;

const currentPov = world.pointOfView();
let newAltitude = currentPov.altitude;

// Calculate new altitude (zoom)
// Reverse logic: Scroll DOWN (deltaY > 0) -> Zoom IN (decrease altitude)
// Scroll UP (deltaY < 0) -> Zoom OUT (increase altitude)
const zoomSpeed = 0.002;
newAltitude -= e.deltaY * zoomSpeed;

// Bounds: 1.5 = Large globe (Image 1), 4.0 = Small globe (Image 2)
if (newAltitude < 1.5) newAltitude = 1.5;
if (newAltitude > 4.0) newAltitude = 4.0;

// If scrolling UP and the globe is already at its smallest (4.0),
// release the scroll event so the user scrolls back to the top of the page!
if (e.deltaY < 0 && currentPov.altitude >= 3.99) {
return;
}

// Otherwise, prevent page scroll and apply zoom & rotation to the globe
e.preventDefault();

// Rotation
scrollAngle += e.deltaY * GLOBE_CONFIG.SCROLL_SENSITIVITY;

world.pointOfView({ lat: currentPov.lat, lng: scrollAngle, altitude: newAltitude });
};

// Attach to container, passive: false so we can preventDefault
container.addEventListener('wheel', handleWheel, { passive: false });

// ── 5. Country boundary polygons (GeoJSON) ────────────────────────────
let isMounted = true;
// TODO (Phase 3 task): Download and serve from /public/ne_110m_countries.geojson locally
fetch('https://unpkg.com/three-globe/example/img/ne_110m_admin_0_countries.geojson')
.then((res) => res.json())
.then((data: GeoJSON) => {
if (!isMounted) return;
world
.polygonsData(data.features)
.polygonCapColor(() => 'rgba(255, 255, 255, 0.15)') // Brighter nation fill
.polygonSideColor(() => 'rgba(255, 255, 255, 0.05)')
.polygonStrokeColor(() => 'rgba(255, 255, 255, 0.8)') // Brighter nation borders
.polygonLabel((d: object) => {
const feature = d as GeoFeature;
return `
<div style="
background: rgba(10, 10, 10, 0.95);
border: 1px solid #ffffff;
padding: 6px 12px;
border-radius: 6px;
color: #fff;
font-family: 'Segoe UI', sans-serif;
font-size: 13px;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.25);
pointer-events: none;
">
<strong>${feature.properties.ADMIN}</strong>
</div>
`;
})
.onPolygonHover((hoverD: object | null) => {
world
.polygonCapColor((d: object) =>
d === hoverD ? 'rgba(255, 255, 255, 0.35)' : 'rgba(255, 255, 255, 0.15)',
)
.polygonSideColor((d: object) =>
d === hoverD ? 'rgba(255, 255, 255, 0.15)' : 'rgba(255, 255, 255, 0.05)',
);
});
})
.catch((err: unknown) => {
console.error('[useGlobe] Failed to load country boundaries GeoJSON:', err);
});

// ── Cleanup ───────────────────────────────────────────────────────────
return () => {
isMounted = false;
window.removeEventListener('resize', handleResize);
container.removeEventListener('wheel', handleWheel);
if (globeInstanceRef.current) {
globeInstanceRef.current._destructor();
}
container.innerHTML = '';
globeInstanceRef.current = null;
};
}, [containerRef]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Clear the pending setTimeouts in cleanup.

The two setTimeout calls (init altitude, brightness boost) aren't tracked/cleared. If the component unmounts before they fire (e.g. fast route change, or React StrictMode's mount→unmount→remount cycle), the callbacks run against a torn-down globe (container.innerHTML already cleared, ref nulled), risking a runtime exception.

🐛 Proposed fix
+    let initTimeoutId: ReturnType<typeof setTimeout>;
+    let brightnessTimeoutId: ReturnType<typeof setTimeout>;
-    setTimeout(() => {
+    initTimeoutId = setTimeout(() => {
       if (world) {
         const pov = world.pointOfView();
         world.pointOfView({ ...pov, altitude: 4.0 });
       }
     }, 50);

     // ── 2. Increase Globe Brightness ──────────────────────────────────────
-    setTimeout(() => {
+    brightnessTimeoutId = setTimeout(() => {
       if (!world) return;
       const scene = world.scene();
       scene.children.forEach((child: any) => {
         ...
       });
     }, 100);
...
     return () => {
       isMounted = false;
+      clearTimeout(initTimeoutId);
+      clearTimeout(brightnessTimeoutId);
       window.removeEventListener('resize', handleResize);
📝 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
useEffect(() => {
const container = containerRef.current;
if (!container) return;
// ── 1. Initialise globe — globe.gl uses a class constructor ────────────
const world = new Globe(container)
.globeImageUrl('https://unpkg.com/three-globe/example/img/earth-night.jpg')
.bumpImageUrl('https://unpkg.com/three-globe/example/img/earth-topology.png')
.backgroundImageUrl('https://unpkg.com/three-globe/example/img/night-sky.png')
.ringsData(
NATIONS.map((n) => ({
lat: n.lat,
lng: n.lng,
})),
)
.ringColor(() => '#3b82f6') // Blue
.ringMaxRadius(3) // Size of the blink
.ringPropagationSpeed(2) // Speed of the pulse
.ringRepeatPeriod(1000); // Blinks every 1 second
world.controls().autoRotate = true;
world.controls().autoRotateSpeed = GLOBE_CONFIG.AUTO_ROTATE_SPEED;
world.controls().enableZoom = false; // Disabled to prevent scroll trapping
globeInstanceRef.current = world;
// Initialize globe as small (altitude 4.0)
setTimeout(() => {
if (world) {
const pov = world.pointOfView();
world.pointOfView({ ...pov, altitude: 4.0 });
}
}, 50);
// ── 2. Increase Globe Brightness ──────────────────────────────────────
setTimeout(() => {
if (!world) return;
const scene = world.scene();
scene.children.forEach((child: any) => {
if (child.type === 'AmbientLight') {
child.intensity = 4.5; // Significantly brighter ambient light
}
if (child.type === 'DirectionalLight') {
child.intensity = 3.0; // Significantly brighter directional light
}
});
}, 100);
// No external animation loop is needed since blinking is handled via CSS.
// ── 3. Resize handler ─────────────────────────────────────────────────
const handleResize = () => {
world.width(container.clientWidth).height(container.clientHeight);
};
window.addEventListener('resize', handleResize);
handleResize();
// ── 4. Smart Scroll: Zoom + Rotate + Page Scroll Handoff ──────────────
let scrollAngle = 0;
const handleWheel = (e: WheelEvent) => {
if (!world) return;
const currentPov = world.pointOfView();
let newAltitude = currentPov.altitude;
// Calculate new altitude (zoom)
// Reverse logic: Scroll DOWN (deltaY > 0) -> Zoom IN (decrease altitude)
// Scroll UP (deltaY < 0) -> Zoom OUT (increase altitude)
const zoomSpeed = 0.002;
newAltitude -= e.deltaY * zoomSpeed;
// Bounds: 1.5 = Large globe (Image 1), 4.0 = Small globe (Image 2)
if (newAltitude < 1.5) newAltitude = 1.5;
if (newAltitude > 4.0) newAltitude = 4.0;
// If scrolling UP and the globe is already at its smallest (4.0),
// release the scroll event so the user scrolls back to the top of the page!
if (e.deltaY < 0 && currentPov.altitude >= 3.99) {
return;
}
// Otherwise, prevent page scroll and apply zoom & rotation to the globe
e.preventDefault();
// Rotation
scrollAngle += e.deltaY * GLOBE_CONFIG.SCROLL_SENSITIVITY;
world.pointOfView({ lat: currentPov.lat, lng: scrollAngle, altitude: newAltitude });
};
// Attach to container, passive: false so we can preventDefault
container.addEventListener('wheel', handleWheel, { passive: false });
// ── 5. Country boundary polygons (GeoJSON) ────────────────────────────
let isMounted = true;
// TODO (Phase 3 task): Download and serve from /public/ne_110m_countries.geojson locally
fetch('https://unpkg.com/three-globe/example/img/ne_110m_admin_0_countries.geojson')
.then((res) => res.json())
.then((data: GeoJSON) => {
if (!isMounted) return;
world
.polygonsData(data.features)
.polygonCapColor(() => 'rgba(255, 255, 255, 0.15)') // Brighter nation fill
.polygonSideColor(() => 'rgba(255, 255, 255, 0.05)')
.polygonStrokeColor(() => 'rgba(255, 255, 255, 0.8)') // Brighter nation borders
.polygonLabel((d: object) => {
const feature = d as GeoFeature;
return `
<div style="
background: rgba(10, 10, 10, 0.95);
border: 1px solid #ffffff;
padding: 6px 12px;
border-radius: 6px;
color: #fff;
font-family: 'Segoe UI', sans-serif;
font-size: 13px;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.25);
pointer-events: none;
">
<strong>${feature.properties.ADMIN}</strong>
</div>
`;
})
.onPolygonHover((hoverD: object | null) => {
world
.polygonCapColor((d: object) =>
d === hoverD ? 'rgba(255, 255, 255, 0.35)' : 'rgba(255, 255, 255, 0.15)',
)
.polygonSideColor((d: object) =>
d === hoverD ? 'rgba(255, 255, 255, 0.15)' : 'rgba(255, 255, 255, 0.05)',
);
});
})
.catch((err: unknown) => {
console.error('[useGlobe] Failed to load country boundaries GeoJSON:', err);
});
// ── Cleanup ───────────────────────────────────────────────────────────
return () => {
isMounted = false;
window.removeEventListener('resize', handleResize);
container.removeEventListener('wheel', handleWheel);
if (globeInstanceRef.current) {
globeInstanceRef.current._destructor();
}
container.innerHTML = '';
globeInstanceRef.current = null;
};
}, [containerRef]);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
// ── 1. Initialise globe — globe.gl uses a class constructor ────────────
const world = new Globe(container)
.globeImageUrl('https://unpkg.com/three-globe/example/img/earth-night.jpg')
.bumpImageUrl('https://unpkg.com/three-globe/example/img/earth-topology.png')
.backgroundImageUrl('https://unpkg.com/three-globe/example/img/night-sky.png')
.ringsData(
NATIONS.map((n) => ({
lat: n.lat,
lng: n.lng,
})),
)
.ringColor(() => '`#3b82f6`') // Blue
.ringMaxRadius(3) // Size of the blink
.ringPropagationSpeed(2) // Speed of the pulse
.ringRepeatPeriod(1000); // Blinks every 1 second
world.controls().autoRotate = true;
world.controls().autoRotateSpeed = GLOBE_CONFIG.AUTO_ROTATE_SPEED;
world.controls().enableZoom = false; // Disabled to prevent scroll trapping
globeInstanceRef.current = world;
let initTimeoutId: ReturnType<typeof setTimeout>;
let brightnessTimeoutId: ReturnType<typeof setTimeout>;
// Initialize globe as small (altitude 4.0)
initTimeoutId = setTimeout(() => {
if (world) {
const pov = world.pointOfView();
world.pointOfView({ ...pov, altitude: 4.0 });
}
}, 50);
// ── 2. Increase Globe Brightness ──────────────────────────────────────
brightnessTimeoutId = setTimeout(() => {
if (!world) return;
const scene = world.scene();
scene.children.forEach((child: any) => {
if (child.type === 'AmbientLight') {
child.intensity = 4.5; // Significantly brighter ambient light
}
if (child.type === 'DirectionalLight') {
child.intensity = 3.0; // Significantly brighter directional light
}
});
}, 100);
// No external animation loop is needed since blinking is handled via CSS.
// ── 3. Resize handler ─────────────────────────────────────────────────
const handleResize = () => {
world.width(container.clientWidth).height(container.clientHeight);
};
window.addEventListener('resize', handleResize);
handleResize();
// ── 4. Smart Scroll: Zoom + Rotate + Page Scroll Handoff ──────────────
let scrollAngle = 0;
const handleWheel = (e: WheelEvent) => {
if (!world) return;
const currentPov = world.pointOfView();
let newAltitude = currentPov.altitude;
// Calculate new altitude (zoom)
// Reverse logic: Scroll DOWN (deltaY > 0) -> Zoom IN (decrease altitude)
// Scroll UP (deltaY < 0) -> Zoom OUT (increase altitude)
const zoomSpeed = 0.002;
newAltitude -= e.deltaY * zoomSpeed;
// Bounds: 1.5 = Large globe (Image 1), 4.0 = Small globe (Image 2)
if (newAltitude < 1.5) newAltitude = 1.5;
if (newAltitude > 4.0) newAltitude = 4.0;
// If scrolling UP and the globe is already at its smallest (4.0),
// release the scroll event so the user scrolls back to the top of the page!
if (e.deltaY < 0 && currentPov.altitude >= 3.99) {
return;
}
// Otherwise, prevent page scroll and apply zoom & rotation to the globe
e.preventDefault();
// Rotation
scrollAngle += e.deltaY * GLOBE_CONFIG.SCROLL_SENSITIVITY;
world.pointOfView({ lat: currentPov.lat, lng: scrollAngle, altitude: newAltitude });
};
// Attach to container, passive: false so we can preventDefault
container.addEventListener('wheel', handleWheel, { passive: false });
// ── 5. Country boundary polygons (GeoJSON) ────────────────────────────
let isMounted = true;
// TODO (Phase 3 task): Download and serve from /public/ne_110m_countries.geojson locally
fetch('https://unpkg.com/three-globe/example/img/ne_110m_admin_0_countries.geojson')
.then((res) => res.json())
.then((data: GeoJSON) => {
if (!isMounted) return;
world
.polygonsData(data.features)
.polygonCapColor(() => 'rgba(255, 255, 255, 0.15)') // Brighter nation fill
.polygonSideColor(() => 'rgba(255, 255, 255, 0.05)')
.polygonStrokeColor(() => 'rgba(255, 255, 255, 0.8)') // Brighter nation borders
.polygonLabel((d: object) => {
const feature = d as GeoFeature;
return `
<div style="
background: rgba(10, 10, 10, 0.95);
border: 1px solid `#ffffff`;
padding: 6px 12px;
border-radius: 6px;
color: `#fff`;
font-family: 'Segoe UI', sans-serif;
font-size: 13px;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.25);
pointer-events: none;
">
<strong>${feature.properties.ADMIN}</strong>
</div>
`;
})
.onPolygonHover((hoverD: object | null) => {
world
.polygonCapColor((d: object) =>
d === hoverD ? 'rgba(255, 255, 255, 0.35)' : 'rgba(255, 255, 255, 0.15)',
)
.polygonSideColor((d: object) =>
d === hoverD ? 'rgba(255, 255, 255, 0.15)' : 'rgba(255, 255, 255, 0.05)',
);
});
})
.catch((err: unknown) => {
console.error('[useGlobe] Failed to load country boundaries GeoJSON:', err);
});
// ── Cleanup ───────────────────────────────────────────────────────────
return () => {
isMounted = false;
clearTimeout(initTimeoutId);
clearTimeout(brightnessTimeoutId);
window.removeEventListener('resize', handleResize);
container.removeEventListener('wheel', handleWheel);
if (globeInstanceRef.current) {
globeInstanceRef.current._destructor();
}
container.innerHTML = '';
globeInstanceRef.current = null;
};
}, [containerRef]);
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 188-188: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: container.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation

(dom-content-modification)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/globe/useGlobe.ts` around lines 45 - 192, Track both timeout
handles created for the initial altitude and brightness updates in the useEffect
setup, then clear them during the effect cleanup before tearing down the globe.
Ensure cleanup prevents either delayed callback from accessing the destroyed
world instance during unmount or remount.

Comment thread src/stores/userStore.ts
Comment on lines +49 to +61
initAuth: async () => {
try {
// If we have a user in localStorage, we can optimistically say they might be authenticated.
// But to be sure, and to get the access token in memory, we fetch their profile.
// Since there is no access token in memory yet, the Axios interceptor will hit a 401
// when it tries to get /auth/me, which will trigger the /auth/refresh flow automatically!
const user = await authService.getMe();
set({ user, isAuthenticated: true });
} catch (error) {
// If refresh fails, clear the state
set({ user: null, isAuthenticated: false, accessToken: null });
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Swallowed error hides refresh/network failures; fix the unused catch param.

The catch (error) in initAuth never uses error, flagged by lint. Beyond the lint failure, silently discarding the error makes it impossible to distinguish "user simply isn't logged in" from a genuine network/backend failure during the refresh flow.

🪵 Proposed fix
-      } catch (error) {
-        // If refresh fails, clear the state
+      } catch (error) {
+        // If refresh fails (no valid session, or a network/server error), clear the state
+        console.error('[userStore] initAuth failed:', error);
         set({ user: null, isAuthenticated: false, accessToken: null });
       }
📝 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
initAuth: async () => {
try {
// If we have a user in localStorage, we can optimistically say they might be authenticated.
// But to be sure, and to get the access token in memory, we fetch their profile.
// Since there is no access token in memory yet, the Axios interceptor will hit a 401
// when it tries to get /auth/me, which will trigger the /auth/refresh flow automatically!
const user = await authService.getMe();
set({ user, isAuthenticated: true });
} catch (error) {
// If refresh fails, clear the state
set({ user: null, isAuthenticated: false, accessToken: null });
}
},
initAuth: async () => {
try {
// If we have a user in localStorage, we can optimistically say they might be authenticated.
// But to be sure, and to get the access token in memory, we fetch their profile.
// Since there is no access token in memory yet, the Axios interceptor will hit a 401
// when it tries to get /auth/me, which will trigger the /auth/refresh flow automatically!
const user = await authService.getMe();
set({ user, isAuthenticated: true });
} catch (error) {
// If refresh fails (no valid session, or a network/server error), clear the state
console.error('[userStore] initAuth failed:', error);
set({ user: null, isAuthenticated: false, accessToken: null });
}
},
🧰 Tools
🪛 GitHub Check: Type-check · Lint · Format · Test · Build

[warning] 57-57: eslint(no-unused-vars)
Catch parameter 'error' is caught but never used.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/userStore.ts` around lines 49 - 61, Update initAuth to handle the
caught error instead of silently discarding it: remove the unused catch
parameter if no error reporting is required, or log/propagate the error using
the project’s established mechanism while preserving the existing
unauthenticated state reset. Ensure genuine refresh or network failures remain
distinguishable from a normal unauthenticated result.

Source: Linters/SAST tools

width: 8px;
height: 8px;
border-radius: 50%;
box-shadow: 0 0 6px currentColor;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

currentColor casing flagged by stylelint.

Same value-keyword-case issue as in index.css.

🎨 Fix
-  box-shadow: 0 0 6px currentColor;
+  box-shadow: 0 0 6px currentcolor;
📝 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
box-shadow: 0 0 6px currentColor;
box-shadow: 0 0 6px currentcolor;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 89-89: Expected "currentColor" to be "currentcolor" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/styles/components/country-list.css` at line 89, Update the box-shadow
declaration in the country-list styles to use the casing required by the
stylelint value-keyword-case rule for the current color keyword, matching the
equivalent correction in index.css.

Source: Linters/SAST tools

Comment thread src/styles/reset.css
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint will fail on text-rendering value casing.

Static analysis flags optimizeLegibility for value-keyword-case. Functionally the value is case-insensitive in browsers, so either lowercase it to satisfy the rule, or add a scoped disable comment to preserve the conventional casing.

🔧 Proposed fix
-  text-rendering: optimizeLegibility;
+  /* stylelint-disable-next-line value-keyword-case */
+  text-rendering: optimizeLegibility;
📝 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
text-rendering: optimizeLegibility;
/* stylelint-disable-next-line value-keyword-case */
text-rendering: optimizeLegibility;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 22-22: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/styles/reset.css` at line 22, Update the text-rendering declaration in
the reset stylesheet so its optimizeLegibility value satisfies Stylelint’s
value-keyword-case rule, preferably by using lowercase casing; preserve the
existing rendering behavior.

Source: Linters/SAST tools

Comment thread src/styles/tokens.css
Comment on lines +33 to +35
--font-family-base:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
--font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stylelint value-keyword-case failures on font-family keywords.

Static analysis flags BlinkMacSystemFont, Roboto, Helvetica, Arial, and Consolas for case mismatches. Either quote these font names (exempting them from the keyword-case rule, as the already-quoted names in --font-family-mono are) or add a stylelint override for the font-family/font properties.

🎨 Proposed fix
   --font-family-base:
-    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
-  --font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
+    -apple-system, 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
+  --font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
📝 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
--font-family-base:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
--font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
--font-family-base:
-apple-system, 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
--font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 34-34: Expected "BlinkMacSystemFont" to be "blinkmacsystemfont" (value-keyword-case)

(value-keyword-case)


[error] 34-34: Expected "Roboto" to be "roboto" (value-keyword-case)

(value-keyword-case)


[error] 34-34: Expected "Helvetica" to be "helvetica" (value-keyword-case)

(value-keyword-case)


[error] 34-34: Expected "Arial" to be "arial" (value-keyword-case)

(value-keyword-case)


[error] 35-35: Expected "Consolas" to be "consolas" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/styles/tokens.css` around lines 33 - 35, Update the font-family
declarations in --font-family-base to satisfy Stylelint value-keyword-case by
quoting the flagged font names, matching the existing quoted names in
--font-family-mono; preserve the current fallback order and values.

Source: Linters/SAST tools

Comment thread src/test/setup.ts
Comment on lines +1 to +5
/**
* Vitest global test setup.
* Runs before every test file.
*/
import '@testing-library/jest-dom';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n## Files\n'
git ls-files src/test/setup.ts vitest.config.ts package.json package-lock.json pnpm-lock.yaml yarn.lock

printf '\n## setup.ts\n'
if [ -f src/test/setup.ts ]; then cat -n src/test/setup.ts; fi

printf '\n## vitest.config.ts\n'
if [ -f vitest.config.ts ]; then cat -n vitest.config.ts; fi

printf '\n## jest-dom usages\n'
rg -n "`@testing-library/jest-dom`" -S .

printf '\n## package entry\n'
if [ -f package.json ]; then jq -r '.devDependencies["`@testing-library/jest-dom`"] // .dependencies["`@testing-library/jest-dom`"] // empty' package.json; fi

Repository: TradeDisciplineAI/ai-trading-discipline-copilot-frontned

Length of output: 2035


🏁 Script executed:

set -euo pipefail
git ls-files src/test/setup.ts vitest.config.ts package.json package-lock.json pnpm-lock.yaml yarn.lock

Repository: TradeDisciplineAI/ai-trading-discipline-copilot-frontned

Length of output: 2035


🏁 Script executed:

set -euo pipefail
printf 'Need repo inspection for src/test/setup.ts and vitest.config.ts\n'

Repository: TradeDisciplineAI/ai-trading-discipline-copilot-frontned

Length of output: 2035


🌐 Web query:

@testing-library/jest-dom Vitest setup documentation generic entrypoint vitest subpath

💡 Result:

To use @testing-library/jest-dom with Vitest, you must import the Vitest-specific entrypoint in your test setup file [1][2]. 1. Configure Setup File Create a setup file (e.g., vitest.setup.ts) and add the following import [1][2]: import '@testing-library/jest-dom/vitest' 2. Update Vitest Configuration Add this file to the setupFiles property in your Vitest configuration file (e.g., vitest.config.ts or vite.config.ts) [1][2]: export default defineConfig({ test: { setupFiles: ['./vitest.setup.ts'], }, }) 3. TypeScript Configuration If you are using TypeScript, ensure your setup file is included in your tsconfig.json so that the IDE recognizes the custom matchers (like toBeInTheDocument) [1][3][4]: { "compilerOptions": { "types": ["@testing-library/jest-dom"] }, "include": [ "./vitest.setup.ts" ] } Note: Do not import @testing-library/jest-dom directly; always use the /vitest subpath to ensure compatibility with Vitest's expect matchers [2]. If you encounter type issues, verifying that the setup file is correctly included in your tsconfig include array is often the required fix [1][3][4].

Citations:


Use the Vitest-specific jest-dom entrypoint. src/test/setup.ts is loaded by Vitest, so this should import @testing-library/jest-dom/vitest; the generic entrypoint may not register toBeInTheDocument() against Vitest’s expect.

Proposed fix
-import '`@testing-library/jest-dom`';
+import '`@testing-library/jest-dom/vitest`';
📝 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
/**
* Vitest global test setup.
* Runs before every test file.
*/
import '@testing-library/jest-dom';
/**
* Vitest global test setup.
* Runs before every test file.
*/
import '`@testing-library/jest-dom/vitest`';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/setup.ts` around lines 1 - 5, Update the Vitest global setup import
in src/test/setup.ts to use the Vitest-specific `@testing-library/jest-dom/vitest`
entrypoint instead of the generic jest-dom entrypoint, preserving the existing
setup behavior.

Source: MCP tools

@sreenandpk sreenandpk closed this Jul 16, 2026
@sreenandpk
sreenandpk deleted the feat/FRO-3-glob-change branch July 16, 2026 13:21
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.

3 participants