Feat/fro 3 glob change#4
Conversation
feat:started authentication
…rs, and hash params
chore:configured frontend to backend connection
📝 WalkthroughWalkthroughThe 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. ChangesFrontend foundation
Authentication and application flows
Globe landing experience
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 winPrettier formatting failure.
CI reports a Prettier
--checkfailure 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 winPrettier formatting failure.
CI reports a Prettier
--checkfailure 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 valueSearch input has no accessible label.
The Ctrl+K search
<input>relies solely onplaceholdertext, 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 winDoc 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 unusedPathData/LabelData/Coordinatetype imports (flagged by static analysis) and the unusedPATH_*/LABEL_*/ANIMATION_SPEEDfields inglobe.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/labelsDatausing the existingPATH_*/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 winTextures/backgrounds loaded from unpkg CDN with no error handling.
globeImageUrl/bumpImageUrl/backgroundImageUrlpoint atunpkg.com(same CDN the GeoJSON TODO already flags for local hosting) but have noonError/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 winDuplicate 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 insrc/styles/components/overlay.cssandsrc/styles/components/country-list.css. Once the overlay/country-list markup is wired up inGlobeView.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 valuePrefer
vi.mocked()overas anycasts.
(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 winDuplicate
API_URLfallback logic.Same
import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'fallback is defined insrc/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 winRepeated 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 existingauth-input-field/auth-submit-btn/back-linkclasses 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 winType
register's payload instead ofany.Every other method in this file uses a precise request/response type;
data: anydrops 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 | 🔵 TrivialTODO: wire up real error tracking (e.g. Sentry) before production.
Currently only
console.errorlogs; 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 valueHardcoded slice length depends on the route path staying exactly
/reset-password/.
pathname.substring(16)relies on the literal string length; ifROUTES.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 winProp name
onNavigateToDashboarddoesn'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 toonGoToSignIn(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 winExtract 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 importauth-layout.cssand 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 winMissing Space-key activation for the
role="button"element.Only
Enteris handled inonKeyDown; native/ARIA button semantics also expectSpaceto activate. Consider using a real<button>element (styled via CSS) instead of adivwith 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
⛔ Files ignored due to path filters (4)
package-lock.jsonis excluded by!**/package-lock.jsonpublic/favicon.svgis excluded by!**/*.svgpublic/icons.svgis excluded by!**/*.svgpublic/trading_banner.pngis 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.jsonREADME.mdindex.htmlpackage.jsonsrc/App.tsxsrc/components/AuthCallback.tsxsrc/components/ForgotPassword.tsxsrc/components/Login.tsxsrc/components/Register.tsxsrc/components/ResetPassword.tsxsrc/components/SuccessPage.tsxsrc/components/VerifyEmail.tsxsrc/components/ui/AuthLayout/AuthLayout.tsxsrc/components/ui/ErrorBoundary/ErrorBoundary.tsxsrc/components/ui/ErrorBoundary/index.tssrc/constants/routes.constants.tssrc/features/auth/AuthGuard.tsxsrc/features/auth/auth.service.tssrc/features/auth/auth.types.tssrc/features/dashboard/DashboardPage.tsxsrc/features/globe/GlobeView.tsxsrc/features/globe/globe.constants.tssrc/features/globe/nations.constants.tssrc/features/globe/useGlobe.tssrc/index.csssrc/lib/api.client.tssrc/main.tsxsrc/stores/userStore.tssrc/styles/components/auth-layout.csssrc/styles/components/country-list.csssrc/styles/components/dashboard.csssrc/styles/components/overlay.csssrc/styles/reset.csssrc/styles/tokens.csssrc/test/Login.test.tsxsrc/test/setup.tssrc/types.d.tssrc/types/env.d.tssrc/types/globe.types.tstsconfig.app.jsontsconfig.jsontsconfig.node.jsonvite.config.tsvitest.config.ts
| - name: Checkout code | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 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.
| - 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
| # 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 |
There was a problem hiding this comment.
🔒 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.
| # 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.
| "engines": { | ||
| "node": ">=20.0.0", | ||
| "npm": ">=10.0.0" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
npm view vite@8.1.1 enginesRepository: 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:
- 1: https://npmx.dev/package/vite/v/8.1.1
- 2: https://www.npmjs.com/package/vite
- 3: https://vite.dev/blog/announcing-vite8
- 4: vitejs/vite@2b80243
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.
| 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); | ||
|
|
There was a problem hiding this comment.
🎯 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.
| 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]); |
There was a problem hiding this comment.
🩺 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.
| 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.
| 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 }); | ||
| } | ||
| }, |
There was a problem hiding this comment.
📐 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.
| 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; |
There was a problem hiding this comment.
📐 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.
| 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
| line-height: 1.5; | ||
| -webkit-font-smoothing: antialiased; | ||
| -moz-osx-font-smoothing: grayscale; | ||
| text-rendering: optimizeLegibility; |
There was a problem hiding this comment.
📐 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.
| 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
| --font-family-base: | ||
| -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | ||
| --font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace; |
There was a problem hiding this comment.
📐 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.
| --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
| /** | ||
| * Vitest global test setup. | ||
| * Runs before every test file. | ||
| */ | ||
| import '@testing-library/jest-dom'; |
There was a problem hiding this comment.
🎯 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; fiRepository: 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.lockRepository: 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:
- 1: https://github.com/testing-library/jest-dom/
- 2: https://toolchew.com/en/how-to-migrate-jest-to-vitest/
- 3: Readme - how to update tsconfig for vitest matchers extension testing-library/jest-dom#624
- 4: Does not work with vitest 1.x.x testing-library/jest-dom#567
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.
| /** | |
| * 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
Summary by CodeRabbit
New Features
Documentation
Chores