-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat/post-105] ✨ feat: 추천 포스트 기능 추가 & supabse 연동 및 인기순 정렬 추가 #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6840c4a
eb0050c
f80d58e
35890ea
ee736f3
c54854d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,28 +83,29 @@ export default function SiteHeader() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <motion.button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="mr-2 md:hidden" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleSearchClick} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label="검색 열기" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whileTap={{ scale: 0.9, opacity: 0.8 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.08 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Search size={22} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </motion.button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <motion.button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="md:hidden" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={toggleMobileMenu} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label="모바일 메뉴 열기" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whileTap={{ scale: 0.9, opacity: 0.8 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.08 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </motion.button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <section className="flex grow justify-between items-center py-1 md:hidden"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <motion.button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="md:hidden" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={toggleMobileMenu} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label="모바일 메뉴 열기" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whileTap={{ scale: 0.9, opacity: 0.8 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.08 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </motion.button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <motion.button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="md:hidden " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleSearchClick} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| aria-label="검색 열기" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whileTap={{ scale: 0.9, opacity: 0.8 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.08 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Search size={22} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </motion.button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+87
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 중복된 부모 ♻️ 제안 수정 <section className="flex grow justify-between items-center py-1 md:hidden">
<motion.button
type="button"
- className="md:hidden"
onClick={toggleMobileMenu}
aria-label="모바일 메뉴 열기"
whileTap={{ scale: 0.9, opacity: 0.8 }}
transition={{ duration: 0.08 }}
>
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</motion.button>
<motion.button
type="button"
- className="md:hidden "
onClick={handleSearchClick}
aria-label="검색 열기"
whileTap={{ scale: 0.9, opacity: 0.8 }}
transition={{ duration: 0.08 }}
>
<Search size={22} />
</motion.button>
</section>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <AnimatePresence initial={false}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ export interface PostCardProps { | |
| category: string; | ||
| date: string; | ||
| title: string; | ||
| excerpt: string; | ||
| excerpt?: string; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. excerpt가 optional로 변경되었지만 렌더링 시 처리가 없습니다.
excerpt가 없을 때 해당 영역을 숨기거나 플레이스홀더를 표시하는 것이 UX상 더 나을 수 있습니다. 🔧 제안된 수정 (excerpt가 없을 때 숨기기)- <p
- className={clsx(
- "text-gray-800 dark:text-gray-200/80",
- isSmall
- ? "mt-1.5 line-clamp-2 text-[10px] leading-tight"
- : "mt-2 line-clamp-2 text-xs"
- )}
- >
- {excerpt}
- </p>
+ {excerpt && (
+ <p
+ className={clsx(
+ "text-gray-800 dark:text-gray-200/80",
+ isSmall
+ ? "mt-1.5 line-clamp-2 text-[10px] leading-tight"
+ : "mt-2 line-clamp-2 text-xs"
+ )}
+ >
+ {excerpt}
+ </p>
+ )}Also applies to: 119-128 🤖 Prompt for AI Agents |
||
| className?: string; | ||
| size?: "md" | "sm"; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,31 +16,30 @@ export default function RecommendedPostCard({ | |||||||||||||||||||||||||||||||||
| <a | ||||||||||||||||||||||||||||||||||
| href={post.href} | ||||||||||||||||||||||||||||||||||
|
Comment on lines
16
to
17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Next.js 현재 ♻️ 제안된 수정 import clsx from "clsx";
import Image from "next/image";
+import Link from "next/link";
// ...
- <a
- href={post.href}
+ <Link
+ href={post.href}
className={clsx(
// ...
)}
>
{/* content */}
- </a>
+ </Link>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| className={clsx( | ||||||||||||||||||||||||||||||||||
| "group relative block overflow-hidden rounded-xl bg-[#f2f3f6] transition-transform duration-200", | ||||||||||||||||||||||||||||||||||
| "group relative block overflow-hidden rounded-xl bg-[#f2f3f6] transition-transform duration-200", | ||||||||||||||||||||||||||||||||||
| "min-w-55", | ||||||||||||||||||||||||||||||||||
| "border border-gray-300", | ||||||||||||||||||||||||||||||||||
| "hover:scale-[1.02]", | ||||||||||||||||||||||||||||||||||
| "dark:bg-[#050a2a] dark:border-white/20" | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <div className="flex gap-3 py-4 px-3.5"> | ||||||||||||||||||||||||||||||||||
| <div className="h-16 w-16 shrink-0 overflow-hidden rounded-lg bg-muted/20"> | ||||||||||||||||||||||||||||||||||
| {post.thumbnail ? ( | ||||||||||||||||||||||||||||||||||
| <Image | ||||||||||||||||||||||||||||||||||
| src={post.thumbnail} | ||||||||||||||||||||||||||||||||||
| alt="" | ||||||||||||||||||||||||||||||||||
| className="h-full w-full object-cover" | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||
| <div className="h-full w-full bg-gray-300 dark:bg-background/50" /> | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| <div className="flex gap-2.5 p-3"> | ||||||||||||||||||||||||||||||||||
| <div className="shrink-0 overflow-hidden rounded-lg bg-muted/20"> | ||||||||||||||||||||||||||||||||||
| <Image | ||||||||||||||||||||||||||||||||||
| src={post.thumbnail ?? "/post-fallback.png"} | ||||||||||||||||||||||||||||||||||
| alt={post.title} | ||||||||||||||||||||||||||||||||||
| width={64} | ||||||||||||||||||||||||||||||||||
| height={64} | ||||||||||||||||||||||||||||||||||
| className="h-16 w-16 object-cover object-center" | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| <div className="min-w-0 flex-1"> | ||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||
| className={clsx( | ||||||||||||||||||||||||||||||||||
| "text-sm font-semibold text-neutral-900 transition-colors", | ||||||||||||||||||||||||||||||||||
| "group-hover:text-neutral-950", | ||||||||||||||||||||||||||||||||||
| "dark:text-foreground/70", | ||||||||||||||||||||||||||||||||||
| "dark:text-foreground/90", | ||||||||||||||||||||||||||||||||||
| "line-clamp-1" | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
|
|
@@ -52,9 +51,6 @@ export default function RecommendedPostCard({ | |||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| <div className="pointer-events-none absolute -top-24 -right-24 hidden h-48 w-48 rounded-full bg-cyan-500/15 blur-3xl dark:block" /> | ||||||||||||||||||||||||||||||||||
| <div className="pointer-events-none absolute -bottom-24 -left-24 hidden h-48 w-48 rounded-full bg-emerald-500/15 blur-3xl dark:block" /> | ||||||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,7 +8,7 @@ import RecommendedPosts from "@/app/(layout)/posts/[slug]/_components/Recommende | |||||||||||||||||||||||||||||||
| import { adaptVeliteToc } from "@/lib/mdx/toc"; | ||||||||||||||||||||||||||||||||
| import { Metadata } from "next"; | ||||||||||||||||||||||||||||||||
| import { notFound } from "next/navigation"; | ||||||||||||||||||||||||||||||||
| import { posts } from "../../../../../.velite"; | ||||||||||||||||||||||||||||||||
| import { velitePosts } from "@/lib/posts"; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| type PageProps = { | ||||||||||||||||||||||||||||||||
| params: Promise<{ slug: string }>; | ||||||||||||||||||||||||||||||||
|
|
@@ -18,7 +18,7 @@ export async function generateMetadata({ | |||||||||||||||||||||||||||||||
| params, | ||||||||||||||||||||||||||||||||
| }: PageProps): Promise<Metadata> { | ||||||||||||||||||||||||||||||||
| const { slug } = await params; | ||||||||||||||||||||||||||||||||
| const post = posts.find((p) => p.slug === slug); | ||||||||||||||||||||||||||||||||
| const post = velitePosts.find((p) => p.slug === slug); | ||||||||||||||||||||||||||||||||
| if (!post) return {}; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "https://b0o0a.com"; | ||||||||||||||||||||||||||||||||
|
|
@@ -56,7 +56,7 @@ export async function generateMetadata({ | |||||||||||||||||||||||||||||||
| export default async function PostPage({ params }: PageProps) { | ||||||||||||||||||||||||||||||||
| const { slug } = await params; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const post = posts.find((p) => p.slug === slug); | ||||||||||||||||||||||||||||||||
| const post = velitePosts.find((p) => p.slug === slug); | ||||||||||||||||||||||||||||||||
| if (!post) return notFound(); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||
|
|
@@ -90,13 +90,13 @@ export default async function PostPage({ params }: PageProps) { | |||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <div className="pt-10 max-w-55 ml-auto"> | ||||||||||||||||||||||||||||||||
| <RecommendedPosts /> | ||||||||||||||||||||||||||||||||
| <RecommendedPosts post={post} /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </aside> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <div className="mt-10 xl:hidden"> | ||||||||||||||||||||||||||||||||
| <RecommendedPosts /> | ||||||||||||||||||||||||||||||||
| <RecommendedPosts post={post}/> | ||||||||||||||||||||||||||||||||
|
Comment on lines
+93
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 코드 포맷팅 일관성 확인 필요 Line 93과 Line 99에서
사소한 부분이지만, 코드 일관성을 위해 통일하면 좋겠습니다. 🔧 제안된 수정 <div className="mt-10 xl:hidden">
- <RecommendedPosts post={post}/>
+ <RecommendedPosts post={post} />
</div>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </main> | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,25 +60,33 @@ export function CommandPaletteProvider({ | |
| <Dialog.Root open={open} onOpenChange={handleOpenChange}> | ||
| <Dialog.Portal> | ||
| <Dialog.Overlay className="fixed inset-0 z-60 bg-black/40 backdrop-blur-sm" /> | ||
| <Dialog.Content className="fixed left-1/2 top-50 z-70 w-full max-w-xl -translate-x-1/2 rounded-2xl border border-white/10 bg-foreground/70 dark:bg-foreground shadow-2xl"> | ||
|
|
||
| <Dialog.Content | ||
| className={[ | ||
| "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(" ")} | ||
| > | ||
|
Comment on lines
+64
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 반응형 레이아웃 개선이 잘 되었습니다. 모바일에서는 다만 한 가지 확인 사항: Line 68의 🤖 Prompt for AI Agents |
||
| <Dialog.Title className="sr-only">사이트 검색</Dialog.Title> | ||
|
|
||
| <Command className="overflow-hidden rounded-2xl bg-transparent text-sm text-background"> | ||
| <Command className="flex max-h-[70vh] flex-col overflow-hidden rounded-2xl bg-transparent text-sm text-background"> | ||
| <div className="flex items-center gap-2 border-b border-white/10 px-3"> | ||
| <Search className="h-4 w-4 text-background " /> | ||
| <Search className="h-4 w-4 text-background" /> | ||
| <Command.Input | ||
| autoFocus | ||
| value={query} | ||
| onValueChange={setQuery} | ||
| placeholder="무엇을 찾고 계신가요?" | ||
| className="flex-1 bg-transparent py-3 text-sm outline-none placeholder:text-background/70" | ||
| /> | ||
| <span className="rounded bg-foreground/70 dark:bg-background/30 px-1.5 py-0.5 text-[10px] text-white "> | ||
| <span className="hidden rounded bg-foreground/70 px-1.5 py-0.5 text-[10px] text-white sm:inline-block dark:bg-background/30"> | ||
| ESC | ||
| </span> | ||
| </div> | ||
|
|
||
| <Command.List className="max-h-80 overflow-y-auto py-2"> | ||
| <Command.List className="max-h-[60vh] overflow-y-auto py-2"> | ||
| <Command.Empty className="px-4 py-3 text-xs text-background/70"> | ||
| 검색 결과가 없습니다. | ||
| </Command.Empty> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
접근성 개선: 메뉴 상태에 따라
aria-label동적 변경 권장현재
aria-label이 "모바일 메뉴 열기"로 고정되어 있습니다. 메뉴가 열린 상태에서는 "모바일 메뉴 닫기"로 변경되어야 스크린 리더 사용자에게 정확한 정보를 전달할 수 있습니다.♿ 제안 수정
<motion.button type="button" - className="md:hidden" onClick={toggleMobileMenu} - aria-label="모바일 메뉴 열기" + aria-label={isMobileMenuOpen ? "모바일 메뉴 닫기" : "모바일 메뉴 열기"} + aria-expanded={isMobileMenuOpen} whileTap={{ scale: 0.9, opacity: 0.8 }} transition={{ duration: 0.08 }} > {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />} </motion.button>aria-expanded속성을 추가하면 메뉴의 열림/닫힘 상태를 보조 기술에 더 명확하게 전달할 수 있습니다.📝 Committable suggestion
🤖 Prompt for AI Agents