Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/posts/INSIGHT/git-merge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ category: "Insight"
series: "git/github"
tags: ["git", "github"]
summary: "Git Flow를 전제로 merge, squash, rebase의 차이와 협업에서 rebase를 조심해야 하는 이유를 정리한 글"
thumbnail: "https://res.cloudinary.com/dvapam1ks/image/upload/v1770430277/git_github%E1%84%8A%E1%85%A5%E1%86%B7%E1%84%82%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AF_sfk61p.png"
thumbnail: "https://res.cloudinary.com/dvapam1ks/image/upload/v1770451051/git-1%E1%84%8A%E1%85%A5%E1%86%B7%E1%84%82%E1%85%A6%E1%84%8B%E1%85%B5%E1%86%AF_zilex3.png"
draft: false
---

Expand Down
2 changes: 1 addition & 1 deletion src/app/(layout)/(shell)/(category)/[category]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default function SiteLayout({
children: React.ReactNode;
}) {
return (
<main className="flex-1 -mt-5 pt-16 px-5 md:px-20 lg:px-40 xl:px-70 xl2:px-80 max-w-[1560px] w-full">
<main className="flex-1 -mt-5 pt-16 px-5 md:px-20 lg:px-40 xl:px-70 3xl:px-80 max-w-[1560px] w-full">
{children}
</main>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default function PostListGrid({
className={clsx(
"md:mt-10",
isSmall ? "gap-5" : "gap-8",
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 3xl:grid-cols-4",
"items-stretch justify-items-center"
)}
>
Expand Down
3 changes: 2 additions & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@config "../../tailwind.config.ts";
@config "../../tailwind.config.ts";
Copy link

Choose a reason for hiding this comment

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

🧹 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.


/* 디자인 토큰 & 다크모드 */
@import "../styles/tokens.css";
Expand All @@ -22,6 +22,7 @@
/* 백그라운드 애니메이션 우주 배경 */
@import "../styles/components/background.css";


.heading-anchor {
text-decoration: none;
}
Expand Down
21 changes: 14 additions & 7 deletions src/components/common/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import useCommandPaletteInternal from "@/hooks/useCommandPaletteInternal";
import * as Dialog from "@radix-ui/react-dialog";
import clsx from "clsx";
import { Command } from "cmdk";
import { Search } from "lucide-react";
import { createContext, useContext, type ReactNode } from "react";
Expand All @@ -25,14 +26,14 @@ interface CommandPaletteProviderProps {
}

const CommandPaletteContext = createContext<CommandPaletteContextValue | null>(
null
null,
);

export function useCommandPalette() {
const ctx = useContext(CommandPaletteContext);
if (!ctx) {
throw new Error(
"useCommandPalette must be used within CommandPaletteProvider"
"useCommandPalette must be used within CommandPaletteProvider",
);
}
return ctx;
Expand All @@ -44,7 +45,7 @@ export function CommandPaletteProvider({
const {
open,
query,
filteredItems,
displayItems,
openPalette,
closePalette,
togglePalette,
Expand All @@ -62,12 +63,12 @@ export function CommandPaletteProvider({
<Dialog.Overlay className="fixed inset-0 z-60 bg-black/40 backdrop-blur-sm" />

<Dialog.Content
className={[
className={clsx(
"fixed inset-x-3 top-[18%] z-70 w-auto rounded-2xl border border-white/10",
"bg-foreground/80 text-background shadow-2xl dark:bg-foreground",
"max-h-[70vh]",
"sm:left-1/2 sm:top-50 sm:w-full sm:max-w-xl sm:-translate-x-1/2 sm:inset-x-auto",
].join(" ")}
)}
>
<Dialog.Title className="sr-only">사이트 검색</Dialog.Title>

Expand All @@ -86,13 +87,19 @@ export function CommandPaletteProvider({
</span>
</div>

<Command.List className="max-h-[60vh] overflow-y-auto py-2">
<Command.List
className={clsx(
"max-h-[60vh] overflow-y-auto py-2",
"scrollbar-thin scrollbar-thumb scrollbar-track-transparent",
"hover:scrollbar-thumb-hover",
)}
>
<Command.Empty className="px-4 py-3 text-xs text-background/70">
검색 결과가 없습니다.
</Command.Empty>

<Command.Group className="px-2 pt-1">
{filteredItems.map((item) => (
{displayItems.map((item) => (
<Command.Item
key={item.id}
value={item.label}
Expand Down
4 changes: 3 additions & 1 deletion src/components/common/controls/sort/SortSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type PostSortValue = "latest" | "popular";
interface Props {
value?: PostSortValue;
onChange?: (value: PostSortValue) => void;
onPrefetch?: (value: PostSortValue) => void;
className?: string;
}

Expand All @@ -15,7 +16,7 @@ const SORT_OPTIONS: { value: PostSortValue; label: string }[] = [
{ value: "popular", label: "인기순" },
];

export default function SortSelect({ value, onChange, className }: Props) {
export default function SortSelect({ value, onChange, onPrefetch, className }: Props) {
const [internalValue, setInternalValue] = useState<PostSortValue>("latest");
const currentValue = value ?? internalValue;

Expand All @@ -42,6 +43,7 @@ export default function SortSelect({ value, onChange, className }: Props) {
key={option.value}
type="button"
onClick={() => handleChange(option.value)}
onPointerEnter={() => onPrefetch?.(option.value)}
aria-pressed={isActive}
className={clsx(
"inline-flex items-center justify-center rounded-full px-1 py-1",
Expand Down
21 changes: 17 additions & 4 deletions src/components/common/controls/sort/SortSelectClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,27 @@ export default function SortSelectClient({ value }: SortSelectClientProps) {
const router = useRouter();
const searchParams = useSearchParams();

const handleChange = (next: PostSortValue) => {
const buildUrl = (next: PostSortValue) => {
const params = new URLSearchParams(searchParams.toString());

params.set("sort", next);
params.set("page", "1");
return `?${params.toString()}`;
};

const handleChange = (next: PostSortValue) => {
router.push(buildUrl(next), { scroll: false });
};
Comment on lines +23 to +25
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

handleChange에서 중복 네비게이션 가드가 빠진 부분 확인 필요

이전 코드에서 handleChange 내에 next === currentValue 가드가 있었는지 확인이 필요합니다. 현재는 SortSelect 내부의 handleChange에서 if (next === currentValue) return; 가드가 있어 onChange가 호출되지 않으므로 실질적으로는 안전합니다. 다만, SortSelectClienthandleChange가 직접 호출될 가능성이 있다면 방어 코드를 추가하는 것도 고려해볼 수 있습니다.

🛡️ 선택적 방어 코드
  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.

Suggested change
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.


router.push(`?${params.toString()}`, { scroll: false });
const handlePrefetch = (next: PostSortValue) => {
if (next === value) return;
router.prefetch(buildUrl(next));
};

return <SortSelect value={value} onChange={handleChange} />;
return (
<SortSelect
value={value}
onChange={handleChange}
onPrefetch={handlePrefetch}
/>
);
}
8 changes: 4 additions & 4 deletions src/components/common/icons/FolderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { CSSProperties } from "react";
export type FolderTone = "gray" | "blue" | "pink" | "purple" | "orange" | "darkblue";

const TONE_COLOR: Record<FolderTone, string> = {
gray: "#9c9fa9",
blue: "#0ea5e9",
darkblue: "#357fff",
gray: "#6c6c6c",
blue: "#0da1e6",
darkblue: "#2371f8",
pink: "#ff66b2",
purple: "#8679ec",
purple: "#6454de",
orange: "#f1842a",
};

Expand Down
65 changes: 57 additions & 8 deletions src/hooks/useCommandPaletteInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
type UseCommandPaletteResult = {
open: boolean;
query: string;
items: CommandItem[];
filteredItems: CommandItem[];
displayItems: CommandItem[];
openPalette: () => void;
closePalette: () => void;
togglePalette: () => void;
Expand Down Expand Up @@ -38,6 +37,12 @@ const STATIC_ITEMS: CommandItem[] = [
},
];

function isPostItem(item: CommandItem) {
return (
item.id.startsWith("post-") || (item.href?.startsWith("/posts/") ?? false)
);
}
Comment on lines +40 to +44
Copy link

Choose a reason for hiding this comment

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

🧹 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 buildPostItems(): CommandItem[] {
const posts = getAllPosts({ includeDrafts: false });

Expand Down Expand Up @@ -72,19 +77,53 @@ function buildPostItems(): CommandItem[] {
});
}

const RECENT_KEY = "b_log_cmdk_recent";
const RECENT_LIMIT = 3;

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 [];
}
}
Comment on lines +83 to +92
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

readRecentJSON.parse 결과에 대한 형태(shape) 검증이 없습니다.

JSON.parse(raw) as CommandItem[]는 TypeScript 타입 단언일 뿐, 런타임에 실제 객체 형태를 보장하지 않습니다. sessionStorage 데이터가 코드 변경이나 수동 조작으로 스키마가 달라질 경우, idlabel이 없는 객체가 recentItems에 들어가 렌더링 시 오류가 발생할 수 있습니다.

RECENT_LIMIT이 3이라 영향 범위는 작지만, 간단한 필터링을 추가하면 더 안전합니다.

🛡️ 간단한 shape 검증 예시
 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) : [];
+    if (!Array.isArray(parsed)) return [];
+    return parsed
+      .filter(
+        (x): x is CommandItem =>
+          typeof x === "object" &&
+          x !== null &&
+          typeof x.id === "string" &&
+          typeof x.label === "string",
+      )
+      .slice(0, RECENT_LIMIT);
   } catch {
     return [];
   }
 }
🤖 Prompt for AI Agents
In `@src/hooks/useCommandPaletteInternal.ts` around lines 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.


function writeRecent(items: CommandItem[]) {
sessionStorage.setItem(
RECENT_KEY,
JSON.stringify(items.slice(0, RECENT_LIMIT)),
);
}
Comment on lines +94 to +99
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

searchableText가 sessionStorage에 함께 저장됩니다.

writeRecentCommandItem 전체를 직렬화하므로, 길 수 있는 searchableText 필드도 함께 저장됩니다. RECENT_LIMIT=3이라 당장 문제는 아니지만, 저장 시 불필요한 필드를 제외하면 저장 크기를 줄일 수 있습니다.

♻️ 저장 시 필요 필드만 추출
 function writeRecent(items: CommandItem[]) {
+  const slim = items.slice(0, RECENT_LIMIT).map(({ id, label, hint, href }) => ({
+    id,
+    label,
+    hint,
+    href,
+  }));
   sessionStorage.setItem(
     RECENT_KEY,
-    JSON.stringify(items.slice(0, RECENT_LIMIT)),
+    JSON.stringify(slim),
   );
 }
🤖 Prompt for AI Agents
In `@src/hooks/useCommandPaletteInternal.ts` around lines 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.


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;
}
Comment on lines +83 to +109
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

readRecent에는 SSR 가드가 있지만 writeRecent에는 없습니다.

readRecenttypeof window === "undefined" 체크를 하고 있지만(Line 84), writeRecent(Line 94-99)와 pushRecent(Line 101-109)는 SSR 가드 없이 바로 sessionStorage에 접근합니다.

현재 pushRecenthandleSelect 콜백 안에서만 호출되므로 실제 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.


export default function useCommandPaletteInternal(): UseCommandPaletteResult {
const router = useRouter();
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const [recentItems, setRecentItems] = useState<CommandItem[]>(() =>
readRecent(),
);

const items = useMemo<CommandItem[]>(() => {
const postItems = buildPostItems();
return [...STATIC_ITEMS, ...postItems];
}, []);

const filteredItems = useMemo(() => {
const searchResults = useMemo(() => {
const q = query.trim().toLowerCase();
if (!q) return items;
if (!q) return [];

return items.filter((item) => {
const target = (
Expand All @@ -101,6 +140,14 @@ export default function useCommandPaletteInternal(): UseCommandPaletteResult {
});
}, [items, query]);

const displayItems = useMemo<CommandItem[]>(() => {
const q = query.trim();
if (q.length > 0) return searchResults;

if (recentItems.length === 0) return STATIC_ITEMS;
return [...STATIC_ITEMS, ...recentItems];
}, [query, recentItems, searchResults]);

const openPalette = useCallback(() => {
setQuery("");
setOpen(true);
Expand All @@ -123,13 +170,16 @@ export default function useCommandPaletteInternal(): UseCommandPaletteResult {

const handleSelect = useCallback(
(item: CommandItem) => {
if (isPostItem(item)) {
setRecentItems(pushRecent(item));
}
if (item.href) {
router.push(item.href);
}
setOpen(false);
setQuery("");
},
[router]
[router],
);
Comment on lines 171 to 183
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

handleSelectpushRecent의 side effect와 상태 업데이트가 한 줄에 섞여 있습니다.

Line 174에서 pushRecent(item)은 sessionStorage 쓰기(side effect)를 수행하면서 동시에 새 배열을 반환하고, 그 결과로 setRecentItems를 호출합니다. 동작 자체는 올바르지만, 하나의 표현식에 side effect와 상태 업데이트가 결합되어 있어 읽는 사람이 pushRecent의 역할을 함수 시그니처만으로 파악하기 어렵습니다.

당장 수정이 필요한 문제는 아니고, 현재 동작도 정확합니다. 다만 추후 로직이 복잡해질 경우 분리를 고려해 보시면 좋겠습니다.

🤖 Prompt for AI Agents
In `@src/hooks/useCommandPaletteInternal.ts` around lines 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.


const handleOpenChange = useCallback((next: boolean) => {
Expand Down Expand Up @@ -157,13 +207,12 @@ export default function useCommandPaletteInternal(): UseCommandPaletteResult {
return {
open,
query,
items,
filteredItems,
displayItems,
openPalette,
closePalette,
togglePalette,
setQuery,
handleSelect,
handleOpenChange,
};
}
}
9 changes: 6 additions & 3 deletions src/hooks/usePostLike.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function usePostLike(postId: string): UsePostLikeResult {
const [liked, setLiked] = useState(false);
const [loading, setLoading] = useState(true);

const viewerIdRef = useRef<string | null>(null);
const viewerIdRef = useRef<string>("");

useEffect(() => {
viewerIdRef.current = getOrCreateViewerId();
Expand All @@ -32,7 +32,7 @@ export function usePostLike(postId: string): UsePostLikeResult {
if (!postId) return;

const run = async () => {
const viewerId = viewerIdRef.current ?? getOrCreateViewerId();
const viewerId = viewerIdRef.current;
const { count, liked } = await fetchPostLikeState(postId, viewerId);

setCount(count);
Expand All @@ -44,7 +44,7 @@ export function usePostLike(postId: string): UsePostLikeResult {
}, [postId]);

const toggleLike = async () => {
const viewerId = viewerIdRef.current ?? getOrCreateViewerId();
const viewerId = viewerIdRef.current;
if (!viewerId || !postId) return;

if (liked) {
Expand All @@ -60,6 +60,9 @@ export function usePostLike(postId: string): UsePostLikeResult {
}

const { error } = await addPostLike(postId, viewerId);
if (error) {
console.error("add like error:", error.code, error.message, error.details, error.hint);
}
Comment on lines +63 to +65
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

중복 에러 체크 + 디버그 로깅이 남아 있습니다

Lines 63-65의 if (error) 블록은 Lines 67-76의 if (error) 블록과 중복됩니다. 첫 번째 블록은 return이 없어 두 번째 블록으로 그대로 통과하므로 기능상 버그는 아닙니다. 하지만:

  1. error.code, error.message, error.details, error.hint를 모두 출력하는 것은 개발용 디버그 로깅으로 보입니다. main 브랜치에 머지하기 전에 제거하거나, 두 번째 블록의 console.error에 통합하는 것이 좋겠습니다.
  2. 들여쓰기가 2칸으로 되어 있어 파일 나머지(4칸)와 불일치합니다.
🧹 디버그 로깅 제거 및 기존 에러 핸들링에 통합 제안
     const { error } = await addPostLike(postId, viewerId);
-    if (error) {
-  console.error("add like error:", error.code, error.message, error.details, error.hint);
-}
-
     if (error) {
       if (isDuplicateKeyError(error)) {
         setLiked(true);
         const refreshed = await fetchPostLikeCount(postId);
         setCount(refreshed);
       } else {
-        console.error("좋아요 추가 실패:", error);
+        console.error("좋아요 추가 실패:", error.code, error.message);
       }
       return;
     }
🤖 Prompt for AI Agents
In `@src/hooks/usePostLike.ts` around lines 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.


if (error) {
if (isDuplicateKeyError(error)) {
Expand Down
Loading
Loading