From 71ac8254f738a1d5c67134aa9e471560aae9806c Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Tue, 20 Jan 2026 14:59:39 +1100 Subject: [PATCH 01/13] docs: upgrade gemini setup add SKILL.md files for code-review, git-manager, notion-cms, test-engineer, ui --- .gemini/Agents.md | 45 ------------- .gemini/GEMINI.md | 88 +++++++++++++++++++++++++ .gemini/skills/SKILL.example.md | 23 +++++++ .gemini/skills/code-review/SKILL.md | 47 ++++++++++++++ .gemini/skills/git-manager/SKILL.md | 32 +++++++++ .gemini/skills/notion-cms/SKILL.md | 36 +++++++++++ .gemini/skills/test-engineer/SKILL.md | 30 +++++++++ .gemini/skills/ui/SKILL.md | 25 +++++++ GEMINI.md | 93 --------------------------- 9 files changed, 281 insertions(+), 138 deletions(-) delete mode 100644 .gemini/Agents.md create mode 100644 .gemini/GEMINI.md create mode 100644 .gemini/skills/SKILL.example.md create mode 100644 .gemini/skills/code-review/SKILL.md create mode 100644 .gemini/skills/git-manager/SKILL.md create mode 100644 .gemini/skills/notion-cms/SKILL.md create mode 100644 .gemini/skills/test-engineer/SKILL.md create mode 100644 .gemini/skills/ui/SKILL.md delete mode 100644 GEMINI.md diff --git a/.gemini/Agents.md b/.gemini/Agents.md deleted file mode 100644 index de5fcc6..0000000 --- a/.gemini/Agents.md +++ /dev/null @@ -1,45 +0,0 @@ -# Global AI Instructions - -## 1. 사고 방식 (Reasoning) -- 코드를 작성하기 전, 항상 "생각 프로세스"를 먼저 출력하라. -- 수정 범위가 넓을 경우 반드시 `Plan`을 먼저 제시하고 사용자의 승인을 받아라. - -## 2. 코드 품질 (Code Quality) -- 가독성이 낮은 한 줄 코드(One-liner)보다 명확한 여러 줄 코드를 선호한다. -- 모든 함수에는 간단한 주석(JSDoc 등)을 포함하라. -- 사용되지 않는 변수와 라이브러리는 즉시 삭제하라. - -## 3. 언어 설정 (Language) -- 설명과 답변은 항상 **한국어**로 진행하라. (단, 코드 내의 주석과 변수명은 영어로 작성한다.) - -## 4. 에러 대응 (Error Handling) -- 에러 발생 시 단순히 코드를 고치는 것에 그치지 말고, 왜 에러가 났는지 원인을 한 줄로 요약해라. -- 터미널 명령 실행 결과가 실패(Exit Code 1)라면 즉시 중단하고 대기하라. - -# TypeScript Global Guidelines - -## 1. Type Safety (타입 안전성) -- **NO `any` allowed:** 어떠한 경우에도 `any` 타입을 사용하지 마라. 타입이 불확실할 경우 `unknown`을 사용하고 Type Guard(타입 가드)나 Zod를 통해 검증하라. -- **Strict Mode:** `strict: true`가 켜져 있다고 가정하고, 잠재적인 `null`이나 `undefined`를 철저히 처리하라. -- **Explicit Return Types:** 모든 함수의 반환 타입(Return Type)을 명시적으로 적어라. (추론에 의존하지 말 것) - -## 2. Type Definition Convention (타입 정의 규칙) -- **Interfaces vs Types:** - 확장이 필요한 객체(Object) 정의에는 `interface`를 사용하라. - - 유니온(Union), 인터섹션(Intersection), 튜플(Tuple), 별칭(Alias)에는 `type`을 사용하라. - -## 3. Modern Syntax (최신 문법 선호) -- **Async/Await:** `.then().catch()` 체이닝 대신 항상 `async/await`을 사용하라. -- **Immutability:** 변수는 가능한 `const`로 선언하고, 변경이 필요한 경우에만 `let`을 사용하라. (`var` 금지) -- **Operators:** 안전한 접근을 위해 `?.` (Optional Chaining)와 `??` (Nullish Coalescing)를 적극 활용하라. - -## 4. Error Handling (에러 처리) -- `try/catch` 블록 내에서 잡힌 `error`는 기본적으로 `unknown` 타입임을 인지하고, `if (error instanceof Error)` 체크를 통해 안전하게 메시지를 추출하라. - -## 5. Comments & Documentation (주석) -- 복잡한 비즈니스 로직이나 유틸리티 함수에는 반드시 JSDoc 형태의 주석을 달아라. - ```ts - /** - * Calculates the user's age. - * @param birthDate - The birth date as a Date object. - * @returns The age in years. - */ diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md new file mode 100644 index 0000000..1fa1c9b --- /dev/null +++ b/.gemini/GEMINI.md @@ -0,0 +1,88 @@ +# Project Guidelines & Rules + +## 1. Project Overview & Tech Stack +- **Description:** A modern blog application using a Notion database as a Headless CMS, built with Next.js. +- **Framework:** Next.js 16.1.1 (App Router) +- **UI Library:** React 19 +- **Language:** TypeScript (Strict Mode) +- **Styling:** Tailwind CSS 4, `class-variance-authority`, `clsx`, `lucide-react`. (**Do not create .css files**; use utility classes). +- **CMS/Database:** Notion API (`@notionhq/client`) serving as the primary data source. +- **Testing:** Vitest (Unit), Playwright (E2E), Testing Library. +- **Package Manager:** `pnpm` + +## 2. AI Behavior Guidelines +- **Language:** Always provide explanations and responses in **Korean**. However, write code comments, variable names, and commit messages in **English**. +- **Reasoning:** Before writing code, always output the **"Reasoning Process"**. If the modification is extensive, present a **"Plan"** first and await user approval. +- **Code Quality:** Prefer clear, multi-line code over unreadable one-liners. Remove unused variables and imports immediately. +- **Error Handling:** When an error occurs, do not just fix it; summarize the **root cause** in one line. If a terminal command fails, stop the process immediately. + +## 3. Server vs. Client Strategy (Architecture) +- **Default to Server:** All components are **Server Components** by default. +- **Client Boundary:** Use `'use client'` only when interactivity (`useState`, `onClick`, `useEffect`) is strictly needed. +- **Data Fetching:** - Fetch data directly in Server Components using `async/await`. + - **Do not** use `useEffect` for data fetching unless absolutely necessary. + - **Caching:** Notion API calls must be cached for **6 hours (21600s)** using `unstable_cache` to minimize API usage. +- **Mutations:** Use **Server Actions** for any data mutations or form submissions. Do not use `pages/api` (API Routes). + +## 4. Coding Standards + +### 4.1 Naming & Structure +- **Components:** `PascalCase` (e.g., `PostCard.tsx`). +- **Functions/Variables:** `camelCase` (e.g., `getPostData`). +- **Files:** Match the export name or use `kebab-case` for utility files/folders (e.g., `app/dashboard/settings`). +- **Imports:** Use **absolute imports** (`@/components/...`) instead of relative paths. +- **Co-location:** Group related components in subdirectories (e.g., `src/components/notion/`). + +### 4.2 Type Safety +- **No `any`:** The use of `any` is strictly prohibited. Use `unknown` with Type Guards (or Zod) for validation. +- **Strict Mode:** Assume `strict: true`; handle `null` or `undefined` rigorously. +- **Explicit Returns:** Explicitly define the return type for all functions (do not rely on inference). +- **Definitions:** Use `interface` for extensible objects; use `type` for unions/intersections. + +### 4.3 State Management Hierarchy +1. **Server State:** Prefer fetching fresh data on the server (React Query/SWR if client-side fetching is needed). +2. **URL State:** Store filter/search params in the URL (`useSearchParams`) for shareability. +3. **Global State:** Use Context API or Zustand only if prop drilling exceeds 3 levels. + +### 4.4 Error Handling Pattern +- **Server Actions:** Handle errors gracefully using `try/catch` and return structured objects: + ```ts + try { + // logic + } catch (error) { + if (error instanceof Error) { + console.error(error.message); + } + return { success: false, error: "Friendly error message" }; + } + ``` + +## 5. Workflows to AVOID 🚫 +- No API Routes: Do not use pages/api; use Server Actions. +- No Hardcoded Secrets: Always use process.env. +- No Huge Components: Break down components if they exceed 200 lines. +- No New Packages: Do not install new npm packages without asking for approval first. +- No Prop Drilling: Avoid passing props more than 3 levels deep. + +## 6. Testing Strategy +- Unit Tests (Vitest): Every new utility function or logic-heavy component must have a corresponding .test.ts(x) file in __tests__/unit. +- E2E Tests (Playwright): Major user flows (Home, Search, Post View) must be covered in __tests__/e2e. +- CI/CD: Ensure pnpm test and pnpm test:e2e pass before committing. + +## 7. Environment & Commands +- Environment Variables: NOTION_API_KEY, NOTION_DATABASE_ID. +- Commands: + - pnpm dev: Start dev server (http://localhost:3000). + - pnpm build: Production build. + - pnpm test: Run unit tests. + - pnpm test:e2e: Run E2E tests. + +## 8. Documentation Etiquette +- Code Comments: When writing complex logic, write a brief comment explaining "Why" (intent), not "What" (syntax). +- JSDoc: Mandatory for all utility functions. + +## 9. Commit Messages +- Use imperative verb in commit messages (e.g., "Add feature", "Fix bug"). +- Keep the summary line concise (50 chars or less). +- Use body for additional details if needed. +- Reference issue numbers if applicable (e.g., "Fix #123"). diff --git a/.gemini/skills/SKILL.example.md b/.gemini/skills/SKILL.example.md new file mode 100644 index 0000000..b726e97 --- /dev/null +++ b/.gemini/skills/SKILL.example.md @@ -0,0 +1,23 @@ +--- +name: [스킬 이름] +description: [이 스킬이 무엇을 하는지 한 줄 요약] +triggers: + - [트리거 키워드 1] (예: @notion, @db) + - [트리거 키워드 2] +--- + +# [Skill Name] Context +이 파일은 [특정 작업]을 수행할 때 AI가 따라야 할 가이드라인입니다. + +## 1. Goal (목표) +- 무엇을 달성해야 하는지 명확히 정의 + +## 2. Rules (제약 사항) +- 절대 하지 말아야 할 것들 + +## 3. Tool Usage (도구 사용법) +- 관련된 함수나 스크립트 실행 방법 + +## 4. Examples (예시 - 가장 중요 ⭐) +User: [질문 예시] +AI: [이상적인 답변 예시] diff --git a/.gemini/skills/code-review/SKILL.md b/.gemini/skills/code-review/SKILL.md new file mode 100644 index 0000000..7cffc1b --- /dev/null +++ b/.gemini/skills/code-review/SKILL.md @@ -0,0 +1,47 @@ +--- +name: Senior Code Reviewer +description: Analyze code for performance, security, type safety, and Next.js best practices. +triggers: + - @review + - @cr + - "코드 리뷰해줘" + - "이 코드 문제점 있어?" +--- + +# Code Review Skill + +## 1. Context +You are a Senior Full-Stack Engineer reviewing a Pull Request. Your goal is to catch logical errors, performance bottlenecks, and maintainability issues before they merge. + +## 2. Review Checklist (Priority Order) +1. **Server vs Client Boundary:** + - Is `'use client'` used only when necessary? (Interactivity, Hooks) + - Are sensitive environment variables (`process.env.NOTION_API_KEY`) leaking into Client Components? +2. **Performance (Critical):** + - Are Notion API calls wrapped in `unstable_cache`? (Must be cached for 6 hours/21600s) + - Are images using `next/image` with proper `sizes` and `alt` tags? +3. **Type Safety:** + - Are there any explicit `any` types? (Flag immediately as Critical) + - Are API responses validated (e.g., checking if `properties` exist in Notion response)? +4. **Code Style:** + - Is the component too large (>200 lines)? Suggest splitting. + - Are tailwind classes organized? (Suggest `clsx` or `cn` usage for conditionals). + +## 3. Output Format +Provide feedback in the following format: + +- **🔴 Critical:** (Bugs, Security, `any` type, Infinite loops) +- **🟡 Improvement:** (Performance optimizations, better readability) +- **🟢 Good:** (Praise for good patterns) + +## 4. Example Interaction +**User:** "Review this code: `const data = await fetch(url).then(res => res.json())`" +**AI:** +"**🔴 Critical:** +1. **Promise Handling:** Use `async/await` instead of `.then()` chains for readability. +2. **Type Safety:** The return type is implicit `any`. Define an interface for the response. +3. **Error Handling:** There is no `try/catch` or error checking. + +**🟡 Improvement:** +- If this is a Server Component, consider wrapping `fetch` with `unstable_cache` if the data doesn't change often." + \ No newline at end of file diff --git a/.gemini/skills/git-manager/SKILL.md b/.gemini/skills/git-manager/SKILL.md new file mode 100644 index 0000000..9bfb9ec --- /dev/null +++ b/.gemini/skills/git-manager/SKILL.md @@ -0,0 +1,32 @@ +--- +name: Git Commit Manager +description: Generate semantic commit messages and manage branches. +triggers: + - @git + - "commit message" + - "커밋해줘" +--- + +# Git Manager Skill + +## 1. Commit Message Convention +Follow the **Conventional Commits** format: +- `feat`: New features (blog post rendering, new API route) +- `fix`: Bug fixes (hydration error, layout shift) +- `refactor`: Code changes without logic change +- `chore`: Config changes, dependency updates + +## 2. Workflow +1. Analyze the `git diff` output. +2. Summarize changes in English (imperative mood). +3. If the change is huge, suggest splitting the commit. + +## 3. Example +**Input:** Changed the header background color and added a logo. +**Output:** +```bash +feat: update header design with new logo + +- Change background color to neutral-900 +- Add Logo component to navigation +``` \ No newline at end of file diff --git a/.gemini/skills/notion-cms/SKILL.md b/.gemini/skills/notion-cms/SKILL.md new file mode 100644 index 0000000..4f04ece --- /dev/null +++ b/.gemini/skills/notion-cms/SKILL.md @@ -0,0 +1,36 @@ +--- +name: Notion CMS Handler +description: Manage Notion API interactions, database queries, and data fetching logic. +triggers: + - @notion + - @cms + - "fetch post" + - "노션" +--- + +# Notion CMS Skill + +## 1. Context +You are a Notion API expert for a Next.js blog. You handle data fetching from the Notion Database ID provided in `.env`. + +## 2. Critical Rules +1. **Caching:** Must use `unstable_cache` for all `notion.databases.query` calls. Revalidate time is 21600s. +2. **SDK:** Use `@notionhq/client`. +3. **Transformer:** Always transform the raw Notion response into a simplified `Post` interface before returning it to the component. Do not leak raw Notion blocks to the UI. +4. **Filter:** Only fetch posts where `status` is 'Published'. + +## 3. Code Pattern (Copy this style) +```typescript +import { Client } from "@notionhq/client"; +import { unstable_cache } from "next/cache"; + +const notion = new Client({ auth: process.env.NOTION_API_KEY }); + +export const getPosts = unstable_cache( + async () => { + // ...implementation... + }, + ["posts"], + { revalidate: 21600 } +); +``` diff --git a/.gemini/skills/test-engineer/SKILL.md b/.gemini/skills/test-engineer/SKILL.md new file mode 100644 index 0000000..97c6491 --- /dev/null +++ b/.gemini/skills/test-engineer/SKILL.md @@ -0,0 +1,30 @@ +--- +name: Test Engineer +description: Write and debug Unit (Vitest) and E2E (Playwright) tests. +triggers: + - @test + - "테스트 작성해줘" + - "에러 고쳐줘" +--- + +# Test Engineer Skill + +## 1. Role +You are a QA Engineer specialized in Vitest and Playwright. + +## 2. File Placement Rules +- **Unit Tests:** Place in `__tests__/unit/{filename}.test.tsx` +- **E2E Tests:** Place in `__tests__/e2e/{feature}.spec.ts` + +## 3. Writing Strategy +- **Unit:** Do not test implementation details. Test behaviors (inputs/outputs). Use `screen.getByRole` for accessibility compliance. +- **E2E:** Always capture screenshots on failure (`screenshot: 'only-on-failure'`). Mock external API calls (Notion) using `page.route` to avoid hitting real limits. + +## 4. Command Reference +- Run Unit: `pnpm test` +- Run E2E: `pnpm test:e2e` +- Debug Mode: `pnpm test:ui` + +## 5. Example Interaction +User: "Create a test for the Comment component." +AI: "Created `__tests__/unit/Comment.test.tsx`. I mocked the submission API to prevent network requests. Would you like me to run `pnpm test` now?" diff --git a/.gemini/skills/ui/SKILL.md b/.gemini/skills/ui/SKILL.md new file mode 100644 index 0000000..7bca4a0 --- /dev/null +++ b/.gemini/skills/ui/SKILL.md @@ -0,0 +1,25 @@ +--- +name: UI/UX Designer +description: Design accessible, responsive, and aesthetic components using Tailwind CSS v4. +triggers: + - @ui + - @design + - "디자인해줘" + - "예쁘게 만들어줘" +--- + +# UI/UX Design Skill + +## 1. Context +You are a Product Designer specialized in Tailwind CSS v4 and Headless UI systems. You prioritize "Mobile First", "Dark Mode", and "Accessibility (a11y)". + +## 2. Design Principles +- **Mobile First:** Always write base classes for mobile, then `md:`, `lg:` for larger screens. +- **Dark Mode Support:** Every color class must have a `dark:` counterpart. (e.g., `bg-white dark:bg-neutral-950`). +- **Interaction:** Add states for `hover:`, `focus-visible:` (for keyboard nav), and `active:`. +- **Spacing:** Use consistent spacing (multiples of 4). `p-4`, `gap-6`, `my-8`. + +## 3. Tech Constraints +- **Library:** Use `lucide-react` for icons. +- **Utils:** Use `cn` (from `lib/utils`) for class merging. +- **Tailwind v4:** Do not use `config` based arbitrary values if possible. Use standard utility classes. diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index 3200acc..0000000 --- a/GEMINI.md +++ /dev/null @@ -1,93 +0,0 @@ -# Project Memory -## Project Overview -This is a modern blog application built with Next.js, React, and Tailwind CSS. The blog posts are fetched from a Notion database, which serves as a headless CMS. The application supports light and dark modes, and it has a comprehensive testing setup with Vitest for unit/integration tests and Playwright for end-to-end tests. - -## Project Architecture -- **Framework**: Next.js 16.1.1 (App Router) -- **UI Library:** React 19 -- **Language**: TypeScript -- **Styling**: Tailwind CSS 4, `class-variance-authority`, `clsx`, `lucide-react` -- **CMS/Database**: Notion API (`@notionhq/client`) -- **Testing**: Vitest (Unit), Playwright (E2E), Testing Library - -## Project Structure -* `src/app`: Next.js App Router pages and layouts. -* `src/components`: UI components. Group related components in subdirectories (e.g., `src/components/notion/`). -* `src/lib`: Utility functions, API clients, and shared logic. -* `__tests__`: All test files, mirroring the `src` structure. - -## Coding Standards -- **Language:** Use TypeScript for all code. Avoid `any` type; define proper interfaces or types. -- **React:** Use functional components with Hooks. Leverage React 19 features where appropriate. -- **Style Guide**: Follow ESLint configuration (`eslint.config.mjs`). -- **Formatting**: Prettier (implied), 2 spaces indentation. -- **Components**: Use functional components. Place reusable components in `src/components`. -- **CSS**: Use Tailwind CSS 4. Follow the utility-first approach. Use `cn` (from `lib/utils.ts`) for conditional classes. Avoid inline styles. -- **State Management:** Prefer React's built-in state (useState, useReducer, Context API) unless a more complex solution is required. -- **Error Handling:** Implement proper error handling using try-catch blocks and error boundaries. -- **Performance:** Optimize performance by using React's built-in features (e.g., memoization, lazy loading, code splitting). - - **Caching:** Notion API calls are cached for 6 hours (21600s) using `unstable_cache` to minimize API usage and improve load times. -- **Accessibility:** Follow WCAG 2.1 guidelines for accessibility. -- **Version Control:** Use Git for version control. -- **Code Review:** Use GitHub for code review. -- **Deployment:** Use Vercel for deployment. -- **CI/CD:** Use GitHub Actions for CI/CD. -- **Monitoring:** Use Sentry for monitoring. -- **Analytics:** Use Google Analytics (GA4) for analytics. -- **SEO:** Dynamic metadata, `sitemap.xml`, and `robots.txt` are generated automatically. -- **RSS:** An RSS 2.0 feed is available at `/feed.xml`. -- **Localization:** Use i18n for localization. - -## Naming Conventions -- **Components:** PascalCase (e.g., `PostCard.tsx`). -- **Functions & Variables:** camelCase (e.g., `getPostData`). -- **Files:** Match the export name or use kebab-case for utility files. -- **Types/Interfaces:** PascalCase, prefixed with `T` or `I` if it helps clarity, though simple PascalCase is preferred. - -## Bash Commands -### Installation -- Command: `pnpm install` - -### Running -- Command: `pnpm dev` -- The application will be available at `http://localhost:3000`. - -### Build -- Command: `pnpm build` -- Output: `.next` directory - -### Test -- Unit Tests: `pnpm test` (Vitest) -- E2E Tests: `pnpm test:e2e` (Playwright) -- UI Mode: `pnpm test:ui` - -### Deploy -- Platform: Vercel (Recommended for Next.js) -- Build Command: `next build` -- Install Command: `pnpm install` - -## Testing Strategy -- **Unit Tests:** Every new utility function or logic-heavy component must have a corresponding `.test.ts` or `.test.tsx` file in `__tests__/unit`. -- **E2E Tests:** Major user flows (Home, Search, Post Viewing) must be covered by Playwright tests in `__tests__/e2e`. -- **CI/CD:** Ensure all tests pass (`pnpm test` and `pnpm test:e2e`) before committing significant changes. - -## Repository Etiquette -- **Branching**: Use feature branches (e.g., `feature/new-component`, `fix/bug-fix`). -- **Commit Message Format**: Use [Conventional Commits](https://www.conventionalcommits.org/): - * `feat:` A new feature. - * `fix:` A bug fix. - * `docs:` Documentation changes. - * `style:` Changes that do not affect the meaning of the code (white-space, formatting, etc). - * `refactor:` A code change that neither fixes a bug nor adds a feature. - * `test:` Adding missing tests or correcting existing tests. - * `chore:` Changes to the build process or auxiliary tools and libraries. -- **Commits**: Use conventional commits (e.g., `feat: add header`, `fix: resolve crash`). -- **Merge**: Squash and merge PRs to keep history clean. -- **Rebase**: Rebase on `main` before merging if necessary. - -## Unexpected Behaviors / Warnings -- (Add any known project-specific quirks here) - -## Common Libraries -- `clsx` & `tailwind-merge`: For conditional class names. -- `lucide-react`: For icons. From dde423d0367d8c4a68c6b6f4a2c7353888a87785 Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Sat, 24 Jan 2026 13:53:14 +1100 Subject: [PATCH 02/13] refector: change folder name .gemini to .agent --- {.gemini => .agent}/GEMINI.md | 27 +++---- {.gemini => .agent}/skills/SKILL.example.md | 0 .../skills/code-review/SKILL.md | 0 .../skills/git-manager/SKILL.md | 20 +++-- .agent/skills/git-manager/cheatsheet.md | 81 +++++++++++++++++++ .../skills/notion-cms/SKILL.md | 17 +++- .../skills/test-engineer/SKILL.md | 1 + {.gemini => .agent}/skills/ui/SKILL.md | 0 .agent/skills/workflows/SKILL.md | 55 +++++++++++++ 9 files changed, 179 insertions(+), 22 deletions(-) rename {.gemini => .agent}/GEMINI.md (82%) rename {.gemini => .agent}/skills/SKILL.example.md (100%) rename {.gemini => .agent}/skills/code-review/SKILL.md (100%) rename {.gemini => .agent}/skills/git-manager/SKILL.md (57%) create mode 100644 .agent/skills/git-manager/cheatsheet.md rename {.gemini => .agent}/skills/notion-cms/SKILL.md (62%) rename {.gemini => .agent}/skills/test-engineer/SKILL.md (93%) rename {.gemini => .agent}/skills/ui/SKILL.md (100%) create mode 100644 .agent/skills/workflows/SKILL.md diff --git a/.gemini/GEMINI.md b/.agent/GEMINI.md similarity index 82% rename from .gemini/GEMINI.md rename to .agent/GEMINI.md index 1fa1c9b..6eca5c4 100644 --- a/.gemini/GEMINI.md +++ b/.agent/GEMINI.md @@ -15,6 +15,16 @@ - **Reasoning:** Before writing code, always output the **"Reasoning Process"**. If the modification is extensive, present a **"Plan"** first and await user approval. - **Code Quality:** Prefer clear, multi-line code over unreadable one-liners. Remove unused variables and imports immediately. - **Error Handling:** When an error occurs, do not just fix it; summarize the **root cause** in one line. If a terminal command fails, stop the process immediately. +- **Documentation:** Every works must be documented in /docs folder. There are several types of documents: + - **/architectures** (for used architecture of the project) + - **/fixes** (for fixing bugs) + - **/plans** (for planning features) + - **/tests** (for testing features) + - ... (if you need to add more documents, add them and save them in /docs folder) + - **CHANGELOG.md** (always update this file when you make a change) +- **Workflow Automation Rule:** + - When a task is deemed complete or the user indicates completion, do not commit immediately; **always initiate the `@finish` workflow.** + - Enforce the **[Code Review] -> [Request Approval] -> [Commit]** procedure. Do not suggest `git commit` directly. ## 3. Server vs. Client Strategy (Architecture) - **Default to Server:** All components are **Server Components** by default. @@ -64,25 +74,14 @@ - No New Packages: Do not install new npm packages without asking for approval first. - No Prop Drilling: Avoid passing props more than 3 levels deep. -## 6. Testing Strategy -- Unit Tests (Vitest): Every new utility function or logic-heavy component must have a corresponding .test.ts(x) file in __tests__/unit. -- E2E Tests (Playwright): Major user flows (Home, Search, Post View) must be covered in __tests__/e2e. -- CI/CD: Ensure pnpm test and pnpm test:e2e pass before committing. - -## 7. Environment & Commands -- Environment Variables: NOTION_API_KEY, NOTION_DATABASE_ID. +## 6. Environment & Commands +- Environment Variables: NOTION_API_KEY, NOTION_POSTS_DATA_SOURCE_ID, NOTION_COMMENTS_DATA_SOURCE_ID. - Commands: - pnpm dev: Start dev server (http://localhost:3000). - pnpm build: Production build. - pnpm test: Run unit tests. - pnpm test:e2e: Run E2E tests. -## 8. Documentation Etiquette +## 7. Documentation Etiquette - Code Comments: When writing complex logic, write a brief comment explaining "Why" (intent), not "What" (syntax). - JSDoc: Mandatory for all utility functions. - -## 9. Commit Messages -- Use imperative verb in commit messages (e.g., "Add feature", "Fix bug"). -- Keep the summary line concise (50 chars or less). -- Use body for additional details if needed. -- Reference issue numbers if applicable (e.g., "Fix #123"). diff --git a/.gemini/skills/SKILL.example.md b/.agent/skills/SKILL.example.md similarity index 100% rename from .gemini/skills/SKILL.example.md rename to .agent/skills/SKILL.example.md diff --git a/.gemini/skills/code-review/SKILL.md b/.agent/skills/code-review/SKILL.md similarity index 100% rename from .gemini/skills/code-review/SKILL.md rename to .agent/skills/code-review/SKILL.md diff --git a/.gemini/skills/git-manager/SKILL.md b/.agent/skills/git-manager/SKILL.md similarity index 57% rename from .gemini/skills/git-manager/SKILL.md rename to .agent/skills/git-manager/SKILL.md index 9bfb9ec..a886e83 100644 --- a/.gemini/skills/git-manager/SKILL.md +++ b/.agent/skills/git-manager/SKILL.md @@ -3,29 +3,39 @@ name: Git Commit Manager description: Generate semantic commit messages and manage branches. triggers: - @git + - @wt + - @cm - "commit message" - "커밋해줘" --- # Git Manager Skill -## 1. Commit Message Convention +## 1. Context & Resources +- **Role:** You are an expert Git manager utilizing **Worktrees** for parallel development. +- **Reference:** For specific commands (especially Worktree & Bare Repo setup), **ALWAYS refer to `./cheatsheet.md`** located in the same directory. + +## 2. Commit Message Convention Follow the **Conventional Commits** format: - `feat`: New features (blog post rendering, new API route) - `fix`: Bug fixes (hydration error, layout shift) +- `docs`: Documentation changes +- `style`: Code changes without logic change - `refactor`: Code changes without logic change +- `test`: Test changes - `chore`: Config changes, dependency updates -## 2. Workflow +## 3. Workflow 1. Analyze the `git diff` output. 2. Summarize changes in English (imperative mood). 3. If the change is huge, suggest splitting the commit. - -## 3. Example +4. Consult `./cheatsheet.md` for exact syntax. + +## 4. Example **Input:** Changed the header background color and added a logo. **Output:** ```bash -feat: update header design with new logo +style: update header design with new logo - Change background color to neutral-900 - Add Logo component to navigation diff --git a/.agent/skills/git-manager/cheatsheet.md b/.agent/skills/git-manager/cheatsheet.md new file mode 100644 index 0000000..de59287 --- /dev/null +++ b/.agent/skills/git-manager/cheatsheet.md @@ -0,0 +1,81 @@ +# 명령어,설명 + +```bash +git config --global user.name "이름" # 사용자 이름 설정 +git config --global user.email "이메일" # 사용자 이메일 설정 +git init # 현재 디렉토리를 Git 저장소로 초기화 +git clone --bare .bare # 워크트리 사용을 위한 Bare Clone +``` + +## 워크트리 +워크트리 사용을 위한 명령어 +root 폴더에 .bare 폴더를 생성하고 그 안에 워크트리 폴더를 생성 +**항상 root 폴더에서 명령어를 실행해야 함** + +```bash + +git -C .bare worktree list # 현재 생성된 모든 워크트리(작업 폴더) 목록 확인 +git -C .bare worktree add ../<폴더명> <브랜치명> # 새 브랜치를 따면서 새 폴더 생성 +git -C .bare worktree add ../<폴더명> # 기존 브랜치를 새 폴더로 체크아웃 +git -C .bare worktree remove <폴더명> # 작업 폴더 삭제 (Git 연결 해제) +git -C .bare worktree prune # 폴더를 강제 삭제(rm -rf)했을 때 찌꺼기 정리 +git -C .bare worktree move <구폴더> ../<신폴더> # 워크트리 폴더 경로/이름 변경 +``` + +## 커밋 + +```bash +git status # 현재 파일 상태(변경됨, 스테이징됨) 확인 +git add . # 모든 변경사항을 스테이징 영역(Staging Area)에 추가 +git add <파일> # 특정 파일만 스테이징 +git commit -m "메시지" # 스테이징된 변경사항 확정(저장) +git commit --amend # 방금 한 커밋의 메시지나 파일 수정 (덮어쓰기) +``` + +## 브랜치 + +```bash +git branch # 로컬 브랜치 목록 확인 +git branch -r # 원격 브랜치 목록 확인 +git branch <이름> # 새 브랜치 생성 (이동은 안 함) +git branch -m <새이름> # 현재 브랜치 이름 변경 +git branch -d <이름> # 브랜치 삭제 (병합된 것만) +git branch -D <이름> # 브랜치 강제 삭제 +git switch <브랜치> # 해당 브랜치로 이동 +git switch -c <이름> # 새 브랜치 만들면서 이동 +``` + +## 원격 저장소 + +```bash +git fetch # 원격 저장소의 최신 이력만 가져옴 (병합 X) +git pull origin <브랜치> # 원격 내용을 가져와서 합침 (Fetch + Merge) +git push origin <브랜치> # 내 커밋을 원격 저장소에 올림 +git push -u origin <브랜치> # 업스트림 설정 (다음부턴 git push만 해도 됨) +git merge <브랜치> # 다른 브랜치를 현재 브랜치로 합침 +git rebase <브랜치> # 내 브랜치의 시작점을 타겟 브랜치 끝으로 옮김 (깔끔한 히스토리) +``` + +## 복구 + +```bash +git restore <파일> # 작업 중인 파일 변경사항 취소 (마지막 커밋 상태로) +git restore --staged <파일> # git add 취소 (스테이징 내리기) +git reset --soft HEAD~1 # 커밋은 취소하되, 변경사항은 스테이징 상태로 보존 +git reset --hard HEAD~1 # 커밋과 변경사항 모두 날려버림 (복구 불가) +git revert <커밋ID> # 특정 커밋의 내용을 반대로 수행하는 새 커밋 생성 (협업 시 안전) +``` + +## 임시 저장 +임시 저장 하지 않음. 워크트리 사용. + +## 히스토리 + +```bash +git log # 커밋 히스토리 조회 +git log --oneline --graph # 히스토리를 그래프 형태로 한 줄 요약해서 보기 +git diff # 스테이징되지 않은 변경사항 확인 +git show <커밋ID> # 특정 커밋의 상세 변경 내용 확인 +git blame <파일> # "파일의 각 라인을 누가, 언제 수정했는지 범인(?) 찾기" +git bisect # 이진 탐색으로 버그가 발생한 커밋 찾아내기 +``` diff --git a/.gemini/skills/notion-cms/SKILL.md b/.agent/skills/notion-cms/SKILL.md similarity index 62% rename from .gemini/skills/notion-cms/SKILL.md rename to .agent/skills/notion-cms/SKILL.md index 4f04ece..6b4af05 100644 --- a/.gemini/skills/notion-cms/SKILL.md +++ b/.agent/skills/notion-cms/SKILL.md @@ -14,10 +14,12 @@ triggers: You are a Notion API expert for a Next.js blog. You handle data fetching from the Notion Database ID provided in `.env`. ## 2. Critical Rules -1. **Caching:** Must use `unstable_cache` for all `notion.databases.query` calls. Revalidate time is 21600s. +1. **Caching:** Must use `unstable_cache` for all `notion.databases.query` calls. Revalidate time is 3600s. 2. **SDK:** Use `@notionhq/client`. 3. **Transformer:** Always transform the raw Notion response into a simplified `Post` interface before returning it to the component. Do not leak raw Notion blocks to the UI. 4. **Filter:** Only fetch posts where `status` is 'Published'. +5. **Document:** Read the Notion API documentation for the latest information on properties and methods. https://developers.notion.com/reference/intro +6. **Postman:** Use Postman to test the Notion API. https://postman.com/notionhq/workspace/notion-s-api-workspace ## 3. Code Pattern (Copy this style) ```typescript @@ -28,9 +30,18 @@ const notion = new Client({ auth: process.env.NOTION_API_KEY }); export const getPosts = unstable_cache( async () => { - // ...implementation... + const response = await notion.databases.query({ + database_id: process.env.NOTION_POSTS_DATA_SOURCE_ID, + filter: { + property: "", + select: { + equals: "", + }, + }, + }); + return response.results; }, ["posts"], - { revalidate: 21600 } + { revalidate: 3600 } ); ``` diff --git a/.gemini/skills/test-engineer/SKILL.md b/.agent/skills/test-engineer/SKILL.md similarity index 93% rename from .gemini/skills/test-engineer/SKILL.md rename to .agent/skills/test-engineer/SKILL.md index 97c6491..06516a7 100644 --- a/.gemini/skills/test-engineer/SKILL.md +++ b/.agent/skills/test-engineer/SKILL.md @@ -19,6 +19,7 @@ You are a QA Engineer specialized in Vitest and Playwright. ## 3. Writing Strategy - **Unit:** Do not test implementation details. Test behaviors (inputs/outputs). Use `screen.getByRole` for accessibility compliance. - **E2E:** Always capture screenshots on failure (`screenshot: 'only-on-failure'`). Mock external API calls (Notion) using `page.route` to avoid hitting real limits. +- **CI/CD:** Ensure `pnpm test` and `pnpm test:e2e` pass before committing. ## 4. Command Reference - Run Unit: `pnpm test` diff --git a/.gemini/skills/ui/SKILL.md b/.agent/skills/ui/SKILL.md similarity index 100% rename from .gemini/skills/ui/SKILL.md rename to .agent/skills/ui/SKILL.md diff --git a/.agent/skills/workflows/SKILL.md b/.agent/skills/workflows/SKILL.md new file mode 100644 index 0000000..feb6bc2 --- /dev/null +++ b/.agent/skills/workflows/SKILL.md @@ -0,0 +1,55 @@ +--- +name: Task Finisher Workflow +description: Automate the Code Review -> Confirmation -> Git Commit pipeline. +triggers: + - @finish + - @done + - "작업 완료" + - "마무리해줘" +--- + +# Task Finisher Workflow + +## 1. Context +You are the **Workflow Orchestrator**. Your goal is to ensure high code quality before finalizing any changes. You act as a bridge between the `@review` skill and the `@git` skill. + +## 2. Execution Steps (Follow Strictly) + +### Step 1: Pre-Commit Review (Invoke Code-Reviewer) +1. Read all modified files (`git diff`). +2. **Act as the `@review` agent:** + - Check for `any` types, missing `unstable_cache`, and logic errors. + - Check if strictly necessary `test` files exist (if logic was changed). +3. **Report:** + - If **Critical Issues** are found 🔴 -> **STOP** and ask the user to fix them. + - If only **Minor Issues** or **Good** 🟢 -> Present the summary and ask: + > "리뷰 결과가 양호합니다. 커밋 메시지를 생성하고 진행할까요?" + +### Step 2: User Confirmation (Wait) +- **DO NOT** generate the commit command yet. +- Wait for the user to reply "Yes", "Proceed", or "ㅇㅇ". + +### Step 3: Commit Generation (Invoke Git-Manager) +1. Once confirmed, **Act as the `@git` agent:** + - Generate a specific `git commit` command following Conventional Commits. + - Provide the command for the user to run (or run it if permissions allow). + +## 3. Example Interaction + +**User:** `@finish` (Auth login implementation) + +**AI:** +"**🔎 Code Review Summary:** +- 🟢 **Security:** `process.env` is properly used. +- 🟡 **Suggestion:** Consider extracting the validation logic to `lib/auth.ts`. +- ✅ **Tests:** Unit tests passed. + +코드가 안정적으로 보입니다. 커밋을 진행할까요?" + +**User:** "응 진행해" + +**AI:** +"**🚀 Git Commit:** +```bash +git add . +git commit -m "feat: implement auth login with validation" From 7b599448a69d74da4e5da6cb5ccd59bb26620342 Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Sat, 24 Jan 2026 15:25:38 +1100 Subject: [PATCH 03/13] feat: add vercel analysis component --- package.json | 1 + pnpm-lock.yaml | 34 ++++++++++++++++++++++++++++++++++ src/app/[locale]/layout.tsx | 8 +++++--- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8f07629..d8f2a09 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@next/third-parties": "^16.1.1", "@notionhq/client": "^5.6.0", + "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccd4d17..8fb897b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@notionhq/client': specifier: ^5.6.0 version: 5.6.0 + '@vercel/analytics': + specifier: ^1.6.1 + version: 1.6.1(next@16.1.1(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) '@vercel/speed-insights': specifier: ^1.3.1 version: 1.3.1(next@16.1.1(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3) @@ -1325,6 +1328,32 @@ packages: cpu: [x64] os: [win32] + '@vercel/analytics@1.6.1': + resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} + peerDependencies: + '@remix-run/react': ^2 + '@sveltejs/kit': ^1 || ^2 + next: '>= 13' + react: ^18 || ^19 || ^19.0.0-rc + svelte: '>= 4' + vue: ^3 + vue-router: ^4 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@sveltejs/kit': + optional: true + next: + optional: true + react: + optional: true + svelte: + optional: true + vue: + optional: true + vue-router: + optional: true + '@vercel/speed-insights@1.3.1': resolution: {integrity: sha512-PbEr7FrMkUrGYvlcLHGkXdCkxnylCWePx7lPxxq36DNdfo9mcUjLOmqOyPDHAOgnfqgGGdmE3XI9L/4+5fr+vQ==} peerDependencies: @@ -4099,6 +4128,11 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@vercel/analytics@1.6.1(next@16.1.1(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': + optionalDependencies: + next: 16.1.1(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + '@vercel/speed-insights@1.3.1(next@16.1.1(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)': optionalDependencies: next: 16.1.1(@babel/core@7.28.5)(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 1a7894b..72d3c0a 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -6,6 +6,7 @@ import { notFound } from 'next/navigation'; import { routing } from '@/i18n/routing'; import { SpeedInsights } from "@vercel/speed-insights/next" import { GoogleTagManager } from '@next/third-parties/google'; +import { Analytics } from "@vercel/analytics/next" import { ThemeProvider } from "@/components/utils/ThemeProvider"; import { GoogleAnalytics } from "@/components/delegator/GoogleAnalytics"; @@ -79,9 +80,9 @@ export default async function LocaleLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} suppressHydrationWarning > + + - - {children} - + + ); From 212b13699717183a1bde4db44c0b833064f845fa Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Sun, 25 Jan 2026 11:07:41 +1100 Subject: [PATCH 04/13] fix: matching env convention --- .env.example | 7 ++++--- .github/workflows/ci-cd.yml | 5 ++++- README.md | 7 ++++--- __tests__/unit/components/GoogleAnalytics.test.tsx | 2 +- src/app/[locale]/layout.tsx | 11 +++++------ src/components/delegator/GoogleAnalytics.tsx | 2 +- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index a787316..36b1afd 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ NEXT_PUBLIC_SITE_URL=http://localhost:3000 NOTION_API_KEY= NOTION_POSTS_DATA_SOURCE_ID= NOTION_COMMENTS_DATA_SOURCE_ID= -NEXT_PUBLIC_GA_ID= -NEXT_PUBLIC_GTM_ID= -NEXT_PUBLIC_GOOGLE_ADSENSE_ID= +GA_ID= +GTM_ID= +GOOGLE_ADSENSE_ACCOUNT= +GOOGLE_ADSENSE_ID= \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 47aca8c..d10a8b7 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -11,7 +11,10 @@ env: NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }} NOTION_POSTS_DATA_SOURCE_ID: ${{ secrets.NOTION_POSTS_DATA_SOURCE_ID }} NOTION_COMMENTS_DATA_SOURCE_ID: ${{ secrets.NOTION_COMMENTS_DATA_SOURCE_ID }} - NEXT_PUBLIC_GA_ID: ${{ secrets.NEXT_PUBLIC_GA_ID }} + GA_ID: ${{ secrets.GA_ID }} + GTM_ID: ${{ secrets.GTM_ID }} + GOOGLE_ADSENSE_ACCOUNT: ${{ secrets.GOOGLE_ADSENSE_ACCOUNT }} + GOOGLE_ADSENSE_ID: ${{ secrets.GOOGLE_ADSENSE_ID }} VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} diff --git a/README.md b/README.md index b82e0ca..004b5c9 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,10 @@ NEXT_PUBLIC_SITE_URL=http://localhost:3000 # Google Services (Optional) - NEXT_PUBLIC_GA_ID=G-XXXXXX - NEXT_PUBLIC_GTM_ID=GTM-XXXXXX - NEXT_PUBLIC_GOOGLE_ADSENSE_ID=ca-pub-XXXXXXXXXXXXXXXX + GA_ID=G-XXXXXX + GTM_ID=GTM-XXXXXX + GOOGLE_ADSENSE_ACCOUNT=ca-pub-XXXXXXXXXXXXXXXX + GOOGLE_ADSENSE_ID=pub-XXXXXXXXXXXXXXXX ``` 4. **Run development server** diff --git a/__tests__/unit/components/GoogleAnalytics.test.tsx b/__tests__/unit/components/GoogleAnalytics.test.tsx index c358707..d6b34a5 100644 --- a/__tests__/unit/components/GoogleAnalytics.test.tsx +++ b/__tests__/unit/components/GoogleAnalytics.test.tsx @@ -10,7 +10,7 @@ describe('GoogleAnalytics Component', () => { }); it('should render without errors when GA ID is set', () => { - // Note: GoogleAnalytics reads from process.env.NEXT_PUBLIC_GA_ID at build time + // Note: GoogleAnalytics reads from process.env.GA_ID at build time // This test just ensures the component renders without runtime errors const { container } = render(); expect(container).toBeDefined(); diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 72d3c0a..b5dbbab 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -5,13 +5,12 @@ import { getMessages } from 'next-intl/server'; import { notFound } from 'next/navigation'; import { routing } from '@/i18n/routing'; import { SpeedInsights } from "@vercel/speed-insights/next" -import { GoogleTagManager } from '@next/third-parties/google'; +import { GoogleTagManager, GoogleAnalytics } from '@next/third-parties/google'; import { Analytics } from "@vercel/analytics/next" import { ThemeProvider } from "@/components/utils/ThemeProvider"; -import { GoogleAnalytics } from "@/components/delegator/GoogleAnalytics"; -import { GoogleAdSense } from "@/components/delegator/GoogleAdSense"; import { ErrorBoundary } from "@/components/ErrorBoundary"; +import { GoogleAdSense } from "@/components/delegator/GoogleAdSense"; import "../globals.css"; @@ -75,13 +74,13 @@ export default async function LocaleLayout({ return ( - + - - + + Date: Sat, 24 Jan 2026 13:53:14 +1100 Subject: [PATCH 05/13] refector: change folder name .gemini to .agent --- .gemini/GEMINI.md | 87 --------------------------- .gemini/skills/SKILL.example.md | 23 ------- .gemini/skills/code-review/SKILL.md | 47 --------------- .gemini/skills/git-manager/SKILL.md | 42 ------------- .gemini/skills/notion-cms/SKILL.md | 36 ----------- .gemini/skills/test-engineer/SKILL.md | 31 ---------- .gemini/skills/ui/SKILL.md | 25 -------- 7 files changed, 291 deletions(-) delete mode 100644 .gemini/GEMINI.md delete mode 100644 .gemini/skills/SKILL.example.md delete mode 100644 .gemini/skills/code-review/SKILL.md delete mode 100644 .gemini/skills/git-manager/SKILL.md delete mode 100644 .gemini/skills/notion-cms/SKILL.md delete mode 100644 .gemini/skills/test-engineer/SKILL.md delete mode 100644 .gemini/skills/ui/SKILL.md diff --git a/.gemini/GEMINI.md b/.gemini/GEMINI.md deleted file mode 100644 index 6eca5c4..0000000 --- a/.gemini/GEMINI.md +++ /dev/null @@ -1,87 +0,0 @@ -# Project Guidelines & Rules - -## 1. Project Overview & Tech Stack -- **Description:** A modern blog application using a Notion database as a Headless CMS, built with Next.js. -- **Framework:** Next.js 16.1.1 (App Router) -- **UI Library:** React 19 -- **Language:** TypeScript (Strict Mode) -- **Styling:** Tailwind CSS 4, `class-variance-authority`, `clsx`, `lucide-react`. (**Do not create .css files**; use utility classes). -- **CMS/Database:** Notion API (`@notionhq/client`) serving as the primary data source. -- **Testing:** Vitest (Unit), Playwright (E2E), Testing Library. -- **Package Manager:** `pnpm` - -## 2. AI Behavior Guidelines -- **Language:** Always provide explanations and responses in **Korean**. However, write code comments, variable names, and commit messages in **English**. -- **Reasoning:** Before writing code, always output the **"Reasoning Process"**. If the modification is extensive, present a **"Plan"** first and await user approval. -- **Code Quality:** Prefer clear, multi-line code over unreadable one-liners. Remove unused variables and imports immediately. -- **Error Handling:** When an error occurs, do not just fix it; summarize the **root cause** in one line. If a terminal command fails, stop the process immediately. -- **Documentation:** Every works must be documented in /docs folder. There are several types of documents: - - **/architectures** (for used architecture of the project) - - **/fixes** (for fixing bugs) - - **/plans** (for planning features) - - **/tests** (for testing features) - - ... (if you need to add more documents, add them and save them in /docs folder) - - **CHANGELOG.md** (always update this file when you make a change) -- **Workflow Automation Rule:** - - When a task is deemed complete or the user indicates completion, do not commit immediately; **always initiate the `@finish` workflow.** - - Enforce the **[Code Review] -> [Request Approval] -> [Commit]** procedure. Do not suggest `git commit` directly. - -## 3. Server vs. Client Strategy (Architecture) -- **Default to Server:** All components are **Server Components** by default. -- **Client Boundary:** Use `'use client'` only when interactivity (`useState`, `onClick`, `useEffect`) is strictly needed. -- **Data Fetching:** - Fetch data directly in Server Components using `async/await`. - - **Do not** use `useEffect` for data fetching unless absolutely necessary. - - **Caching:** Notion API calls must be cached for **6 hours (21600s)** using `unstable_cache` to minimize API usage. -- **Mutations:** Use **Server Actions** for any data mutations or form submissions. Do not use `pages/api` (API Routes). - -## 4. Coding Standards - -### 4.1 Naming & Structure -- **Components:** `PascalCase` (e.g., `PostCard.tsx`). -- **Functions/Variables:** `camelCase` (e.g., `getPostData`). -- **Files:** Match the export name or use `kebab-case` for utility files/folders (e.g., `app/dashboard/settings`). -- **Imports:** Use **absolute imports** (`@/components/...`) instead of relative paths. -- **Co-location:** Group related components in subdirectories (e.g., `src/components/notion/`). - -### 4.2 Type Safety -- **No `any`:** The use of `any` is strictly prohibited. Use `unknown` with Type Guards (or Zod) for validation. -- **Strict Mode:** Assume `strict: true`; handle `null` or `undefined` rigorously. -- **Explicit Returns:** Explicitly define the return type for all functions (do not rely on inference). -- **Definitions:** Use `interface` for extensible objects; use `type` for unions/intersections. - -### 4.3 State Management Hierarchy -1. **Server State:** Prefer fetching fresh data on the server (React Query/SWR if client-side fetching is needed). -2. **URL State:** Store filter/search params in the URL (`useSearchParams`) for shareability. -3. **Global State:** Use Context API or Zustand only if prop drilling exceeds 3 levels. - -### 4.4 Error Handling Pattern -- **Server Actions:** Handle errors gracefully using `try/catch` and return structured objects: - ```ts - try { - // logic - } catch (error) { - if (error instanceof Error) { - console.error(error.message); - } - return { success: false, error: "Friendly error message" }; - } - ``` - -## 5. Workflows to AVOID 🚫 -- No API Routes: Do not use pages/api; use Server Actions. -- No Hardcoded Secrets: Always use process.env. -- No Huge Components: Break down components if they exceed 200 lines. -- No New Packages: Do not install new npm packages without asking for approval first. -- No Prop Drilling: Avoid passing props more than 3 levels deep. - -## 6. Environment & Commands -- Environment Variables: NOTION_API_KEY, NOTION_POSTS_DATA_SOURCE_ID, NOTION_COMMENTS_DATA_SOURCE_ID. -- Commands: - - pnpm dev: Start dev server (http://localhost:3000). - - pnpm build: Production build. - - pnpm test: Run unit tests. - - pnpm test:e2e: Run E2E tests. - -## 7. Documentation Etiquette -- Code Comments: When writing complex logic, write a brief comment explaining "Why" (intent), not "What" (syntax). -- JSDoc: Mandatory for all utility functions. diff --git a/.gemini/skills/SKILL.example.md b/.gemini/skills/SKILL.example.md deleted file mode 100644 index b726e97..0000000 --- a/.gemini/skills/SKILL.example.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: [스킬 이름] -description: [이 스킬이 무엇을 하는지 한 줄 요약] -triggers: - - [트리거 키워드 1] (예: @notion, @db) - - [트리거 키워드 2] ---- - -# [Skill Name] Context -이 파일은 [특정 작업]을 수행할 때 AI가 따라야 할 가이드라인입니다. - -## 1. Goal (목표) -- 무엇을 달성해야 하는지 명확히 정의 - -## 2. Rules (제약 사항) -- 절대 하지 말아야 할 것들 - -## 3. Tool Usage (도구 사용법) -- 관련된 함수나 스크립트 실행 방법 - -## 4. Examples (예시 - 가장 중요 ⭐) -User: [질문 예시] -AI: [이상적인 답변 예시] diff --git a/.gemini/skills/code-review/SKILL.md b/.gemini/skills/code-review/SKILL.md deleted file mode 100644 index 7cffc1b..0000000 --- a/.gemini/skills/code-review/SKILL.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -name: Senior Code Reviewer -description: Analyze code for performance, security, type safety, and Next.js best practices. -triggers: - - @review - - @cr - - "코드 리뷰해줘" - - "이 코드 문제점 있어?" ---- - -# Code Review Skill - -## 1. Context -You are a Senior Full-Stack Engineer reviewing a Pull Request. Your goal is to catch logical errors, performance bottlenecks, and maintainability issues before they merge. - -## 2. Review Checklist (Priority Order) -1. **Server vs Client Boundary:** - - Is `'use client'` used only when necessary? (Interactivity, Hooks) - - Are sensitive environment variables (`process.env.NOTION_API_KEY`) leaking into Client Components? -2. **Performance (Critical):** - - Are Notion API calls wrapped in `unstable_cache`? (Must be cached for 6 hours/21600s) - - Are images using `next/image` with proper `sizes` and `alt` tags? -3. **Type Safety:** - - Are there any explicit `any` types? (Flag immediately as Critical) - - Are API responses validated (e.g., checking if `properties` exist in Notion response)? -4. **Code Style:** - - Is the component too large (>200 lines)? Suggest splitting. - - Are tailwind classes organized? (Suggest `clsx` or `cn` usage for conditionals). - -## 3. Output Format -Provide feedback in the following format: - -- **🔴 Critical:** (Bugs, Security, `any` type, Infinite loops) -- **🟡 Improvement:** (Performance optimizations, better readability) -- **🟢 Good:** (Praise for good patterns) - -## 4. Example Interaction -**User:** "Review this code: `const data = await fetch(url).then(res => res.json())`" -**AI:** -"**🔴 Critical:** -1. **Promise Handling:** Use `async/await` instead of `.then()` chains for readability. -2. **Type Safety:** The return type is implicit `any`. Define an interface for the response. -3. **Error Handling:** There is no `try/catch` or error checking. - -**🟡 Improvement:** -- If this is a Server Component, consider wrapping `fetch` with `unstable_cache` if the data doesn't change often." - \ No newline at end of file diff --git a/.gemini/skills/git-manager/SKILL.md b/.gemini/skills/git-manager/SKILL.md deleted file mode 100644 index a886e83..0000000 --- a/.gemini/skills/git-manager/SKILL.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: Git Commit Manager -description: Generate semantic commit messages and manage branches. -triggers: - - @git - - @wt - - @cm - - "commit message" - - "커밋해줘" ---- - -# Git Manager Skill - -## 1. Context & Resources -- **Role:** You are an expert Git manager utilizing **Worktrees** for parallel development. -- **Reference:** For specific commands (especially Worktree & Bare Repo setup), **ALWAYS refer to `./cheatsheet.md`** located in the same directory. - -## 2. Commit Message Convention -Follow the **Conventional Commits** format: -- `feat`: New features (blog post rendering, new API route) -- `fix`: Bug fixes (hydration error, layout shift) -- `docs`: Documentation changes -- `style`: Code changes without logic change -- `refactor`: Code changes without logic change -- `test`: Test changes -- `chore`: Config changes, dependency updates - -## 3. Workflow -1. Analyze the `git diff` output. -2. Summarize changes in English (imperative mood). -3. If the change is huge, suggest splitting the commit. -4. Consult `./cheatsheet.md` for exact syntax. - -## 4. Example -**Input:** Changed the header background color and added a logo. -**Output:** -```bash -style: update header design with new logo - -- Change background color to neutral-900 -- Add Logo component to navigation -``` \ No newline at end of file diff --git a/.gemini/skills/notion-cms/SKILL.md b/.gemini/skills/notion-cms/SKILL.md deleted file mode 100644 index 4f04ece..0000000 --- a/.gemini/skills/notion-cms/SKILL.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Notion CMS Handler -description: Manage Notion API interactions, database queries, and data fetching logic. -triggers: - - @notion - - @cms - - "fetch post" - - "노션" ---- - -# Notion CMS Skill - -## 1. Context -You are a Notion API expert for a Next.js blog. You handle data fetching from the Notion Database ID provided in `.env`. - -## 2. Critical Rules -1. **Caching:** Must use `unstable_cache` for all `notion.databases.query` calls. Revalidate time is 21600s. -2. **SDK:** Use `@notionhq/client`. -3. **Transformer:** Always transform the raw Notion response into a simplified `Post` interface before returning it to the component. Do not leak raw Notion blocks to the UI. -4. **Filter:** Only fetch posts where `status` is 'Published'. - -## 3. Code Pattern (Copy this style) -```typescript -import { Client } from "@notionhq/client"; -import { unstable_cache } from "next/cache"; - -const notion = new Client({ auth: process.env.NOTION_API_KEY }); - -export const getPosts = unstable_cache( - async () => { - // ...implementation... - }, - ["posts"], - { revalidate: 21600 } -); -``` diff --git a/.gemini/skills/test-engineer/SKILL.md b/.gemini/skills/test-engineer/SKILL.md deleted file mode 100644 index 06516a7..0000000 --- a/.gemini/skills/test-engineer/SKILL.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: Test Engineer -description: Write and debug Unit (Vitest) and E2E (Playwright) tests. -triggers: - - @test - - "테스트 작성해줘" - - "에러 고쳐줘" ---- - -# Test Engineer Skill - -## 1. Role -You are a QA Engineer specialized in Vitest and Playwright. - -## 2. File Placement Rules -- **Unit Tests:** Place in `__tests__/unit/{filename}.test.tsx` -- **E2E Tests:** Place in `__tests__/e2e/{feature}.spec.ts` - -## 3. Writing Strategy -- **Unit:** Do not test implementation details. Test behaviors (inputs/outputs). Use `screen.getByRole` for accessibility compliance. -- **E2E:** Always capture screenshots on failure (`screenshot: 'only-on-failure'`). Mock external API calls (Notion) using `page.route` to avoid hitting real limits. -- **CI/CD:** Ensure `pnpm test` and `pnpm test:e2e` pass before committing. - -## 4. Command Reference -- Run Unit: `pnpm test` -- Run E2E: `pnpm test:e2e` -- Debug Mode: `pnpm test:ui` - -## 5. Example Interaction -User: "Create a test for the Comment component." -AI: "Created `__tests__/unit/Comment.test.tsx`. I mocked the submission API to prevent network requests. Would you like me to run `pnpm test` now?" diff --git a/.gemini/skills/ui/SKILL.md b/.gemini/skills/ui/SKILL.md deleted file mode 100644 index 7bca4a0..0000000 --- a/.gemini/skills/ui/SKILL.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: UI/UX Designer -description: Design accessible, responsive, and aesthetic components using Tailwind CSS v4. -triggers: - - @ui - - @design - - "디자인해줘" - - "예쁘게 만들어줘" ---- - -# UI/UX Design Skill - -## 1. Context -You are a Product Designer specialized in Tailwind CSS v4 and Headless UI systems. You prioritize "Mobile First", "Dark Mode", and "Accessibility (a11y)". - -## 2. Design Principles -- **Mobile First:** Always write base classes for mobile, then `md:`, `lg:` for larger screens. -- **Dark Mode Support:** Every color class must have a `dark:` counterpart. (e.g., `bg-white dark:bg-neutral-950`). -- **Interaction:** Add states for `hover:`, `focus-visible:` (for keyboard nav), and `active:`. -- **Spacing:** Use consistent spacing (multiples of 4). `p-4`, `gap-6`, `my-8`. - -## 3. Tech Constraints -- **Library:** Use `lucide-react` for icons. -- **Utils:** Use `cn` (from `lib/utils`) for class merging. -- **Tailwind v4:** Do not use `config` based arbitrary values if possible. Use standard utility classes. From 14de6e0c232f0c7ffdf5484cf37657643bd3ed7b Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Sat, 24 Jan 2026 15:25:38 +1100 Subject: [PATCH 06/13] feat: add vercel analysis component --- src/app/[locale]/layout.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 09ec35c..dbddcc3 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -96,6 +96,8 @@ export default async function LocaleLayout({ + + ); From f02010314554cf0a2ce5089c8bb51c39449d7c69 Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Sun, 25 Jan 2026 11:07:41 +1100 Subject: [PATCH 07/13] fix: matching env convention --- src/app/[locale]/layout.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index dbddcc3..69ec3d9 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -6,10 +6,12 @@ import { notFound } from 'next/navigation'; import { routing } from '@/i18n/routing'; import { SpeedInsights } from "@vercel/speed-insights/next" import { GoogleTagManager, GoogleAnalytics } from '@next/third-parties/google'; +import { GoogleTagManager, GoogleAnalytics } from '@next/third-parties/google'; import { Analytics } from "@vercel/analytics/next" import { ThemeProvider } from "@/components/utils/ThemeProvider"; import { ErrorBoundary } from "@/components/ErrorBoundary"; +import { ErrorBoundary } from "@/components/ErrorBoundary"; import { GoogleAdSense } from "@/components/delegator/GoogleAdSense"; import "../globals.css"; From e5a7b4f6193cc6be64d19d074bb206ac6fc2b1f9 Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Sun, 25 Jan 2026 11:22:55 +1100 Subject: [PATCH 08/13] fix: duplicated error in layout --- src/app/[locale]/layout.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 69ec3d9..dbddcc3 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -6,12 +6,10 @@ import { notFound } from 'next/navigation'; import { routing } from '@/i18n/routing'; import { SpeedInsights } from "@vercel/speed-insights/next" import { GoogleTagManager, GoogleAnalytics } from '@next/third-parties/google'; -import { GoogleTagManager, GoogleAnalytics } from '@next/third-parties/google'; import { Analytics } from "@vercel/analytics/next" import { ThemeProvider } from "@/components/utils/ThemeProvider"; import { ErrorBoundary } from "@/components/ErrorBoundary"; -import { ErrorBoundary } from "@/components/ErrorBoundary"; import { GoogleAdSense } from "@/components/delegator/GoogleAdSense"; import "../globals.css"; From 83a4c5640b6d4ccf2566f267f274db38f316fc46 Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Sun, 25 Jan 2026 23:25:11 +1100 Subject: [PATCH 09/13] refector: delete unused variable --- src/app/[locale]/[slug]/page.tsx | 15 ++++++--------- src/lib/services/posts.service.ts | 10 ++++++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/app/[locale]/[slug]/page.tsx b/src/app/[locale]/[slug]/page.tsx index a079c1c..5bd435d 100644 --- a/src/app/[locale]/[slug]/page.tsx +++ b/src/app/[locale]/[slug]/page.tsx @@ -1,9 +1,11 @@ -import { getPageContent, getPostBySlug, getPublishedPosts, getPostById } from "@/lib/services/posts.service"; -import { BlockRenderer } from "@/components/notion/BlockRenderer"; -import { Link } from "@/i18n/routing"; import Image from "next/image"; import { notFound } from "next/navigation"; import { getTranslations } from 'next-intl/server'; +import { Metadata } from "next"; + +import { getPageContent, getPostBySlug, getPublishedPosts, getPostById } from "@/lib/services/posts.service"; +import { BlockRenderer } from "@/components/notion/BlockRenderer"; +import { Link } from "@/i18n/routing"; import { LanguageToggle } from "@/components/utils/LanguageToggle"; import { ViewTracker } from "@/components/utils/ViewTracker"; import { PostEngagement } from "@/components/posts/PostEngagement"; @@ -11,12 +13,6 @@ import { CommentSection } from "@/components/comments/CommentSection"; import { ErrorBoundary } from "@/components/ErrorBoundary"; import { CommentErrorFallback } from "@/components/error-fallbacks/CommentErrorFallback"; -export const dynamic = 'force-dynamic'; - -export const revalidate = 3600; // Revalidate every 1 hour - -import { Metadata } from "next"; - export async function generateStaticParams() { const posts = await getPublishedPosts(); if (!posts) return []; @@ -38,6 +34,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str return { title: post.title, description: post.description || `Read ${post.title} on VXD Blog`, + keywords: post.tags?.join(', '), openGraph: { title: post.title, description: post.description || `Read ${post.title} on VXD Blog`, diff --git a/src/lib/services/posts.service.ts b/src/lib/services/posts.service.ts index 573a49b..9adab50 100644 --- a/src/lib/services/posts.service.ts +++ b/src/lib/services/posts.service.ts @@ -5,6 +5,8 @@ import { notion } from "../notion"; import { BlogPost, SortOption, SortDirection } from "../types"; import { getNumberValue, extractBlogPostFromPage } from "./posts.helper"; +export const revalidate = 1 * 60 * 60; // Revalidate every 1 hour + export const getPostsDataSourceId = () => { const dataSourceId = process.env.NOTION_POSTS_DATA_SOURCE_ID; if (!dataSourceId) { @@ -43,7 +45,7 @@ const getCachedAllPosts = unstable_cache(async (): Promise => .map(extractBlogPostFromPage); return posts; -}, ['all-posts-v5'], { revalidate: 3600 }); +}, ['all-posts'], { revalidate }); export interface GetPublishedPostsOptions { tag?: string; @@ -235,7 +237,7 @@ export const getPageContent = unstable_cache(async (pageId: string) => { console.error("Failed to fetch page content:", error); return []; } -}, ['page-content'], { revalidate: 3600 }); +}, ['page-content'], { revalidate }); export const getPostById = unstable_cache(async (pageId: string): Promise => { try { @@ -255,7 +257,7 @@ export const getPostById = unstable_cache(async (pageId: string): Promise => { try { @@ -276,7 +278,7 @@ export const getPostBySlug = unstable_cache(async (slug: string): Promise { From ee3288a16c1fab0086cac0a6700eda176638ac78 Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Mon, 26 Jan 2026 00:49:12 +1100 Subject: [PATCH 10/13] feat(ui): implement code block copy and syntax highlighting --- .gemini/skills/git-manager/cheatsheet.md | 3 +- .gemini/skills/workflows/SKILL.md | 55 ------- package.json | 2 + pnpm-lock.yaml | 189 +++++++++++++++++++++++ src/components/notion/BlockRenderer.tsx | 15 +- src/components/notion/CodeBlock.tsx | 65 ++++++++ 6 files changed, 267 insertions(+), 62 deletions(-) delete mode 100644 .gemini/skills/workflows/SKILL.md create mode 100644 src/components/notion/CodeBlock.tsx diff --git a/.gemini/skills/git-manager/cheatsheet.md b/.gemini/skills/git-manager/cheatsheet.md index de59287..2b71a6d 100644 --- a/.gemini/skills/git-manager/cheatsheet.md +++ b/.gemini/skills/git-manager/cheatsheet.md @@ -13,9 +13,8 @@ root 폴더에 .bare 폴더를 생성하고 그 안에 워크트리 폴더를 **항상 root 폴더에서 명령어를 실행해야 함** ```bash - git -C .bare worktree list # 현재 생성된 모든 워크트리(작업 폴더) 목록 확인 -git -C .bare worktree add ../<폴더명> <브랜치명> # 새 브랜치를 따면서 새 폴더 생성 +git -C .bare worktree add -b <브랜치명> ../<폴더명> main # main으로부터 새 브랜치를 따면서 새 폴더 생성 git -C .bare worktree add ../<폴더명> # 기존 브랜치를 새 폴더로 체크아웃 git -C .bare worktree remove <폴더명> # 작업 폴더 삭제 (Git 연결 해제) git -C .bare worktree prune # 폴더를 강제 삭제(rm -rf)했을 때 찌꺼기 정리 diff --git a/.gemini/skills/workflows/SKILL.md b/.gemini/skills/workflows/SKILL.md deleted file mode 100644 index feb6bc2..0000000 --- a/.gemini/skills/workflows/SKILL.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: Task Finisher Workflow -description: Automate the Code Review -> Confirmation -> Git Commit pipeline. -triggers: - - @finish - - @done - - "작업 완료" - - "마무리해줘" ---- - -# Task Finisher Workflow - -## 1. Context -You are the **Workflow Orchestrator**. Your goal is to ensure high code quality before finalizing any changes. You act as a bridge between the `@review` skill and the `@git` skill. - -## 2. Execution Steps (Follow Strictly) - -### Step 1: Pre-Commit Review (Invoke Code-Reviewer) -1. Read all modified files (`git diff`). -2. **Act as the `@review` agent:** - - Check for `any` types, missing `unstable_cache`, and logic errors. - - Check if strictly necessary `test` files exist (if logic was changed). -3. **Report:** - - If **Critical Issues** are found 🔴 -> **STOP** and ask the user to fix them. - - If only **Minor Issues** or **Good** 🟢 -> Present the summary and ask: - > "리뷰 결과가 양호합니다. 커밋 메시지를 생성하고 진행할까요?" - -### Step 2: User Confirmation (Wait) -- **DO NOT** generate the commit command yet. -- Wait for the user to reply "Yes", "Proceed", or "ㅇㅇ". - -### Step 3: Commit Generation (Invoke Git-Manager) -1. Once confirmed, **Act as the `@git` agent:** - - Generate a specific `git commit` command following Conventional Commits. - - Provide the command for the user to run (or run it if permissions allow). - -## 3. Example Interaction - -**User:** `@finish` (Auth login implementation) - -**AI:** -"**🔎 Code Review Summary:** -- 🟢 **Security:** `process.env` is properly used. -- 🟡 **Suggestion:** Consider extracting the validation logic to `lib/auth.ts`. -- ✅ **Tests:** Unit tests passed. - -코드가 안정적으로 보입니다. 커밋을 진행할까요?" - -**User:** "응 진행해" - -**AI:** -"**🚀 Git Commit:** -```bash -git add . -git commit -m "feat: implement auth login with validation" diff --git a/package.json b/package.json index d8f2a09..927ba72 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "next-themes": "^0.4.6", "react": "19.2.3", "react-dom": "19.2.3", + "react-syntax-highlighter": "^16.1.0", "tailwind-merge": "^3.4.0" }, "devDependencies": { @@ -36,6 +37,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^5.1.2", "@vitest/coverage-v8": "4.0.16", "dotenv": "^17.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fb897b..4e34f6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: react-dom: specifier: 19.2.3 version: 19.2.3(react@19.2.3) + react-syntax-highlighter: + specifier: ^16.1.0 + version: 16.1.0(react@19.2.3) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -72,6 +75,9 @@ importers: '@types/react-dom': specifier: ^19 version: 19.2.3(@types/react@19.2.7) + '@types/react-syntax-highlighter': + specifier: ^15.5.13 + version: 15.5.13 '@vitejs/plugin-react': specifier: ^5.1.2 version: 5.1.2(vite@7.3.0(@types/node@20.19.27)(jiti@2.6.1)(lightningcss@1.30.2)) @@ -1157,6 +1163,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1166,14 +1175,26 @@ packages: '@types/node@20.19.27': resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 + '@types/react-syntax-highlighter@15.5.13': + resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} + '@types/react@19.2.7': resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/eslint-plugin@8.51.0': resolution: {integrity: sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1574,6 +1595,15 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1591,6 +1621,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -1658,6 +1691,9 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1901,6 +1937,9 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1933,6 +1972,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2034,12 +2077,24 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -2078,6 +2133,12 @@ packages: intl-messageformat@10.7.18: resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2113,6 +2174,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2129,6 +2193,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2359,6 +2426,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} @@ -2539,6 +2609,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} @@ -2600,9 +2673,16 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2625,6 +2705,12 @@ packages: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} + react-syntax-highlighter@16.1.0: + resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} + engines: {node: '>= 16.20.2'} + peerDependencies: + react: '>= 0.14.0' + react@19.2.3: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} @@ -2633,6 +2719,9 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} + refractor@5.0.0: + resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} + regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -2748,6 +2837,9 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -3962,6 +4054,10 @@ snapshots: '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -3970,14 +4066,24 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/prismjs@1.26.5': {} + '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: '@types/react': 19.2.7 + '@types/react-syntax-highlighter@15.5.13': + dependencies: + '@types/react': 19.2.7 + '@types/react@19.2.7': dependencies: csstype: 3.2.3 + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4385,6 +4491,12 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4399,6 +4511,8 @@ snapshots: color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + commander@7.2.0: {} concat-map@0.0.1: {} @@ -4462,6 +4576,10 @@ snapshots: decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -4872,6 +4990,10 @@ snapshots: dependencies: reusify: 1.1.0 + fault@1.0.4: + dependencies: + format: 0.2.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -4900,6 +5022,8 @@ snapshots: dependencies: is-callable: 1.2.7 + format@0.2.2: {} + fsevents@2.3.2: optional: true @@ -4998,12 +5122,28 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: dependencies: hermes-estree: 0.25.1 + highlight.js@10.7.3: {} + + highlightjs-vue@1.0.0: {} + html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.7.0 @@ -5050,6 +5190,13 @@ snapshots: '@formatjs/icu-messageformat-parser': 2.11.4 tslib: 2.8.1 + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -5094,6 +5241,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -5112,6 +5261,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -5333,6 +5484,11 @@ snapshots: dependencies: js-tokens: 4.0.0 + lowlight@1.20.0: + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + lru-cache@11.2.4: {} lru-cache@5.1.1: @@ -5517,6 +5673,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse5@8.0.0: dependencies: entities: 6.0.1 @@ -5567,12 +5733,16 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + prismjs@1.30.0: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.1.0: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -5588,6 +5758,16 @@ snapshots: react-refresh@0.18.0: {} + react-syntax-highlighter@16.1.0(react@19.2.3): + dependencies: + '@babel/runtime': 7.28.4 + highlight.js: 10.7.3 + highlightjs-vue: 1.0.0 + lowlight: 1.20.0 + prismjs: 1.30.0 + react: 19.2.3 + refractor: 5.0.0 + react@19.2.3: {} reflect.getprototypeof@1.0.10: @@ -5601,6 +5781,13 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 + refractor@5.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/prismjs': 1.26.5 + hastscript: 9.0.1 + parse-entities: 4.0.2 + regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -5789,6 +5976,8 @@ snapshots: source-map-js@1.2.1: {} + space-separated-tokens@2.0.2: {} + stable-hash@0.0.5: {} stackback@0.0.2: {} diff --git a/src/components/notion/BlockRenderer.tsx b/src/components/notion/BlockRenderer.tsx index 7f80820..81de2dc 100644 --- a/src/components/notion/BlockRenderer.tsx +++ b/src/components/notion/BlockRenderer.tsx @@ -1,4 +1,5 @@ import { TextRenderer } from "./TextRenderer"; +import { CodeBlock } from "./CodeBlock"; import { cn } from "@/lib/utils"; import Image from "next/image"; import type { BlockObjectResponse, RichTextItemResponse, TableRowBlockObjectResponse, PartialBlockObjectResponse } from "@notionhq/client/build/src/api-endpoints"; @@ -92,12 +93,16 @@ export function BlockRenderer({ block }: BlockRendererProps) { ); case "code": + // Extract code text from rich text array + const codeText = value.rich_text + .map((rt: RichTextItemResponse) => rt.plain_text) + .join(''); + return ( -
-          
-            
-          
-        
+ ); case "image": const src = diff --git a/src/components/notion/CodeBlock.tsx b/src/components/notion/CodeBlock.tsx new file mode 100644 index 0000000..aeb248f --- /dev/null +++ b/src/components/notion/CodeBlock.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { useState } from 'react'; +import { Check, Copy } from 'lucide-react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; + +interface CodeBlockProps { + code: string; + language: string; +} + +export function CodeBlock({ code, language }: CodeBlockProps) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+ {/* Language Badge */} + {language && ( +
+ {language} +
+ )} + + {/* Copy Button */} + + + {/* Code Block with Syntax Highlighting */} +
+ + {code} + +
+
+ ); +} From 9d68962ec8769e2a91f6cb5d6e9723030b666319 Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Mon, 26 Jan 2026 00:55:24 +1100 Subject: [PATCH 11/13] docs: remove unused file and change definition of 'refactor' commit tag --- .agent/skills/git-manager/SKILL.md | 2 +- .gemini/skills/git-manager/cheatsheet.md | 80 ------------------------ 2 files changed, 1 insertion(+), 81 deletions(-) delete mode 100644 .gemini/skills/git-manager/cheatsheet.md diff --git a/.agent/skills/git-manager/SKILL.md b/.agent/skills/git-manager/SKILL.md index a886e83..633892c 100644 --- a/.agent/skills/git-manager/SKILL.md +++ b/.agent/skills/git-manager/SKILL.md @@ -21,7 +21,7 @@ Follow the **Conventional Commits** format: - `fix`: Bug fixes (hydration error, layout shift) - `docs`: Documentation changes - `style`: Code changes without logic change -- `refactor`: Code changes without logic change +- `refactor`: Code changes that improve code structure - `test`: Test changes - `chore`: Config changes, dependency updates diff --git a/.gemini/skills/git-manager/cheatsheet.md b/.gemini/skills/git-manager/cheatsheet.md deleted file mode 100644 index 2b71a6d..0000000 --- a/.gemini/skills/git-manager/cheatsheet.md +++ /dev/null @@ -1,80 +0,0 @@ -# 명령어,설명 - -```bash -git config --global user.name "이름" # 사용자 이름 설정 -git config --global user.email "이메일" # 사용자 이메일 설정 -git init # 현재 디렉토리를 Git 저장소로 초기화 -git clone --bare .bare # 워크트리 사용을 위한 Bare Clone -``` - -## 워크트리 -워크트리 사용을 위한 명령어 -root 폴더에 .bare 폴더를 생성하고 그 안에 워크트리 폴더를 생성 -**항상 root 폴더에서 명령어를 실행해야 함** - -```bash -git -C .bare worktree list # 현재 생성된 모든 워크트리(작업 폴더) 목록 확인 -git -C .bare worktree add -b <브랜치명> ../<폴더명> main # main으로부터 새 브랜치를 따면서 새 폴더 생성 -git -C .bare worktree add ../<폴더명> # 기존 브랜치를 새 폴더로 체크아웃 -git -C .bare worktree remove <폴더명> # 작업 폴더 삭제 (Git 연결 해제) -git -C .bare worktree prune # 폴더를 강제 삭제(rm -rf)했을 때 찌꺼기 정리 -git -C .bare worktree move <구폴더> ../<신폴더> # 워크트리 폴더 경로/이름 변경 -``` - -## 커밋 - -```bash -git status # 현재 파일 상태(변경됨, 스테이징됨) 확인 -git add . # 모든 변경사항을 스테이징 영역(Staging Area)에 추가 -git add <파일> # 특정 파일만 스테이징 -git commit -m "메시지" # 스테이징된 변경사항 확정(저장) -git commit --amend # 방금 한 커밋의 메시지나 파일 수정 (덮어쓰기) -``` - -## 브랜치 - -```bash -git branch # 로컬 브랜치 목록 확인 -git branch -r # 원격 브랜치 목록 확인 -git branch <이름> # 새 브랜치 생성 (이동은 안 함) -git branch -m <새이름> # 현재 브랜치 이름 변경 -git branch -d <이름> # 브랜치 삭제 (병합된 것만) -git branch -D <이름> # 브랜치 강제 삭제 -git switch <브랜치> # 해당 브랜치로 이동 -git switch -c <이름> # 새 브랜치 만들면서 이동 -``` - -## 원격 저장소 - -```bash -git fetch # 원격 저장소의 최신 이력만 가져옴 (병합 X) -git pull origin <브랜치> # 원격 내용을 가져와서 합침 (Fetch + Merge) -git push origin <브랜치> # 내 커밋을 원격 저장소에 올림 -git push -u origin <브랜치> # 업스트림 설정 (다음부턴 git push만 해도 됨) -git merge <브랜치> # 다른 브랜치를 현재 브랜치로 합침 -git rebase <브랜치> # 내 브랜치의 시작점을 타겟 브랜치 끝으로 옮김 (깔끔한 히스토리) -``` - -## 복구 - -```bash -git restore <파일> # 작업 중인 파일 변경사항 취소 (마지막 커밋 상태로) -git restore --staged <파일> # git add 취소 (스테이징 내리기) -git reset --soft HEAD~1 # 커밋은 취소하되, 변경사항은 스테이징 상태로 보존 -git reset --hard HEAD~1 # 커밋과 변경사항 모두 날려버림 (복구 불가) -git revert <커밋ID> # 특정 커밋의 내용을 반대로 수행하는 새 커밋 생성 (협업 시 안전) -``` - -## 임시 저장 -임시 저장 하지 않음. 워크트리 사용. - -## 히스토리 - -```bash -git log # 커밋 히스토리 조회 -git log --oneline --graph # 히스토리를 그래프 형태로 한 줄 요약해서 보기 -git diff # 스테이징되지 않은 변경사항 확인 -git show <커밋ID> # 특정 커밋의 상세 변경 내용 확인 -git blame <파일> # "파일의 각 라인을 누가, 언제 수정했는지 범인(?) 찾기" -git bisect # 이진 탐색으로 버그가 발생한 커밋 찾아내기 -``` From 0032bc5157719a3b4585c2ae968e2db34bac034f Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Tue, 27 Jan 2026 00:49:46 +1100 Subject: [PATCH 12/13] fix(images): optimize Vercel image usage by reducing variants and bypassing content images --- next-env.d.ts | 2 +- next.config.ts | 5 ++--- src/components/notion/BlockRenderer.tsx | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts index c66fbb7..cf83429 100644 --- a/next.config.ts +++ b/next.config.ts @@ -11,7 +11,8 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ const nextConfig: NextConfig = { images: { - formats: ['image/webp', 'image/avif'], + deviceSizes: [640, 1080, 1920], + imageSizes: [64, 128, 256], remotePatterns: [ { protocol: "https", @@ -26,8 +27,6 @@ const nextConfig: NextConfig = { hostname: "**.amazonaws.com", }, ], - deviceSizes: [640, 750, 828, 1080, 1200, 1920], - imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], }, // async redirects() { // return [ diff --git a/src/components/notion/BlockRenderer.tsx b/src/components/notion/BlockRenderer.tsx index 81de2dc..2b89019 100644 --- a/src/components/notion/BlockRenderer.tsx +++ b/src/components/notion/BlockRenderer.tsx @@ -116,6 +116,7 @@ export function BlockRenderer({ block }: BlockRendererProps) { src={src} alt={caption || "Blog image"} className="object-cover w-full h-full" + unoptimized={true} /> {caption && ( From 2a7598ddeef17ee230b0164da75d78e3c318637c Mon Sep 17 00:00:00 2001 From: VisioneXperienceDeveloper Date: Tue, 27 Jan 2026 05:48:39 +1100 Subject: [PATCH 13/13] fix: BlockRenderer test --- __tests__/unit/components/notion/BlockRenderer.test.tsx | 9 +++++++++ next-env.d.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/__tests__/unit/components/notion/BlockRenderer.test.tsx b/__tests__/unit/components/notion/BlockRenderer.test.tsx index 647ef94..79a856b 100644 --- a/__tests__/unit/components/notion/BlockRenderer.test.tsx +++ b/__tests__/unit/components/notion/BlockRenderer.test.tsx @@ -4,6 +4,15 @@ import { BlockRenderer } from '@/components/notion/BlockRenderer'; import { createTestBlock } from '../../../helpers/block-test-helpers'; // Mock next/image to avoid width/height requirement in tests +// Mock react-syntax-highlighter +vi.mock('react-syntax-highlighter', () => ({ + Prism: ({ children }: { children: string }) =>
{children}
, +})); + +vi.mock('react-syntax-highlighter/dist/esm/styles/prism', () => ({ + atomDark: {}, +})); + vi.mock('next/image', () => ({ default: ({ src, alt, className }: { src: string; alt: string; className?: string }) => ( // eslint-disable-next-line @next/next/no-img-element diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.