Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Sep 30, 2025

PR Type

Enhancement


Description

  • Add spinning loader animation to plant icon during AI generation

  • Create global loading state management with React context

  • Integrate loading state with AI streaming responses

  • Apply counter-clockwise spinning animation with CSS keyframes


Diagram Walkthrough

flowchart LR
  A["Chat Component"] --> B["LoadingStateUpdater"]
  B --> C["IsLoadingProvider"]
  C --> D["Header Component"]
  D --> E["Plant Icon Animation"]
  F["AI Streaming"] --> B
Loading

File Walkthrough

Relevant files
Enhancement
globals.css
Add counter-clockwise spinning animation styles                   

app/globals.css

  • Add CSS keyframes for counter-clockwise spinning animation
  • Define animate-spin-counter-clockwise utility class
+13/-0   
layout.tsx
Integrate loading provider in app layout                                 

app/layout.tsx

  • Wrap application with IsLoadingProvider context
  • Import and integrate new loading provider
+23/-20 
chat.tsx
Add loading state tracking for AI responses                           

components/chat.tsx

  • Create LoadingStateUpdater component to monitor AI generation
  • Use useStreamableValue to track streaming state
  • Update global loading state based on message generation
+18/-1   
header.tsx
Add spinning animation to header logo                                       

components/header.tsx

  • Convert to client component with 'use client' directive
  • Apply spinning animation to logo based on loading state
  • Use useIsLoading hook to consume loading context
+14/-1   
is-loading-provider.tsx
Create loading state context provider                                       

components/is-loading-provider.tsx

  • Create React context for global loading state management
  • Implement provider component with state and setter
  • Export custom hook for consuming loading context
+30/-0   

Summary by CodeRabbit

  • New Features
    • App-wide loading indicator: the header logo now spins while the app is processing.
    • Chat activity sets a global loading state for consistent feedback across views.
    • Map updates react immediately when drawn features change for a more responsive experience.
  • Style
    • Added a counter-clockwise spin animation utility for smoother loading visuals.

This commit introduces a spinning loader on the top-left plant icon to indicate when the AI is generating tokens. A new `IsLoadingProvider` is created to manage the loading state, which is updated from the `Chat` component based on the `isGenerating` streamable value. The `Header` component consumes this state to conditionally apply a counter-clockwise spinning animation.
This commit introduces a spinning loader on the top-left plant icon to indicate when the AI is generating tokens. A new `IsLoadingProvider` is created to manage the loading state, which is updated from the `Chat` component based on the `isGenerating` streamable value. The `Header` component consumes this state to conditionally apply a counter-clockwise spinning animation.
@vercel
Copy link

vercel bot commented Sep 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
qcx Ready Ready Preview Comment Sep 30, 2025 5:47pm

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

Walkthrough

Adds a global CSS counter-clockwise spin animation. Introduces an IsLoadingProvider and hook, wraps app layout with it, and uses the loading state to animate the header logo. Chat now updates loading status based on message generation and triggers a server action when map drawing data changes.

Changes

Cohort / File(s) Summary
Styling: Loading Animation
app/globals.css
Adds @keyframes spin-counter-clockwise and .animate-spin-counter-clockwise utility.
App Layout: Provider Composition
app/layout.tsx
Wraps existing providers with IsLoadingProvider; internal structure unchanged.
Loading Context Module
components/is-loading-provider.tsx
New client module exporting IsLoadingProvider and useIsLoading for global loading state.
Header: Loading Indicator
components/header.tsx
Uses useIsLoading; applies animate-spin-counter-clockwise to logo when loading.
Chat: Loading + Map Data Sync
components/chat.tsx
Adds LoadingStateUpdater to sync loading from last message; wires MapDataProvider; on drawnFeatures change calls updateDrawingContext server action; injects updater into mobile/desktop layouts.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Chat as Chat UI
  participant Updater as LoadingStateUpdater
  participant LoadCtx as IsLoadingProvider
  participant Header as Header Logo

  User->>Chat: Send prompt / observe messages
  Chat-->>Updater: Last message isGenerating (stream)
  Updater->>LoadCtx: setIsLoading(true/false)
  LoadCtx-->>Header: isLoading context value
  opt isLoading === true
    Header->>Header: Apply .animate-spin-counter-clockwise
  end
Loading
sequenceDiagram
  autonumber
  participant Chat as Chat UI
  participant MapCtx as useMapData
  participant Server as updateDrawingContext (server action)

  Chat-->>MapCtx: drawnFeatures updated
  MapCtx->>Server: updateDrawingContext(drawnFeatures)
  Server-->>MapCtx: ack/updated context
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I twitch my ears at spinners bright,
A counter-twirl in loading light—
Providers nest, the state aligned,
The header hums, the map’s refined.
Messages brew, then hop to done—
I stamp my paw: ship it, run! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “Feat/add spinning loader” succinctly and accurately describes the primary feature introduced in this changeset, namely the addition of a spinning loader animation and related provider logic, without listing implementation details or extraneous information.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-spinning-loader

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

❤️ Share

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

@qodo-merge-pro
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

The LoadingStateUpdater derives isGenerating from the last message only; if earlier messages can still stream or if messages can be empty/undefined, loading state may be inaccurate. Validate that messages[messages.length - 1]?.isGenerating reliably reflects global generation status.

function LoadingStateUpdater({ messages }: { messages: UIState }) {
  const { setIsLoading } = useIsLoading();
  const lastMessage = messages[messages.length - 1];
  const isGenerating = lastMessage?.isGenerating;
  const [loading] = useStreamableValue(isGenerating);

  useEffect(() => {
    setIsLoading(loading || false);
  }, [loading, setIsLoading]);

  return null;
}
UX Consistency

The counter-clockwise spin uses a 1s linear infinite animation; verify it matches existing design motion specs and reduces prefers-reduced-motion. Consider respecting reduced motion to improve accessibility.

@keyframes spin-counter-clockwise {
  from {
    transform: rotate(360deg);
  }
  to {
    transform: rotate(0deg);
  }
}

.animate-spin-counter-clockwise {
  animation: spin-counter-clockwise 1s linear infinite;
}
Layout Shift

Adding spinning class toggles only on load; ensure the image dimensions are fixed and animation toggle doesn’t cause layout shift or trigger unnecessary re-renders. Validate performance on low-end devices.

<Image
  src="/images/logo.svg"
  alt="Logo"
  width={24}
  height={24}
  className={cn('h-6 w-auto', {
    'animate-spin-counter-clockwise': isLoading
  })}
/>

@qodo-merge-pro
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Use pending state from stream hook

Destructure and use the pending state returned by the useStreamableValue hook to
more accurately track the loading status.

components/chat.tsx [26]

-const [loading] = useStreamableValue(isGenerating);
+const [loading, pending] = useStreamableValue(isGenerating);
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the pending state from useStreamableValue is not being used, which could lead to a more robust loading state implementation.

Medium
Refine loading state logic in effect

Update the useEffect hook to set the loading state based on both the pending and
loading values from the stream for a more accurate status.

components/chat.tsx [28-30]

 useEffect(() => {
-  setIsLoading(loading || false);
-}, [loading, setIsLoading]);
+  setIsLoading(pending || loading || false);
+}, [pending, loading, setIsLoading]);
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly proposes refining the loading logic to include the stream's pending state, which would make the loading indicator more accurate and prevent potential UI flickering.

Medium
High-level
Consolidate global state into fewer contexts

Consolidate multiple React context providers, including the new
IsLoadingProvider, into a single application context. This change aims to
simplify the component tree and centralize state management logic, preventing
"provider hell".

Examples:

app/layout.tsx [58-79]
        <IsLoadingProvider>
          <MapToggleProvider>
            <ProfileToggleProvider>
              <ThemeProvider
                attribute="class"
                defaultTheme="earth"
                enableSystem
                disableTransitionOnChange
                themes={['light', 'dark', 'earth']}
              >

 ... (clipped 12 lines)

Solution Walkthrough:

Before:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <IsLoadingProvider>
          <MapToggleProvider>
            <ProfileToggleProvider>
              <ThemeProvider ...>
                <MapLoadingProvider>
                  <Header />
                  {children}
                  ...
                </MapLoadingProvider>
              </ThemeProvider>
            </ProfileToggleProvider>
          </MapToggleProvider>
        </IsLoadingProvider>
        ...
      </body>
    </html>
  )
}

After:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <AppProvider>
          <ThemeProvider ...>
            <Header />
            {children}
            ...
          </ThemeProvider>
        </AppProvider>
        ...
      </body>
    </html>
  )
}

// components/app-provider.tsx (new file)
export function AppProvider({ children }) {
  const [isLoading, setIsLoading] = useState(false);
  const [isMapLoaded, setIsMapLoaded] = useState(false);
  // ... other states like map toggle, profile toggle
  
  const value = { isLoading, setIsLoading, isMapLoaded, ... };

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}
Suggestion importance[1-10]: 6

__

Why: This is a valid architectural suggestion that addresses the increasing complexity of nested providers ("provider hell"), improving long-term maintainability, although it's not a critical bug.

Low
  • More

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

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

  • Potential stuck spinner if Chat unmounts while isLoading is true; add an effect cleanup to reset the state.
  • LoadingStateUpdater is duplicated across mobile and desktop branches; inlining the logic in Chat reduces duplication and complexity.
  • The new animation does not respect prefers-reduced-motion; add a media query override for accessibility.
  • Consider dynamically updating the logo alt/title when loading to improve assistive feedback.
Additional notes (1)
  • Maintainability | components/chat.tsx:93-93
    LoadingStateUpdater is rendered in both the mobile and desktop branches, creating duplication and room for drift. You can inline the effect into Chat itself and remove the extra component entirely, simplifying control flow and reducing re-renders.
Summary of changes
  • Added a global CSS counter-clockwise spin animation (spin-counter-clockwise) and utility class .animate-spin-counter-clockwise.
  • Introduced a new IsLoadingProvider React context to share an isLoading boolean across the app.
  • Wrapped the app in IsLoadingProvider (in app/layout.tsx) so Header and Chat can share loading state.
  • In Chat, added LoadingStateUpdater that derives isLoading from the latest message’s isGenerating streamable value and updates context.
  • Marked Header as a client component and conditionally added a spinning animation to the logo when isLoading is true.

Comment on lines +22 to +33
function LoadingStateUpdater({ messages }: { messages: UIState }) {
const { setIsLoading } = useIsLoading();
const lastMessage = messages[messages.length - 1];
const isGenerating = lastMessage?.isGenerating;
const [loading] = useStreamableValue(isGenerating);

useEffect(() => {
setIsLoading(loading || false);
}, [loading, setIsLoading]);

return null;
}

Choose a reason for hiding this comment

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

When Chat unmounts (e.g., on route changes), the isLoading state can remain true, leaving the header icon spinning indefinitely. Add a cleanup in the effect to reset loading on unmount to avoid a stuck spinner.

Suggestion

Consider adding a cleanup that resets the flag:

useEffect(() => {
  setIsLoading(Boolean(loading));
  return () => setIsLoading(false);
}, [loading, setIsLoading]);

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.

Comment on lines +241 to +252
@keyframes spin-counter-clockwise {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}

.animate-spin-counter-clockwise {
animation: spin-counter-clockwise 1s linear infinite;
}

Choose a reason for hiding this comment

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

To respect users’ reduced-motion preferences and improve accessibility, consider disabling the spin animation when prefers-reduced-motion: reduce is set.

Suggestion

Add a reduced-motion override:

@media (prefers-reduced-motion: reduce) {
  .animate-spin-counter-clockwise {
    animation: none;
  }
}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this CSS.

Comment on lines +241 to +252
@keyframes spin-counter-clockwise {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}

.animate-spin-counter-clockwise {
animation: spin-counter-clockwise 1s linear infinite;
}

Choose a reason for hiding this comment

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

The keyframes use from { transform: rotate(360deg); } to { transform: rotate(0deg); }. Because 0deg and 360deg are equivalent, some browsers may normalize angles and produce no visible animation or inconsistent direction. For a robust counter-clockwise spin, explicitly rotate from 0deg to -360deg. Also consider honoring prefers-reduced-motion to avoid unnecessary motion for sensitive users.

Suggestion

Consider changing the keyframes to rotate from 0deg to -360deg and add a reduced-motion rule:

@keyframes spin-counter-clockwise {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-360deg); }
}

.animate-spin-counter-clockwise {
  animation: spin-counter-clockwise 1s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .animate-spin-counter-clockwise {
    animation: none;
  }
}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

@charliecreates charliecreates bot removed the request for review from CharlieHelps September 30, 2025 17:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
components/chat.tsx (1)

82-87: Debounce and harden server action calls on drawnFeatures changes.

Calling updateDrawingContext on every reference change can spam the server; also avoid unhandled rejections and remove console noise.

Apply inside the effect:

-  useEffect(() => {
-    if (id && mapData.drawnFeatures && mapData.drawnFeatures.length > 0) {
-      console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures);
-      updateDrawingContext(id, mapData.drawnFeatures);
-    }
-  }, [id, mapData.drawnFeatures]);
+  useEffect(() => {
+    if (!id || !mapData.drawnFeatures || mapData.drawnFeatures.length === 0) return;
+    const key = JSON.stringify(mapData.drawnFeatures);
+    if (key === lastFeaturesKeyRef.current) return;
+    lastFeaturesKeyRef.current = key;
+    void updateDrawingContext(id, mapData.drawnFeatures).catch(console.error);
+  }, [id, mapData.drawnFeatures]);

And add once above the effect:

// Track last sent payload to avoid duplicate calls
const lastFeaturesKeyRef = useRef<string | null>(null);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f027d5b and e8fa0ba.

📒 Files selected for processing (5)
  • app/globals.css (1 hunks)
  • app/layout.tsx (2 hunks)
  • components/chat.tsx (4 hunks)
  • components/header.tsx (3 hunks)
  • components/is-loading-provider.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/globals.css (1)
components/ui/spinner.tsx (1)
  • svg (6-19)
components/is-loading-provider.tsx (1)
components/map-loading-context.tsx (3)
  • children (11-18)
  • MapLoadingContextType (4-7)
  • context (20-26)
components/header.tsx (2)
components/is-loading-provider.tsx (1)
  • useIsLoading (24-30)
lib/utils/index.ts (1)
  • cn (11-13)
app/layout.tsx (2)
components/is-loading-provider.tsx (1)
  • IsLoadingProvider (14-22)
components/header.tsx (1)
  • Header (20-79)
components/chat.tsx (2)
app/actions.tsx (1)
  • UIState (292-297)
components/is-loading-provider.tsx (1)
  • useIsLoading (24-30)
🔇 Additional comments (2)
components/header.tsx (1)

21-21: LGTM: clean integration of loading state into header logo.

Conditional class application via cn is correct and minimal.

Also applies to: 33-41

app/layout.tsx (1)

58-79: LGTM: Provider wiring is correct and orders Header under IsLoadingProvider.

No issues spotted with composition.

Comment on lines +241 to +252
@keyframes spin-counter-clockwise {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}

.animate-spin-counter-clockwise {
animation: spin-counter-clockwise 1s linear infinite;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Respect prefers-reduced-motion for the new spinner.

Add a reduced‑motion override to avoid continuous rotation for users who request less motion.

 @keyframes spin-counter-clockwise {
   from {
     transform: rotate(360deg);
   }
   to {
     transform: rotate(0deg);
   }
 }
 
 .animate-spin-counter-clockwise {
   animation: spin-counter-clockwise 1s linear infinite;
 }
+
+@media (prefers-reduced-motion: reduce) {
+  .animate-spin-counter-clockwise {
+    animation: none;
+  }
+}
📝 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
@keyframes spin-counter-clockwise {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
.animate-spin-counter-clockwise {
animation: spin-counter-clockwise 1s linear infinite;
}
@keyframes spin-counter-clockwise {
from {
transform: rotate(360deg);
}
to {
transform: rotate(0deg);
}
}
.animate-spin-counter-clockwise {
animation: spin-counter-clockwise 1s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.animate-spin-counter-clockwise {
animation: none;
}
}
🤖 Prompt for AI Agents
In app/globals.css around lines 241 to 252, the new spin-counter-clockwise
animation does not respect users' prefers-reduced-motion setting; add a
prefers-reduced-motion override so users requesting reduced motion do not see
continuous rotation by adding a CSS media query @media (prefers-reduced-motion:
reduce) that disables the animation for .animate-spin-counter-clockwise (set
animation: none or animation-duration: 0s and remove animation-iteration-count)
so the spinner remains static for those users.

Comment on lines +22 to +33
function LoadingStateUpdater({ messages }: { messages: UIState }) {
const { setIsLoading } = useIsLoading();
const lastMessage = messages[messages.length - 1];
const isGenerating = lastMessage?.isGenerating;
const [loading] = useStreamableValue(isGenerating);

useEffect(() => {
setIsLoading(loading || false);
}, [loading, setIsLoading]);

return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure cleanup so the global loading indicator never gets stuck.

When Chat unmounts during generation, isLoading can remain true. Reset on unmount.

   useEffect(() => {
-    setIsLoading(loading || false);
-  }, [loading, setIsLoading]);
+    setIsLoading(Boolean(loading));
+    return () => setIsLoading(false);
+  }, [loading, setIsLoading]);

If useStreamableValue from ai/rsc receives undefined, does it safely return [undefined] without throwing? If not, guard against an undefined isGenerating before calling it.


🌐 Web query:

Does the Vercel AI SDK (`ai/rsc`) hook `useStreamableValue` accept `undefined` safely? Link to docs/source.

💡 Result:

Short answer: No — useStreamableValue expects a StreamableValue created by createStreamableValue and does not accept undefined safely. The docs state the hook "takes a streamable value created using createStreamableValue" (so pass the streamable.value), and the RSC utilities/type code shows the streamable value shape is required. See the docs and implementation/discussion for details. [1][2]

Sources


Guard streamable value and reset loading on unmount
Wrap useStreamableValue to avoid passing undefined, and add an unmount cleanup:

 function LoadingStateUpdater({ messages }: { messages: UIState }) {
   const { setIsLoading } = useIsLoading();
   const lastMessage = messages[messages.length - 1];
   const isGenerating = lastMessage?.isGenerating;
-  const [loading] = useStreamableValue(isGenerating);
+  const [loading] = isGenerating != null 
+    ? useStreamableValue(isGenerating) 
+    : [false];

   useEffect(() => {
-    setIsLoading(loading || false);
-  }, [loading, setIsLoading]);
+    setIsLoading(loading);
+    return () => setIsLoading(false);
+  }, [loading, setIsLoading]);

   return 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
function LoadingStateUpdater({ messages }: { messages: UIState }) {
const { setIsLoading } = useIsLoading();
const lastMessage = messages[messages.length - 1];
const isGenerating = lastMessage?.isGenerating;
const [loading] = useStreamableValue(isGenerating);
useEffect(() => {
setIsLoading(loading || false);
}, [loading, setIsLoading]);
return null;
}
function LoadingStateUpdater({ messages }: { messages: UIState }) {
const { setIsLoading } = useIsLoading();
const lastMessage = messages[messages.length - 1];
const isGenerating = lastMessage?.isGenerating;
const [loading] = isGenerating != null
? useStreamableValue(isGenerating)
: [false];
useEffect(() => {
setIsLoading(loading);
return () => setIsLoading(false);
}, [loading, setIsLoading]);
return null;
}
🤖 Prompt for AI Agents
In components/chat.tsx around lines 22 to 33, the call to useStreamableValue can
receive undefined and the effect never resets loading on unmount; change the
input to a guarded boolean (e.g. !!lastMessage?.isGenerating) so
useStreamableValue never gets undefined, and add a cleanup to the useEffect that
calls setIsLoading(false) on unmount (and keep setIsLoading dependency) so
loading is reset when the component unmounts.

const { isLoading } = useIsLoading()

return (
<header className="fixed w-full p-1 md:p-2 flex justify-between items-center z-10 backdrop-blur md:backdrop-blur-none bg-background/80 md:bg-transparent">
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix a11y: non-functional Button and expose loading state to ATs.

  • The icon Button has no onClick/href, creating a focusable control that does nothing.
  • Expose loading via aria-busy to announce to assistive tech.

Apply:

@@
-    <header className="fixed w-full p-1 md:p-2 flex justify-between items-center z-10 backdrop-blur md:backdrop-blur-none bg-background/80 md:bg-transparent">
+    <header
+      className="fixed w-full p-1 md:p-2 flex justify-between items-center z-10 backdrop-blur md:backdrop-blur-none bg-background/80 md:bg-transparent"
+      aria-busy={isLoading}
+    >
@@
-        <Button variant="ghost" size="icon">
-          <Image
+        <Button variant="ghost" size="icon" asChild>
+          <a href="/" aria-label="Home">
+            <Image
              src="/images/logo.svg"
              alt="Logo"
              width={24}
              height={24}
              className={cn('h-6 w-auto', {
                'animate-spin-counter-clockwise': isLoading
              })}
-          />
-        </Button>
+            />
+          </a>
+        </Button>

Also applies to: 32-42

🤖 Prompt for AI Agents
In components/header.tsx around lines 24 and 32-42, the icon Button is focusable
but has no onClick/href and the component doesn't expose loading state to ATs;
either make the control functional (add an onClick handler or href and forward
it from props) or if it's purely decorative convert it to a non-interactive
element (e.g., span/div) and remove button semantics/keyboard focus;
additionally expose the loading state via aria-busy (on the actionable element
or a containing region) and/or aria-disabled when appropriate, and forward a
loading prop so assistive tech can detect busy status.

Comment on lines +5 to +8
interface IsLoadingContextType {
isLoading: boolean
setIsLoading: (isLoading: boolean) => void
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Broaden setIsLoading type to match React’s setState.

Current type blocks functional updates. Use Dispatch<SetStateAction>.

-import { createContext, useContext, useState, ReactNode } from 'react'
+import { createContext, useContext, useState, ReactNode, type Dispatch, type SetStateAction } from 'react'
@@
 interface IsLoadingContextType {
   isLoading: boolean
-  setIsLoading: (isLoading: boolean) => void
+  setIsLoading: Dispatch<SetStateAction<boolean>>
 }

Also applies to: 15-15

🤖 Prompt for AI Agents
In components/is-loading-provider.tsx around lines 5 to 8 (and also at line 15),
the setIsLoading type is currently (isLoading: boolean) => void which prevents
functional updates; change the type to
React.Dispatch<React.SetStateAction<boolean>> and import Dispatch and
SetStateAction (or use React.Dispatch/React.SetStateAction) from React, then
update the useState typing/assignment at line 15 to use the same
Dispatch<SetStateAction<boolean>> type so callers can pass either a boolean or
an updater function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants