[v0.6.6] - cmdk 검색 UX 개선 및 인기순 정렬 로직 최적화#124
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughUI/UX 개선을 위한 복합적 업데이트입니다. 반응형 디자인 개선(xl2→3xl 브레이크포인트), 검색 팔레트 최근 항목 기록 추가, 인기도 정렬 알고리즘 개선, 이미지 공유 기능 확대, 컴포넌트 색상/스타일 조정 등이 포함됩니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
리뷰 포인트🟡 주목할 영역
✅ 긍정적 측면
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/hooks/useShare.ts (1)
134-136: 🧹 Nitpick | 🔵 Trivial
useCallback의 의존성 배열에defaultOptions객체 참조가 직접 들어있습니다.호출자(예:
ShareButton)가 매 렌더마다 새 객체 리터럴을defaultOptions로 전달하면share함수가 매번 재생성됩니다. 현재ShareButton에서useShare({ title, imageUrl })로 인라인 객체를 넘기고 있어서,title이나imageUrl이 변경되지 않아도share가 재생성될 수 있습니다.실제 동작에 영향을 주지는 않지만(ref 기반이므로), 하위 컴포넌트에
share를 prop으로 전달하거나useEffect의존성으로 사용하게 되면 불필요한 리렌더가 발생할 수 있습니다.♻️ 호출부에서 useMemo로 옵션 안정화하는 방법
ShareButton.tsx등 호출부에서:const shareOptions = useMemo(() => ({ title, imageUrl }), [title, imageUrl]); const { share } = useShare(shareOptions);src/hooks/usePostLike.ts (2)
31-44:⚠️ Potential issue | 🟡 Minor데이터 로딩 시
viewerId가 빈 문자열일 때 불필요한 API 호출이 발생할 수 있습니다
toggleLike(Line 48)에서는if (!viewerId)가드가 있지만, 이useEffect내부(Line 35)에서는viewerIdRef.current가 빈 문자열이어도fetchPostLikeState(postId, "")가 그대로 호출됩니다.
getOrCreateViewerId()가 실패하거나 SSR 환경 등에서 빈 문자열이 반환된 경우, 의미 없는 Supabase 요청이 발생하게 됩니다.toggleLike와 동일한 가드를 추가하는 것을 권장합니다.🛡️ viewerId 가드 추가 제안
useEffect(() => { if (!postId) return; const run = async () => { const viewerId = viewerIdRef.current; + if (!viewerId) { + setLoading(false); + return; + } const { count, liked } = await fetchPostLikeState(postId, viewerId); setCount(count); setLiked(liked); setLoading(false); }; void run(); }, [postId]);
27-29: 🧹 Nitpick | 🔵 Trivial두
useEffect간 실행 순서 의존성에 대한 참고첫 번째
useEffect(Line 27-29)가viewerIdRef.current를 설정하고, 두 번째useEffect(Line 31-44)가 그 값을 읽습니다. React는 같은 렌더 사이클에서 선언 순서대로 effect를 실행하므로 현재는 정상 동작합니다.다만, 향후 리팩토링 시 이 순서가 바뀌면 빈 문자열로 API가 호출될 수 있습니다. 위에서 제안한
viewerId가드를 추가하면 이 의존성이 깨져도 안전합니다.Also applies to: 31-44
src/hooks/useCommandPaletteInternal.ts (1)
192-205: 🧹 Nitpick | 🔵 Trivial
navigator.platform은 deprecated입니다.기존 코드이긴 하지만,
navigator.platform은 웹 표준에서 deprecated 처리되었습니다.navigator.userAgentData?.platform또는 User-Agent 기반 감지로 전환을 검토해 보세요.src/components/common/CommandPalette.tsx (1)
75-118:⚠️ Potential issue | 🔴 Critical
<Command>에shouldFilter={false}가 빠져 있어 검색 결과가 이중 필터링됩니다.현재 훅(
useCommandPaletteInternal)에서label + hint + searchableText를 기반으로 직접 필터링하여displayItems를 반환하고 있습니다. 그런데 cmdk의<Command>컴포넌트는 기본적으로 자체 내장 필터도 적용합니다(각Command.Item의value속성 기준).Line 105에서
value={item.label}로 설정되어 있으므로, cmdk는 label 텍스트만 기준으로 다시 필터링합니다. 결과적으로:
- 훅에서
hint나searchableText로 매칭된 항목이 cmdk 내장 필터에 의해 숨겨질 수 있습니다.Command.Empty("검색 결과가 없습니다")가 실제로 훅이 반환한 결과가 있는 상황에서도 표시될 수 있습니다.
shouldFilter={false}를 추가하여 cmdk의 내장 필터를 비활성화해야 합니다.🐛 수정 제안
- <Command className="flex max-h-[70vh] flex-col overflow-hidden rounded-2xl bg-transparent text-sm text-background"> + <Command shouldFilter={false} className="flex max-h-[70vh] flex-col overflow-hidden rounded-2xl bg-transparent text-sm text-background">
🤖 Fix all issues with AI agents
In `@src/app/`(layout)/posts/[slug]/page.tsx:
- Around line 24-25: Remove or clarify the ineffective ISR setting: delete the
export const revalidate = 60; (or replace it with a comment) because velitePosts
are static artifacts produced into the .velite directory at build time and won’t
update with ISR; if you intend to keep it as a placeholder for future dynamic
data, replace the line with a comment referencing velitePosts and .velite (e.g.,
explaining that new posts require velite --clean → next build) so readers won’t
expect revalidate to refresh content.
In `@src/app/globals.css`:
- Line 3: The Biome CSS parser is flagging the Tailwind v4 directive '@config
"../../tailwind.config.ts"' — enable Tailwind directive support by setting the
Biome CSS parser option tailwindDirectives to true in your Biome configuration
so the '@config' directive (and other Tailwind directives) are recognized;
update the Biome config's css parser settings accordingly.
In `@src/components/common/controls/sort/SortSelectClient.tsx`:
- Around line 23-25: handleChange currently always calls
router.push(buildUrl(next)) which can cause redundant navigation when next ===
currentValue; add a defensive early return at the start of
SortSelectClient.handleChange (the function named handleChange that calls
router.push(buildUrl(next))) to check if next === currentValue and return
immediately, ensuring you reference the same currentValue used by SortSelect and
leaving buildUrl and router.push unchanged.
In `@src/hooks/useCommandPaletteInternal.ts`:
- Around line 40-44: The isPostItem predicate mixes two criteria and should be
narrowed to only check the id to avoid false positives; update the isPostItem
function to return item.id.startsWith("post-") only (referencing isPostItem and
buildPostItems so you know post items are created with id `post-${post.slug}`
and href `/posts/${post.slug}`), removing the href-based check to ensure only
items explicitly created as posts are classified as posts.
- Around line 171-183: handleSelect currently calls
setRecentItems(pushRecent(item)) which mixes the side-effecting sessionStorage
write inside pushRecent with the state update; extract the side effect by
calling pushRecent(item) first, store its return value in a const (e.g.
newRecent) and then call setRecentItems(newRecent), and ensure the useCallback
dependency array includes pushRecent and setRecentItems (and any other
referenced symbols) so the memoization is correct; this makes pushRecent's
sessionStorage side effect explicit and separates it from the state update in
handleSelect.
- Around line 94-99: writeRecent currently serializes the entire CommandItem
array (including the potentially large searchableText) into sessionStorage under
RECENT_KEY; change writeRecent to first map items.slice(0, RECENT_LIMIT) to a
compact shape that omits searchableText (and any other non-essential or large
fields) before JSON.stringify, ensuring only necessary fields (e.g., id,
title/name, category, shortcut) are stored to reduce size while preserving the
recent list behavior.
- Around line 83-109: Add the same SSR guard used in readRecent to any function
that accesses sessionStorage: update writeRecent and pushRecent to first check
typeof window !== "undefined" (or otherwise no-op/return current items) before
calling sessionStorage.setItem/getItem; ensure writeRecent still respects
RECENT_KEY and RECENT_LIMIT and pushRecent returns the current list unchanged
when running in SSR so callers of pushRecent (e.g., handleSelect) remain safe.
- Around line 83-92: The readRecent function returns parsed sessionStorage data
without runtime shape validation; change it to validate each parsed item (from
RECENT_KEY) is an object matching CommandItem (e.g., has required properties
like id and label and correct types) before including it in the result, filter
out invalid entries, then return at most RECENT_LIMIT items (keeping the
existing slice logic), and preserve the try/catch behavior to fall back to [] on
parse errors.
In `@src/hooks/usePostLike.ts`:
- Around line 63-65: Remove the duplicate short debug block that logs
error.code/error.message/error.details/error.hint (the first if (error) around
the early part of usePostLike) and consolidate any needed logging into the
existing main error handler later in the same function (the second if (error)
block that already handles and returns); also fix indentation to match the
file's 4-space style and avoid emitting development-only fields (remove
details/hint or replace with a single contextual message) so only one error
handler remains with consistent formatting.
In `@src/hooks/useShare.ts`:
- Around line 25-27: normalizeUrl currently reads window.location.href at module
scope which can throw in SSR; change normalizeUrl to avoid accessing window at
import time by computing the base URL lazily inside the function with a typeof
window !== "undefined" guard (or by adding an optional base parameter) and fall
back to a safe default or require callers to pass a base. Update callers such as
buildShareFile to pass a base when running in non-browser contexts or rely on
the guarded behavior so normalizeUrl never accesses window during SSR.
- Around line 29-37: In buildShareFile, after calling
fetch(normalizeUrl(imageUrl), ...) check response.ok and throw or reject with a
clear error (include response.status/statusText) to avoid turning error HTML
into a blob; then call response.blob() only for ok responses. Also derive the
file extension from blob.type (e.g., split after "image/" and fallback to "png"
if missing) and use that extension when constructing the File instead of the
hardcoded "post-thumbnail.png" so the filename matches the actual MIME type.
- Around line 122-125: Wrap the navigator.clipboard.writeText call in a
try-catch inside the useShare hook (the block currently using
navigator.clipboard?.writeText) so any thrown exception is caught and an
explicit error result is returned instead of bubbling as an unhandled rejection;
on catch return a failure object (e.g., { ok: false, method: "clipboard", error
}) and ensure isSharingRef is still released in the existing finally block. Use
the symbols navigator.clipboard.writeText, isSharingRef, and the share function
inside useShare to locate and update the code.
In `@src/lib/posts/query.ts`:
- Line 80: fetchPopularRankPage is always called with offset = 0 (const
candidateRows = await fetchPopularRankPage(candidateLimit, 0);) making the
offset parameter dead; either remove the offset parameter from
fetchPopularRankPage's signature and all its callers (including this call) or
explicitly mark it as intentional with a TODO explaining future server-side
pagination plans; update the function definition (fetchPopularRankPage) and any
references (candidateRows, candidateLimit, paginate()) accordingly so the
signature and usage are consistent.
- Around line 76-77: The code redundantly recalculates safePage and safePerPage
even though paginate() already applies Math.max(1, Math.floor(...)); remove the
duplicate Math.max/Math.floor calls in query.ts (the safePage and safePerPage
assignments) and rely on the sanitized values from paginate(), or if you
intentionally need a sanitized safePerPage specifically for candidateLimit
calculation, keep only safePerPage and add a concise comment above it
referencing candidateLimit to explain the purpose; ensure references to
paginate(), safePage, safePerPage, and candidateLimit are preserved so reviewers
can locate the logic.
- Around line 75-124: The current popular-sort logic can misclassify liked posts
outside the fetched candidate window because candidateLimit is fixed; update the
candidateLimit calculation to account for the current pool size before calling
fetchPopularRankPage (use pool.length to raise the limit when pool is smaller
than candidateLimit or use Math.min(pool.length, MAX_CANDIDATE_CEILING) to avoid
excessive queries), then call fetchPopularRankPage with this adjusted limit
(symbols to edit: POPULAR_CANDIDATE_MULTIPLIER, candidateLimit,
fetchPopularRankPage, and pool), or alternatively detect when the fetched
candidateRows length < pool.length and log a warning and/or fall back to
treating all pool items as potentially liked so sorting remains correct.
In `@src/lib/supabase/viewerId.ts`:
- Around line 4-6: 현재 getOrCreateViewerId 함수 내에서 매 호출마다 const KEY = "viewer_id"를
선언하고 있어 KEY를 모듈 스코프로 옮기면 일관성과 가독성이 좋아집니다; 해결책은 getOrCreateViewerId 함수 바깥에 KEY
상수를 선언(예: const KEY = "viewer_id";)하고 기존 함수 내부의 선언을 제거하여 UUID_REGEX 등 다른 모듈 수준
상수들과 같은 스코프에 두는 것입니다.
- Around line 8-12: Wrap the localStorage and crypto.randomUUID calls in a
try-catch inside the viewerId logic so any exceptions (missing SecureContext,
TypeError from crypto.randomUUID, or localStorage throws) are caught and the
function returns an empty string; specifically, in the block that reads KEY via
window.localStorage.getItem(KEY), tests UUID_REGEX, generates id via
crypto.randomUUID(), and calls window.localStorage.setItem(KEY, id), guard
crypto.randomUUID existence (e.g. typeof crypto?.randomUUID === "function")
before calling and surround getItem/setItem and id creation with try-catch,
returning "" on error to avoid throwing from viewerId.
In `@src/styles/tokens.css`:
- Around line 40-41: The hover color variable --color-scrollbar-thumb-hover is
currently the same for dark and light themes and is too faint on the dark
background; update the dark-theme value so it is visibly lighter/more opaque
than --color-scrollbar-thumb (e.g., increase alpha or use a brighter rgba) and
ensure you scope it for dark mode (using the .dark or :root[data-theme="dark"]
selector) while keeping the light-theme hover value unchanged.
| @import "tailwindcss"; | ||
| @plugin "@tailwindcss/typography"; | ||
| @config "../../tailwind.config.ts"; | ||
| @config "../../tailwind.config.ts"; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Biome 정적 분석 경고는 설정 이슈입니다.
Biome가 @config 디렉티브를 인식하지 못하고 있습니다. 이는 Tailwind CSS v4의 정상적인 문법이므로, Biome CSS 파서 옵션에서 tailwindDirectives를 활성화하면 해결됩니다. 코드 자체에는 문제가 없습니다.
🧰 Tools
🪛 Biome (2.3.13)
[error] 3-3: Tailwind-specific syntax is disabled.
Enable tailwindDirectives in the css parser options, or remove this if you are not using Tailwind CSS.
(parse)
🤖 Prompt for AI Agents
In `@src/app/globals.css` at line 3, The Biome CSS parser is flagging the Tailwind
v4 directive '@config "../../tailwind.config.ts"' — enable Tailwind directive
support by setting the Biome CSS parser option tailwindDirectives to true in
your Biome configuration so the '@config' directive (and other Tailwind
directives) are recognized; update the Biome config's css parser settings
accordingly.
| const handleChange = (next: PostSortValue) => { | ||
| router.push(buildUrl(next), { scroll: false }); | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
handleChange에서 중복 네비게이션 가드가 빠진 부분 확인 필요
이전 코드에서 handleChange 내에 next === currentValue 가드가 있었는지 확인이 필요합니다. 현재는 SortSelect 내부의 handleChange에서 if (next === currentValue) return; 가드가 있어 onChange가 호출되지 않으므로 실질적으로는 안전합니다. 다만, SortSelectClient의 handleChange가 직접 호출될 가능성이 있다면 방어 코드를 추가하는 것도 고려해볼 수 있습니다.
🛡️ 선택적 방어 코드
const handleChange = (next: PostSortValue) => {
+ if (next === value) return;
router.push(buildUrl(next), { scroll: 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.
| const handleChange = (next: PostSortValue) => { | |
| router.push(buildUrl(next), { scroll: false }); | |
| }; | |
| const handleChange = (next: PostSortValue) => { | |
| if (next === value) return; | |
| router.push(buildUrl(next), { scroll: false }); | |
| }; |
🤖 Prompt for AI Agents
In `@src/components/common/controls/sort/SortSelectClient.tsx` around lines 23 -
25, handleChange currently always calls router.push(buildUrl(next)) which can
cause redundant navigation when next === currentValue; add a defensive early
return at the start of SortSelectClient.handleChange (the function named
handleChange that calls router.push(buildUrl(next))) to check if next ===
currentValue and return immediately, ensuring you reference the same
currentValue used by SortSelect and leaving buildUrl and router.push unchanged.
| function isPostItem(item: CommandItem) { | ||
| return ( | ||
| item.id.startsWith("post-") || (item.href?.startsWith("/posts/") ?? false) | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
isPostItem — 판별 조건이 buildPostItems와 결합되어 있습니다.
item.id.startsWith("post-") 와 item.href?.startsWith("/posts/") 두 조건을 OR로 묶고 있는데, buildPostItems에서 생성하는 아이템은 항상 두 조건을 동시에 만족합니다(Line 69: post-${post.slug}, Line 74: /posts/${post.slug}).
현재 동작에 문제는 없지만, 만약 나중에 /posts/ 경로를 가지는 static 아이템이 추가되거나 post- prefix가 다른 용도로 쓰이면 의도치 않게 recent에 저장될 수 있습니다. id 기반 판별 하나로 충분해 보이므로, 단일 조건으로 좁히는 것도 고려해 주세요.
🤖 Prompt for AI Agents
In `@src/hooks/useCommandPaletteInternal.ts` around lines 40 - 44, The isPostItem
predicate mixes two criteria and should be narrowed to only check the id to
avoid false positives; update the isPostItem function to return
item.id.startsWith("post-") only (referencing isPostItem and buildPostItems so
you know post items are created with id `post-${post.slug}` and href
`/posts/${post.slug}`), removing the href-based check to ensure only items
explicitly created as posts are classified as posts.
| function readRecent(): CommandItem[] { | ||
| if (typeof window === "undefined") return []; | ||
| try { | ||
| const raw = sessionStorage.getItem(RECENT_KEY); | ||
| const parsed = raw ? (JSON.parse(raw) as CommandItem[]) : []; | ||
| return Array.isArray(parsed) ? parsed.slice(0, RECENT_LIMIT) : []; | ||
| } catch { | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| function writeRecent(items: CommandItem[]) { | ||
| sessionStorage.setItem( | ||
| RECENT_KEY, | ||
| JSON.stringify(items.slice(0, RECENT_LIMIT)), | ||
| ); | ||
| } | ||
|
|
||
| function pushRecent(item: CommandItem): CommandItem[] { | ||
| const current = readRecent(); | ||
| const next = [item, ...current.filter((x) => x.id !== item.id)].slice( | ||
| 0, | ||
| RECENT_LIMIT, | ||
| ); | ||
| writeRecent(next); | ||
| return next; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
readRecent에는 SSR 가드가 있지만 writeRecent에는 없습니다.
readRecent는 typeof window === "undefined" 체크를 하고 있지만(Line 84), writeRecent(Line 94-99)와 pushRecent(Line 101-109)는 SSR 가드 없이 바로 sessionStorage에 접근합니다.
현재 pushRecent가 handleSelect 콜백 안에서만 호출되므로 실제 SSR에서 실행될 가능성은 낮지만, 방어적으로 writeRecent에도 동일한 가드를 추가하면 일관성과 안전성이 높아집니다.
🛡️ 제안
function writeRecent(items: CommandItem[]) {
+ if (typeof window === "undefined") return;
sessionStorage.setItem(
RECENT_KEY,
JSON.stringify(items.slice(0, RECENT_LIMIT)),
);
}🤖 Prompt for AI Agents
In `@src/hooks/useCommandPaletteInternal.ts` around lines 83 - 109, Add the same
SSR guard used in readRecent to any function that accesses sessionStorage:
update writeRecent and pushRecent to first check typeof window !== "undefined"
(or otherwise no-op/return current items) before calling
sessionStorage.setItem/getItem; ensure writeRecent still respects RECENT_KEY and
RECENT_LIMIT and pushRecent returns the current list unchanged when running in
SSR so callers of pushRecent (e.g., handleSelect) remain safe.
| const safePage = Math.max(1, Math.floor(page)); | ||
| const safePerPage = Math.max(1, Math.floor(perPage)); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
safePage/safePerPage 중복 계산
paginate() 내부(utils.ts Line 67~74)에서 이미 동일한 안전 처리(Math.max(1, Math.floor(...)))를 하고 있어서 여기서 한 번 더 하는 것은 중복입니다. 해를 끼치진 않지만, candidateLimit 계산에만 safePerPage가 필요하다면 그 목적을 주석으로 명시하면 의도가 더 명확해집니다.
🤖 Prompt for AI Agents
In `@src/lib/posts/query.ts` around lines 76 - 77, The code redundantly
recalculates safePage and safePerPage even though paginate() already applies
Math.max(1, Math.floor(...)); remove the duplicate Math.max/Math.floor calls in
query.ts (the safePage and safePerPage assignments) and rely on the sanitized
values from paginate(), or if you intentionally need a sanitized safePerPage
specifically for candidateLimit calculation, keep only safePerPage and add a
concise comment above it referencing candidateLimit to explain the purpose;
ensure references to paginate(), safePage, safePerPage, and candidateLimit are
preserved so reviewers can locate the logic.
| const safePerPage = Math.max(1, Math.floor(perPage)); | ||
|
|
||
| const candidateLimit = safePerPage * POPULAR_CANDIDATE_MULTIPLIER; | ||
| const candidateRows = await fetchPopularRankPage(candidateLimit, 0); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
offset이 항상 0으로 고정되어 있습니다
fetchPopularRankPage가 offset 파라미터를 받지만, 여기서는 항상 0을 전달합니다. 현재 구조에서 페이지네이션은 클라이언트에서 paginate()로 처리하므로 동작에는 문제가 없지만, fetchPopularRankPage의 offset 파라미터가 사실상 미사용(dead parameter) 상태입니다. 향후 서버사이드 페이지네이션으로 전환할 계획이 아니라면, 함수 시그니처를 단순화하거나 TODO 주석을 남겨 두면 좋겠습니다.
🤖 Prompt for AI Agents
In `@src/lib/posts/query.ts` at line 80, fetchPopularRankPage is always called
with offset = 0 (const candidateRows = await
fetchPopularRankPage(candidateLimit, 0);) making the offset parameter dead;
either remove the offset parameter from fetchPopularRankPage's signature and all
its callers (including this call) or explicitly mark it as intentional with a
TODO explaining future server-side pagination plans; update the function
definition (fetchPopularRankPage) and any references (candidateRows,
candidateLimit, paginate()) accordingly so the signature and usage are
consistent.
| --color-scrollbar-thumb: rgba(187, 187, 187, 0.852); | ||
| --color-scrollbar-thumb-hover: rgba(15, 23, 42, 0.28); |
There was a problem hiding this comment.
다크 모드 스크롤바 hover 색상이 라이트 모드와 동일합니다 — 복사 실수로 보입니다.
--color-scrollbar-thumb-hover가 다크/라이트 모드 모두 rgba(15, 23, 42, 0.28)로 동일합니다. 다크 배경(#040523) 위에서 이 색상은 거의 보이지 않아, hover 피드백이 사실상 없게 됩니다.
의도된 것이 아니라면, 다크 모드 thumb 색상(rgba(187, 187, 187, 0.852))보다 약간 밝거나 불투명한 값으로 수정하는 것이 좋겠습니다.
🎨 수정 제안
--color-scrollbar-thumb: rgba(187, 187, 187, 0.852);
- --color-scrollbar-thumb-hover: rgba(15, 23, 42, 0.28);
+ --color-scrollbar-thumb-hover: rgba(200, 200, 200, 0.95);📝 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.
| --color-scrollbar-thumb: rgba(187, 187, 187, 0.852); | |
| --color-scrollbar-thumb-hover: rgba(15, 23, 42, 0.28); | |
| --color-scrollbar-thumb: rgba(187, 187, 187, 0.852); | |
| --color-scrollbar-thumb-hover: rgba(200, 200, 200, 0.95); |
🤖 Prompt for AI Agents
In `@src/styles/tokens.css` around lines 40 - 41, The hover color variable
--color-scrollbar-thumb-hover is currently the same for dark and light themes
and is too faint on the dark background; update the dark-theme value so it is
visibly lighter/more opaque than --color-scrollbar-thumb (e.g., increase alpha
or use a brighter rgba) and ensure you scope it for dark mode (using the .dark
or :root[data-theme="dark"] selector) while keeping the light-theme hover value
unchanged.
요약
변경 목적(왜?):
커맨드 팔레트(cmdk) UX와 인기순 정렬 흐름에서 발생하던 사용성·성능 이슈를 개선하고, 디자인 시스템 일관성을 정리하기 위함
주요 변경(무엇을?):
변경 내용
UI/컴포넌트
로직/유틸
문서/설정
스크린샷/동영상 (선택)
테스트
pnpm test통과pnpm typecheck,pnpm lint)관련 이슈
close #123
Summary by CodeRabbit
릴리스 노트
새 기능
스타일