Skip to content

[Feat] : 검색어 히스토리 컴포넌트 UI 개선 (#175)#178

Merged
GulSam00 merged 1 commit intodevelopfrom
feat/175-searchHistoryUiImprove
Apr 4, 2026
Merged

[Feat] : 검색어 히스토리 컴포넌트 UI 개선 (#175)#178
GulSam00 merged 1 commit intodevelopfrom
feat/175-searchHistoryUiImprove

Conversation

@GulSam00
Copy link
Copy Markdown
Owner

@GulSam00 GulSam00 commented Apr 2, 2026

User description

📌 PR 제목

[Feat] : 검색어 히스토리 컴포넌트 UI 개선

📌 변경 사항

  • SearchHistory, PopularSearchHistory 컴포넌트에 고정 높이(h-30) 적용하여 레이아웃 시프트 방지
  • SearchHistory: Zustand persist 하이드레이션 전 로딩 스피너 표시
  • PopularSearchHistory: API 요청 중(isPending) 로딩 스피너 표시
  • SearchHistory에서 slice(10)slice(0, 10) 버그 수정

💬 추가 참고 사항


PR Type

Enhancement


Description

  • Add fixed height (h-30) to prevent layout shift in search history components

  • Display loading spinner during Zustand hydration in SearchHistory

  • Display loading spinner during API requests in PopularSearchHistory

  • Fix slice bug: change slice(10) to slice(0, 10) in SearchHistory


Diagram Walkthrough

flowchart LR
  A["SearchHistory Component"] -->|Add hydration state| B["Loading Spinner"]
  A -->|Apply fixed height| C["h-30 container"]
  A -->|Fix slice bug| D["slice(0, 10)"]
  E["PopularSearchHistory Component"] -->|Add isPending state| F["Loading Spinner"]
  E -->|Apply fixed height| G["h-30 container"]
  E -->|Remove index display| H["Simplified UI"]
Loading

File Walkthrough

Relevant files
Enhancement
SearchHistory.tsx
Add hydration loading state and fix slice bug                       

apps/web/src/app/search/SearchHistory.tsx

  • Import Loader2 icon and React hooks for hydration tracking
  • Add isHydrated state to detect Zustand store hydration completion
  • Wrap content with conditional rendering to show loading spinner during
    hydration
  • Apply fixed height class h-30 to root container
  • Fix slice bug from slice(10) to slice(0, 10) to show first 10 items
+36/-24 
PopularSearchHistory.tsx
Add loading spinner and fixed height container                     

apps/web/src/app/search/PopularSearchHistory.tsx

  • Import Loader2 icon for loading indicator
  • Extract isPending state from useSearchLogQuery hook
  • Apply fixed height class h-30 to root container
  • Add conditional rendering to display loading spinner when isPending is
    true
  • Remove index number display from search result items
  • Remove early return for empty state, now handled by loading state
+24/-21 

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 2, 2026

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

Project Deployment Actions Updated (UTC)
singcode Ready Ready Preview, Comment Apr 2, 2026 4:29pm

@GulSam00
Copy link
Copy Markdown
Owner Author

GulSam00 commented Apr 2, 2026

/describe

@GulSam00
Copy link
Copy Markdown
Owner Author

GulSam00 commented Apr 2, 2026

/review

@GulSam00
Copy link
Copy Markdown
Owner Author

GulSam00 commented Apr 2, 2026

/improve

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Improve search history UI with fixed height and loading states

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add fixed height (h-30) to prevent layout shift in search history components
• Display loading spinner during Zustand hydration in SearchHistory
• Display loading spinner during API requests in PopularSearchHistory
• Fix slice bug: change slice(10) to slice(0, 10) in SearchHistory
Diagram
flowchart LR
  A["SearchHistory Component"] -->|Add hydration state| B["Loading Spinner"]
  A -->|Fix slice bug| C["Display first 10 items"]
  A -->|Add fixed height| D["Prevent layout shift"]
  E["PopularSearchHistory Component"] -->|Add isPending state| F["Loading Spinner"]
  E -->|Add fixed height| G["Prevent layout shift"]
Loading

Grey Divider

File Changes

1. apps/web/src/app/search/SearchHistory.tsx ✨ Enhancement +36/-24

Add hydration state and fix slice bug

• Import Loader2 icon and React hooks for state management
• Add isHydrated state to track Zustand store hydration
• Add useEffect hook to set hydration flag when search history loads
• Apply fixed height class h-30 to root container
• Fix slice bug from slice(10) to slice(0, 10) to display first 10 items
• Conditionally render loading spinner during hydration phase

apps/web/src/app/search/SearchHistory.tsx


2. apps/web/src/app/search/PopularSearchHistory.tsx ✨ Enhancement +24/-21

Add loading state and fixed height layout

• Import Loader2 icon for loading indicator
• Extract isPending state from useSearchLogQuery hook
• Apply fixed height class h-30 to root container
• Remove early return for empty search logs
• Conditionally render loading spinner when API request is pending
• Add optional chaining to searchLogs?.slice() for safety
• Remove index display from search log items

apps/web/src/app/search/PopularSearchHistory.tsx


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 2, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (2)

Grey Divider


Action required

1. isHydrated tied to history length 📎 Requirement gap ≡ Correctness ⭐ New
Description
SearchHistory sets isHydrated to true only when searchHistory.length > 0, so if the persisted
store hydrates to an empty array the component can remain stuck showing the spinner forever. This
fails the requirement to transition from the pre-hydration spinner to the hydrated UI once Zustand
persist hydration completes.
Code

apps/web/src/app/search/SearchHistory.tsx[R12-30]

+  const [isHydrated, setIsHydrated] = useState(false);

-  if (searchHistory.length === 0) return null;
+  useEffect(() => {
+    if (searchHistory.length > 0) {
+      setIsHydrated(true);
+    }
+  }, [searchHistory]);

  return (
-    <div>
+    <div className="h-30 overflow-hidden">
      <div className="flex items-center gap-2">
        <Clock className="h-4 w-4" />
        <p className="m-2">최근 검색어</p>
      </div>
-      <div className="flex flex-wrap gap-2 pb-4">
-        {searchHistory.slice(10).map((term, index) => (
-          <div
-            key={`${term}-${index}`}
-            className="bg-background flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm"
-          >
-            <span
-              className="hover:text-primary max-w-30 cursor-pointer truncate text-left"
-              onClick={() => onHistoryClick(term)}
+      {!isHydrated ? (
+        <div className="flex items-center justify-center py-4">
+          <Loader2 className="h-5 w-5 animate-spin" />
+        </div>
+      ) : (
Evidence
PR Compliance ID 2 requires showing a spinner only until Zustand persist hydration completes and
then transitioning to content. The new logic marks hydration complete based on `searchHistory.length
> 0 and renders the spinner when isHydrated` is false, which will never flip true for a
legitimately hydrated-but-empty history.

SearchHistory shows loading spinner before Zustand persist hydration completes
apps/web/src/app/search/SearchHistory.tsx[12-18]
apps/web/src/app/search/SearchHistory.tsx[26-30]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SearchHistory` determines hydration via `searchHistory.length > 0`, which can leave the UI stuck on the loading spinner when hydration completes with an empty list.

## Issue Context
Zustand `persist` provides hydration lifecycle helpers (e.g., `useStore.persist.hasHydrated()` / `useStore.persist.onFinishHydration(...)`) that should be used to track hydration completion independently of the stored data length.

## Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[12-30]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. isHydrated tied to history 📎 Requirement gap ☼ Reliability
Description
SearchHistory sets isHydrated to true only when searchHistory.length > 0, so users with no
stored history will see the loading spinner forever even after Zustand persist hydration completes.
This makes the hydration transition incorrect/unclear and violates the requirement to show the
spinner only until hydration finishes.
Code

apps/web/src/app/search/SearchHistory.tsx[R14-18]

+  useEffect(() => {
+    if (searchHistory.length > 0) {
+      setIsHydrated(true);
+    }
+  }, [searchHistory]);
Evidence
The checklist requires a hydration-based spinner that ends when persist hydration finishes. The new
code instead treats “has at least one history item” as hydrated, and the store is persisted (can
hydrate to an empty array), meaning hydration can complete while isHydrated stays false and the
spinner remains indefinitely.

SearchHistory: Zustand persist 하이드레이션 전 로딩 스피너 표시
apps/web/src/app/search/SearchHistory.tsx[14-18]
apps/web/src/app/search/SearchHistory.tsx[26-33]
apps/web/src/stores/useSearchHistoryStore.ts[18-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SearchHistory` shows a loading spinner until `isHydrated` becomes true, but `isHydrated` is currently set based on `searchHistory.length > 0` instead of Zustand persist hydration completion. If the persisted history is empty, hydration can finish while the spinner never disappears.
## Issue Context
`useSearchHistoryStore` uses `zustand/middleware` `persist`, so hydration completion is independent of whether the hydrated `searchHistory` array has items.
## Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[14-33]
- apps/web/src/stores/useSearchHistoryStore.ts[18-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Undefined Tailwind height class 🐞 Bug ⚙ Maintainability ⭐ New
Description
h-30 is used to enforce fixed height in both history components, but the repo’s Tailwind config
does not extend the spacing/height scale to include 30, so the class will not be generated and the
container won’t actually have the intended fixed height.
Code

apps/web/src/app/search/SearchHistory.tsx[21]

+    <div className="h-30 overflow-hidden">
Evidence
Both components rely on h-30, but the only Tailwind config in the repo has no theme extensions for
height/spacing. Since 30 is not part of Tailwind’s default spacing scale, the utility won’t exist
unless explicitly extended, so the fixed-height intent is not applied.

apps/web/src/app/search/SearchHistory.tsx[20-23]
apps/web/src/app/search/PopularSearchHistory.tsx[14-16]
apps/web/tailwind.config.ts[1-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The components use `h-30`, but Tailwind won’t generate this class unless the project extends the theme to include it. As a result, the new fixed-height behavior likely won’t apply.

### Issue Context
The repo’s Tailwind config does not extend `theme.height`/`theme.spacing`.

### Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[20-22]
- apps/web/src/app/search/PopularSearchHistory.tsx[14-16]
- apps/web/tailwind.config.ts[3-12]

### Suggested fix (pick one)
1) Replace `h-30` with an existing utility (e.g., `h-28` or `h-32`) that matches the desired design.
2) Use an arbitrary value: `h-[7.5rem]` (Tailwind “30” spacing typically corresponds to 7.5rem).
3) Extend Tailwind config:
  - `theme.extend.spacing['30'] = '7.5rem'` (and/or `theme.extend.height['30'] = '7.5rem'`) so `h-30` becomes valid.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Undefined h-30 utility 🐞 Bug ⚙ Maintainability
Description
Both history components use className="h-30" for fixed height, but this repo’s Tailwind config
does not extend spacing/height and global CSS doesn’t define .h-30, so the fixed-height constraint
likely won’t apply. This undermines the PR’s goal of preventing layout shift via a fixed component
height.
Code

apps/web/src/app/search/PopularSearchHistory.tsx[15]

+    <div className="h-30 overflow-hidden">
Evidence
h-30 is introduced on the wrapper in both components, but apps/web/tailwind.config.ts only
extends fontFamily and does not define a custom spacing/height scale entry for 30. The only
global stylesheet (globals.css) contains no custom .h-30 utility definition, so there’s no
in-repo definition supporting this class.

apps/web/src/app/search/SearchHistory.tsx[20-23]
apps/web/src/app/search/PopularSearchHistory.tsx[14-16]
apps/web/tailwind.config.ts[3-13]
apps/web/src/globals.css[1-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The UI relies on `h-30` to enforce a fixed height, but this utility is not defined in the repo’s Tailwind config or global CSS, so it may be a no-op.
### Issue Context
Both `SearchHistory` and `PopularSearchHistory` set `className="h-30 overflow-hidden"`.
### Fix
Pick one:
1) Replace with an existing Tailwind height (e.g. `h-32`) if the exact height is flexible.
2) Use an arbitrary value (e.g. `h-[7.5rem]`) to guarantee the height.
3) Extend Tailwind theme to include `30` (e.g. in `theme.extend.spacing` or `theme.extend.height`).
### Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[20-22]
- apps/web/src/app/search/PopularSearchHistory.tsx[14-16]
- apps/web/tailwind.config.ts[3-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 2, 2026

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 2, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (1)

Grey Divider


Action required

1. isHydrated tied to history 📎 Requirement gap ☼ Reliability
Description
SearchHistory sets isHydrated to true only when searchHistory.length > 0, so users with no
stored history will see the loading spinner forever even after Zustand persist hydration completes.
This makes the hydration transition incorrect/unclear and violates the requirement to show the
spinner only until hydration finishes.
Code

apps/web/src/app/search/SearchHistory.tsx[R14-18]

+  useEffect(() => {
+    if (searchHistory.length > 0) {
+      setIsHydrated(true);
+    }
+  }, [searchHistory]);
Evidence
The checklist requires a hydration-based spinner that ends when persist hydration finishes. The new
code instead treats “has at least one history item” as hydrated, and the store is persisted (can
hydrate to an empty array), meaning hydration can complete while isHydrated stays false and the
spinner remains indefinitely.

SearchHistory: Zustand persist 하이드레이션 전 로딩 스피너 표시
apps/web/src/app/search/SearchHistory.tsx[14-18]
apps/web/src/app/search/SearchHistory.tsx[26-33]
apps/web/src/stores/useSearchHistoryStore.ts[18-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SearchHistory` shows a loading spinner until `isHydrated` becomes true, but `isHydrated` is currently set based on `searchHistory.length > 0` instead of Zustand persist hydration completion. If the persisted history is empty, hydration can finish while the spinner never disappears.

## Issue Context
`useSearchHistoryStore` uses `zustand/middleware` `persist`, so hydration completion is independent of whether the hydrated `searchHistory` array has items.

## Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[14-33]
- apps/web/src/stores/useSearchHistoryStore.ts[18-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Undefined h-30 utility 🐞 Bug ⚙ Maintainability
Description
Both history components use className="h-30" for fixed height, but this repo’s Tailwind config
does not extend spacing/height and global CSS doesn’t define .h-30, so the fixed-height constraint
likely won’t apply. This undermines the PR’s goal of preventing layout shift via a fixed component
height.
Code

apps/web/src/app/search/PopularSearchHistory.tsx[15]

+    <div className="h-30 overflow-hidden">
Evidence
h-30 is introduced on the wrapper in both components, but apps/web/tailwind.config.ts only
extends fontFamily and does not define a custom spacing/height scale entry for 30. The only
global stylesheet (globals.css) contains no custom .h-30 utility definition, so there’s no
in-repo definition supporting this class.

apps/web/src/app/search/SearchHistory.tsx[20-23]
apps/web/src/app/search/PopularSearchHistory.tsx[14-16]
apps/web/tailwind.config.ts[3-13]
apps/web/src/globals.css[1-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The UI relies on `h-30` to enforce a fixed height, but this utility is not defined in the repo’s Tailwind config or global CSS, so it may be a no-op.

### Issue Context
Both `SearchHistory` and `PopularSearchHistory` set `className="h-30 overflow-hidden"`.

### Fix
Pick one:
1) Replace with an existing Tailwind height (e.g. `h-32`) if the exact height is flexible.
2) Use an arbitrary value (e.g. `h-[7.5rem]`) to guarantee the height.
3) Extend Tailwind theme to include `30` (e.g. in `theme.extend.spacing` or `theme.extend.height`).

### Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[20-22]
- apps/web/src/app/search/PopularSearchHistory.tsx[14-16]
- apps/web/tailwind.config.ts[3-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

PR Description updated to latest commit (2580aea)

Comment on lines +14 to +18
useEffect(() => {
if (searchHistory.length > 0) {
setIsHydrated(true);
}
}, [searchHistory]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. ishydrated tied to history 📎 Requirement gap ☼ Reliability

SearchHistory sets isHydrated to true only when searchHistory.length > 0, so users with no
stored history will see the loading spinner forever even after Zustand persist hydration completes.
This makes the hydration transition incorrect/unclear and violates the requirement to show the
spinner only until hydration finishes.
Agent Prompt
## Issue description
`SearchHistory` shows a loading spinner until `isHydrated` becomes true, but `isHydrated` is currently set based on `searchHistory.length > 0` instead of Zustand persist hydration completion. If the persisted history is empty, hydration can finish while the spinner never disappears.

## Issue Context
`useSearchHistoryStore` uses `zustand/middleware` `persist`, so hydration completion is independent of whether the hydrated `searchHistory` array has items.

## Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[14-33]
- apps/web/src/stores/useSearchHistoryStore.ts[18-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +12 to +30
const [isHydrated, setIsHydrated] = useState(false);

if (searchHistory.length === 0) return null;
useEffect(() => {
if (searchHistory.length > 0) {
setIsHydrated(true);
}
}, [searchHistory]);

return (
<div>
<div className="h-30 overflow-hidden">
<div className="flex items-center gap-2">
<Clock className="h-4 w-4" />
<p className="m-2">최근 검색어</p>
</div>
<div className="flex flex-wrap gap-2 pb-4">
{searchHistory.slice(10).map((term, index) => (
<div
key={`${term}-${index}`}
className="bg-background flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm"
>
<span
className="hover:text-primary max-w-30 cursor-pointer truncate text-left"
onClick={() => onHistoryClick(term)}
{!isHydrated ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="h-5 w-5 animate-spin" />
</div>
) : (
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. ishydrated tied to history length 📎 Requirement gap ≡ Correctness

SearchHistory sets isHydrated to true only when searchHistory.length > 0, so if the persisted
store hydrates to an empty array the component can remain stuck showing the spinner forever. This
fails the requirement to transition from the pre-hydration spinner to the hydrated UI once Zustand
persist hydration completes.
Agent Prompt
## Issue description
`SearchHistory` determines hydration via `searchHistory.length > 0`, which can leave the UI stuck on the loading spinner when hydration completes with an empty list.

## Issue Context
Zustand `persist` provides hydration lifecycle helpers (e.g., `useStore.persist.hasHydrated()` / `useStore.persist.onFinishHydration(...)`) that should be used to track hydration completion independently of the stored data length.

## Fix Focus Areas
- apps/web/src/app/search/SearchHistory.tsx[12-30]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Base automatically changed from feat/174-searchLogAndUiImprove to develop April 4, 2026 14:32
@GulSam00 GulSam00 merged commit 586a4ef into develop Apr 4, 2026
2 checks passed
@GulSam00 GulSam00 deleted the feat/175-searchHistoryUiImprove branch April 4, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

검색어 히스토리 컴포넌트 UI 개선 - 고정 높이 및 로딩 스피너 추가

1 participant