Skip to content

chore: Web 추가 기초 설정#20

Merged
ychany merged 16 commits into
devfrom
chore/12-web-setting
Apr 25, 2026
Merged

chore: Web 추가 기초 설정#20
ychany merged 16 commits into
devfrom
chore/12-web-setting

Conversation

@iOdiO89

@iOdiO89 iOdiO89 commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

작업 요약

  • 레포 lint config 정리
  • Web 프로젝트 기초 설정 추가 (provider, reset css)
  • Web 프로젝트 pretendard 폰트 추가
  • 불필요한 파일 제거

작업 내용

Lint / Monorepo

  • eslint-config 패키지 삭제 후 ESLint 설정을 루트 중심으로 일원화
    • 공통 규칙을 root에 두고, web/app 프로젝트에서 오버라이드해서 사용하는 구조를 채택했습니다.
    • eslint-config를 따로 패키지로 배포할 일이 없기 때문에, 관리하기 쉽도록 root lint config로 변경했습니다.
  • 루트 lint config와 중복되는 Web 프로젝트 lint 의존성 제거

Web 프로젝트

  • Reset CSS 도입
  • Pretendard 폰트 추가

기타

  • JSX 반환 타입 오류 수정 (참고)
  • import 경로 및 파일명 케이스를 정리하여 참조 안정성 확보
  • 불필요한 에셋, 파일, 패키지 삭제

비고

모노레포라서 web/app 프로젝트 구분을 위해 커밋 시 자동으로 앞에 어떤 프로젝트인지 명시해주는 husky를 도입하면 어떨까 하는데, 어떤가용

연관 이슈

close #12

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • Tailwind 유틸리티 클래스 병합을 위한 cn 헬퍼 추가
    • React Query 클라이언트 관리 유틸리티 추가
  • 스타일

    • 웹 앱 폰트를 Pretendard로 변경
    • CSS 리셋 스타일시트 추가
  • 문서

    • 풀 리퀘스트 템플릿 섹션 이름 업데이트
  • Chores

    • ESLint 설정 재구성
    • TypeScript 구성 최적화
    • 불필요한 패키지 및 예제 코드 제거

@iOdiO89 iOdiO89 self-assigned this Apr 23, 2026
@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown

Walkthrough

이 PR은 ESLint 설정을 루트로 통합하고, 웹 앱의 기초 설정을 정리하며, 불필요한 패키지들을 제거합니다. 세미콜론 포맷팅 개선, 폰트 및 스타일링 변경, 코어 패키지 예제 코드 제거를 포함합니다.

Changes

Cohort / File(s) Summary
ESLint 설정 루트 이동 및 통합
eslint.config.mjs, package.json, .github/workflows/slack-pr-bot.yml, .github/PULL_REQUEST_TEMPLATE.md
ESLint 설정을 루트 레벨로 통합하고, PR 템플릿의 섹션명을 '작업 내용'에서 '작업 요약'으로 변경하며, Slack 워크플로우를 이에 맞게 업데이트합니다.
앱별 ESLint 설정 전환
apps/app/eslint.config.js, apps/app/eslint.config.mjs, apps/web/eslint.config.mjs
앱별로 기존 JS 설정을 제거하고 새로운 ESM 형식의 flat config로 전환하여 루트 설정을 상속받도록 합니다.
웹 앱 기초 설정 및 스타일링
apps/web/src/app/layout.tsx, apps/web/src/app/page.tsx, apps/web/src/app/page.module.css, apps/web/src/styles/globals.css, apps/web/src/styles/reset.css
Geist에서 Pretendard 폰트로 변경하고, 페이지 콘텐츠를 단순화하며, 전역 스타일을 Tailwind 기반으로 리셋하고 CSS 리셋 파일을 추가합니다.
웹 앱 Provider 및 유틸 함수
apps/web/src/app/providers.tsx, apps/web/src/components/Providers.tsx, apps/web/src/utils/cn.ts, apps/web/src/utils/queryClient.ts, apps/web/package.json
Provider를 컴포넌트 디렉토리로 이동하고, Tailwind 클래스 병합 유틸, SSR 지원 QueryClient 유틸을 추가하며, 필수 의존성(clsx, tailwind-merge)을 추가합니다.
앱 코드 포맷팅 및 세미콜론
apps/app/app/_layout.tsx, apps/app/app/index.tsx, apps/app/app/modal.tsx, apps/app/components/ExternalLink.tsx, apps/app/components/HapticTab.tsx, apps/app/scripts/reset-project.js
import 문과 문장 종료에 세미콜론을 추가하고, type-only import를 명시하며, 불필요한 괄호를 제거합니다.
코어 패키지 예제 코드 제거
packages/core/src/hooks/example.ts, packages/core/src/types/example.ts, packages/core/src/utils/example.ts, packages/core/src/index.ts, packages/core/package.json, packages/core/tsconfig.json
예제 훅(useCounter), 타입(UserT), 유틸(formatDate)을 삭제하고 인덱스 export를 업데이트합니다.
ESLint 설정 패키지 완전 제거
packages/eslint-config/base.js, packages/eslint-config/next.js, packages/eslint-config/react-internal.js, packages/eslint-config/package.json, packages/eslint-config/README.md
@repo/eslint-config 패키지 전체를 삭제하여 루트 설정으로 통합합니다.
UI 패키지 완전 제거
packages/ui/package.json, packages/ui/eslint.config.mjs, packages/ui/src/button.tsx, packages/ui/src/card.tsx, packages/ui/src/code.tsx
@repo/ui 패키지 전체를 삭제하여 불필요한 UI 컴포넌트와 빌드 아티팩트를 제거합니다.
설정 파일 및 문서 업데이트
README.md, pnpm-workspace.yaml, packages/typescript-config/nextjs.json
제거된 패키지들을 README에서 삭제하고, 글자 따옴표 스타일을 통일하며, TypeScript 선언 파일 생성을 비활성화합니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰✨ 깔끔해진 ESLint, 정리된 웹 기초,
불필요한 패키지는 이제 안녕,
Pretendard로 예뻐진 폰트,
QueryClient는 SSR을 안고,
세미콜론 하나하나 찬찬히 정렬하며
토끼가 지어올린 튼튼한 발판! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning PR이 이슈 #12 범위를 벗어난 여러 변경사항을 포함하고 있습니다. JSX 반환 타입 수정, Prettier 일괄 적용, PR 템플릿/Slack 워크플로우 수정, @repo/ui 패키지 완전 삭제 등이 추가되었습니다. 범위 외 변경사항들(Prettier 스타일, PR 템플릿, Slack workflow, UI 패키지 삭제)을 별도 PR로 분리하거나 명시적으로 이슈에 추가하는 것을 권장합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive PR이 연결된 이슈 #12의 대부분의 코딩 요구사항(eslint 루트 이동, web tanstack query provider Next 형식 변경, 불필요한 파일 삭제)을 완료했으나 logger 함수 추가는 미완료 상태입니다. logger 함수 추가 기능이 이번 PR 범위에서 의도적으로 제외되었는지, 아니면 향후 작업으로 미루기로 결정했는지 명확히 해야 합니다.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 'chore: Web 추가 기초 설정'은 웹 프로젝트의 기초 설정 추가라는 주요 변경사항을 명확하게 요약하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/12-web-setting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@iOdiO89
iOdiO89 requested review from kanghaeun, soyeong0115 and ychany and removed request for soyeong0115 April 23, 2026 14:46
@github-actions

Copy link
Copy Markdown

No description provided.

@kanghaeun

Copy link
Copy Markdown
Contributor

파일 경로에서 이미 프로젝트 구분이 되기도 하고 앞에 붙이면 커밋 메시지만 길어질 것 같아서 나는 안 해도 될 것 같어 근데 도입해도 상관없음!

Comment on lines +8 to +10
queries: {
staleTime: 60 * 1000,
refetchOnWindowFocus: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3) 이전에 retry: 1이 있었던 것 같은데 없앤 이유가 있어?
기본값보다는 1로 맞추는 게 좋을듯합니다
(기본값으로 두면 404같은 에러도 3번 호출돼서 불필요한 느낌)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

좋다 ~ 1로 고정했어 공식 docs에서 권장하는 내용 그대로 들고 오다보니 빠진듯!


import { getQueryClient } from '@/utils/queryClient';

export default function Providers({ children }: Readonly<{ children: ReactNode }>) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

p3) 다른 컴포넌트에서는 React.JSX.Element가 있는데 여기 빠졌다! 추가하거나 아예 다 빼거나 통일하는 게 어때?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

허거걱 layout에만 들어있는 거 같길래 layout쪽을 뻈습니다요

@kanghaeun kanghaeun changed the title Web 추가 기초 설정 chore: Web 추가 기초 설정 Apr 23, 2026

@soyeong0115 soyeong0115 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

커밋 메시지에 명시되어 있으면 보기 편해서 도입하면 좋을 것 같긴 한데, 나도 상관없음!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/slack-pr-bot.yml (1)

224-224: ⚠️ Potential issue | 🔴 Critical

첫 번째 build_summary의 헤더 매칭이 누락 업데이트되어 새 PR Slack 메시지가 항상 요약 없음으로 게시됩니다.

Post parent message 단계의 build_summary는 여전히 작업 내용을 찾고 있는 반면, PR 템플릿(.github/PULL_REQUEST_TEMPLATE.md)과 line 430의 두 번째 build_summary작업 요약을 사용하도록 변경되었습니다. PR이 처음 열릴 때(opened/reopened)는 이 단계가 실행되므로, 새로 생성된 모든 PR의 부모 Slack 메시지에서 작업 요약이 추출되지 않고 요약 없음으로 표시되며, 이후 PR 본문이 수정되어 Update parent Slack message 단계가 실행되어야만 정상적으로 갱신됩니다.

🔧 제안 수정
-                ok = (match(r, /^작업 내용([^A-Za-z]|$)/) || match(r, /[[:space:]]작업 내용([^A-Za-z]|$)/))
+                ok = (match(r, /^작업 요약([^A-Za-z]|$)/) || match(r, /[[:space:]]작업 요약([^A-Za-z]|$)/))

추가로, 동일한 awk 스니펫이 두 단계에 중복 정의되어 있어 향후 이런 종류의 누락이 재발할 가능성이 있습니다. 공용 셸 함수(예: 워크플로 시작부의 별도 step에서 $GITHUB_ENV로 export하거나, composite action으로 추출)로 분리하면 동기화가 쉬워집니다. (선택)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/slack-pr-bot.yml at line 224, The first build_summary
awk/regex in the "Post parent message" step still matches "작업 내용" so new PRs
show "요약 없음"; update that regex to match "작업 요약" (the same header used by the PR
template and the second build_summary at line 430) and ensure both build_summary
occurrences use the identical pattern. Also remove duplication by extracting the
awk snippet into a single reusable place (e.g., a shared step that exports the
parsed summary to GITHUB_ENV or a composite action) and have both "Post parent
message" and "Update parent Slack message" call that shared logic so future
edits stay in sync.
🧹 Nitpick comments (8)
apps/app/components/ExternalLink.tsx (1)

7-7: 컴포넌트 export 스타일을 규칙에 맞춰 default function으로 통일해 주세요.

현재 export function ExternalLink는 저장소 TSX 컴포넌트 규칙과 다릅니다.

As per coding guidelines, "**/*.tsx: Use function keyword with default export for React components".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/app/components/ExternalLink.tsx` at line 7, Change the named export to a
default function export to follow the TSX component rule: replace the named
export declaration "export function ExternalLink(...)" with a default-exported
function declaration "export default function ExternalLink(...)" (keep the same
Props signature and body), and update any imports elsewhere that referenced a
named import to use the default import if necessary.
apps/app/components/HapticTab.tsx (1)

5-5: 컴포넌트 선언을 default export function 형태로 맞추는 것을 권장합니다.

현재 선언(export function HapticTab)이 TSX 컴포넌트 규칙과 불일치합니다.

As per coding guidelines, "**/*.tsx: Use function keyword with default export for React components".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/app/components/HapticTab.tsx` at line 5, The HapticTab component is
currently a named export (export function HapticTab) but must follow the
codebase rule of using a default-exported function component; change its
declaration to a default export function (export default function
HapticTab(props: BottomTabBarButtonProps) { ...) and update any imports/usages
that expect the named export to use the default import form. Ensure the symbol
HapticTab is updated consistently across files that import it so type
annotations and props remain unchanged.
packages/core/tsconfig.json (1)

7-7: dist 제외 패턴 유지 권장

exclude에서 dist가 제거되었습니다. 현재 빌드 산출물이 dist에 떨어지지 않는다면 동작상 문제는 없지만, 향후 빌드/캐시 산출물이 생성될 경우 TS가 컴파일 대상으로 잘못 포함할 수 있습니다. 안전을 위해 다시 추가해두는 편을 권장합니다.

♻️ 제안
-  "exclude": ["node_modules"]
+  "exclude": ["node_modules", "dist"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/tsconfig.json` at line 7, The tsconfig.json's "exclude" list
currently omits "dist", which can cause built artifacts to be included in
TypeScript compilation later; update the "exclude" array (the "exclude" property
in packages/core/tsconfig.json) to include "dist" alongside "node_modules" so
that compiled output is ignored by tsc and accidental recompilation or caching
issues are prevented.
eslint.config.mjs (3)

24-31: turbo 플러그인 블록에 files 필터가 없어 모든 파일에 적용됩니다.

JSON·MD 등 비타깃 파일까지 매칭되어 불필요한 워크가 생깁니다. JS/TS로 한정하는 편이 좋습니다.

♻️ 제안
   {
+    files: ['**/*.{js,jsx,ts,tsx,mjs,cjs}'],
     plugins: {
       turbo: turboPlugin,
     },
     rules: {
       'turbo/no-undeclared-env-vars': 'warn',
     },
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.mjs` around lines 24 - 31, The turbo plugin configuration
currently applies to all files (plugins: { turbo: turboPlugin }) so the
'turbo/no-undeclared-env-vars' rule runs on non-JS/TS files; update the turbo
plugin options to include a files filter (e.g. patterns like
**/*.{js,jsx,ts,tsx}) so the plugin only targets JS/TS sources and avoids
matching JSON/MD and other non-vital files; adjust the plugin invocation that
defines turboPlugin or the object where
plugins.rules['turbo/no-undeclared-env-vars'] is set to include that files
pattern.

35-43: ecmaVersion: 2020은 base tsconfig의 target: ES2022보다 낮습니다.

base TS 설정은 ES2022를 타겟팅하는데 ESLint 파서는 2020으로 제한되어 있어 ES2021/2022 문법(예: ??=, Object.hasOwn 사용 등 일부 신택스)에서 파싱 이슈가 발생할 수 있습니다. 2022 또는 latest로 맞추는 것을 권장합니다.

♻️ 제안
-      ecmaVersion: 2020,
+      ecmaVersion: 2022,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.mjs` around lines 35 - 43, The ESLint parser's languageOptions
currently set ecmaVersion: 2020 which is older than the project's base TS target
(ES2022) and can cause parsing errors for ES2021/ES2022 syntax; update
languageOptions.ecmaVersion to 2022 or 'latest' (in eslint.config.mjs) so it
matches the base tsconfig target and avoids syntax parsing issues when using
features like ??= or Object.hasOwn.

39-42: tsconfigRootDirprocess.cwd() 기반으로 두면 실행 위치에 따라 동작이 달라질 수 있습니다.

process.cwd()는 ESLint가 어디서 실행되는지에 따라 달라집니다(에디터, 루트에서 직접 실행, 각 워크스페이스에서 실행 등). 모노레포에서는 보통 설정 파일 위치 기준으로 고정하는 편이 더 안정적입니다.

♻️ 제안
+import { fileURLToPath } from 'node:url';
+import { dirname } from 'node:path';
-import process from 'node:process';
...
-        tsconfigRootDir: process.cwd(),
+        tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),

또는 Node 20.11+이면 import.meta.dirname을 직접 사용할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.mjs` around lines 39 - 42, The parserOptions currently sets
tsconfigRootDir to process.cwd(), which makes ESLint behavior depend on where
it’s executed; change tsconfigRootDir to a stable directory resolved from the
config file instead of process.cwd(). For Node ≥20.11 prefer import.meta.dirname
(or new URL('.', import.meta.url)) to derive the config file directory; for
older Node use fileURLToPath(import.meta.url) + path.dirname to compute the
directory. Update the parserOptions.tsconfigRootDir assignment (the
tsconfigRootDir property in the parserOptions block) to use that resolved
directory rather than process.cwd().
apps/web/src/styles/reset.css (1)

86-93: Meyer reset 원본 구조 유지 — stylelint 경고는 의도된 폴백.

font-size: 100% 다음에 font: inherit이 오는 것은 Eric Meyer reset 원본 구조 그대로이며, font 단축 속성을 지원하지 않는 구형 브라우저를 위한 폴백 의도입니다. stylelint의 declaration-block-no-shorthand-property-overrides 경고는 무시해도 무방하나, 노이즈가 거슬리면 해당 셀렉터 블록에 stylelint-disable 주석을 추가하거나 규칙을 완화하는 것을 고려해주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/styles/reset.css` around lines 86 - 93, The video selector
intentionally uses "font-size: 100%" followed by "font: inherit" (Meyer reset)
and triggers stylelint's declaration-block-no-shorthand-property-overrides;
preserve the original property order but silence the linter for this block by
adding a stylelint disable directive specifically for
declaration-block-no-shorthand-property-overrides around the video rule (so the
selector 'video' with properties 'font-size: 100%' and 'font: inherit' remains
unchanged).
apps/web/src/app/layout.tsx (1)

8-24: Pretendard 로컬 폰트 설정은 좋습니다 — variable 옵션 추가를 권장합니다.

display: 'swap', weight: '45 920'(가변 폰트 범위), preload, fallback 스택이 합리적으로 설정되어 있습니다. Tailwind 4.2.2가 설치되어 있고 globals.css의 @theme 블록이 준비된 상태이므로, variable: '--font-pretendard' 옵션을 추가하여 CSS 변수로 노출하면 향후 Tailwind 테마 통합이 용이해집니다.

♻️ 개선 예시
const pretendard = localFont({
  src: '../assets/fonts/PretendardVariable.woff2',
  display: 'swap',
  weight: '45 920',
+  variable: '--font-pretendard',
  preload: true,
  fallback: [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/layout.tsx` around lines 8 - 24, The localFont configuration
for the Pretendard font (const pretendard) should expose a CSS variable so
Tailwind/theme integration can consume it; update the pretendard localFont call
to include the variable option (e.g., variable: '--font-pretendard') while
keeping existing src, display, weight, preload, and fallback settings so the
font is available as the CSS variable for globals.css/@theme and Tailwind.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/app/eslint.config.mjs`:
- Around line 6-12: The config array should spread expoConfig instead of
including it as a single element; update the call to defineConfig (used with
baseConfig and expoConfig) to use ...expoConfig so the exported array flattens
consistently like ...baseConfig; locate the usage of defineConfig and replace
the expoConfig entry with a spread (...expoConfig) to follow the recommended
pattern.

In `@apps/app/scripts/reset-project.js`:
- Line 53: Replace all naked console.log calls in reset-project.js (e.g., the
message using `console.log(\`📁 /\${exampleDir} directory created.\`)` and the
other occurrences at the lines called out) with the appropriate allowed logger:
use console.warn for informational/non-error messages and console.error for
error cases, or wire them into the project's centralized logger if available;
update each call site to keep the same message content but call console.warn or
console.error (or the logger function) instead of console.log so it complies
with the "no console.log" rule.
- Around line 9-11: This file uses Node built-ins (fs, path, readline) but
causes ESLint no-undef/no-require-imports errors; fix it by adding a file-level
ESLint Node environment override at the very top (e.g. add the comment "/*
eslint-env node */") so require/process/console are treated as Node globals;
apply the same change to other affected CLI files that use require (references:
variables fs, path, readline in reset-project.js) or alternatively convert the
module to ESM if preferred by the codebase.

In `@apps/web/src/app/layout.tsx`:
- Line 3: Change the runtime import of React to a type-only import because only
types are used: replace the default import "import React from 'react';" with a
type import and update the component props type to reference React.ReactNode via
the type import (e.g., use "import type { ReactNode } from 'react';" and update
any prop declarations that use React.ReactNode to ReactNode) so the file (and
other occurrences at lines ~31-35) conforms to
`@typescript-eslint/consistent-type-imports` and esbuild optimization.
- Line 5: Import in layout.tsx uses a relative path; change the import of
Providers to use the apps/web absolute path alias. Replace "import Providers
from '../components/Providers';" with "import Providers from
'@/components/Providers';" so the Providers symbol in layout.tsx follows the `@/`*
alias convention for apps/web sources.

In `@apps/web/src/utils/cn.ts`:
- Around line 4-6: The exported utility function cn is declared as a function
declaration but your repo convention requires arrow functions for utils; change
the declaration to an exported arrow function (e.g., export const cn =
(...inputs: ClassValue[]) => twMerge(clsx(inputs));) keeping the same parameters
and return behavior and preserving references to ClassValue, twMerge and clsx so
callers and types remain unchanged.

---

Outside diff comments:
In @.github/workflows/slack-pr-bot.yml:
- Line 224: The first build_summary awk/regex in the "Post parent message" step
still matches "작업 내용" so new PRs show "요약 없음"; update that regex to match "작업
요약" (the same header used by the PR template and the second build_summary at
line 430) and ensure both build_summary occurrences use the identical pattern.
Also remove duplication by extracting the awk snippet into a single reusable
place (e.g., a shared step that exports the parsed summary to GITHUB_ENV or a
composite action) and have both "Post parent message" and "Update parent Slack
message" call that shared logic so future edits stay in sync.

---

Nitpick comments:
In `@apps/app/components/ExternalLink.tsx`:
- Line 7: Change the named export to a default function export to follow the TSX
component rule: replace the named export declaration "export function
ExternalLink(...)" with a default-exported function declaration "export default
function ExternalLink(...)" (keep the same Props signature and body), and update
any imports elsewhere that referenced a named import to use the default import
if necessary.

In `@apps/app/components/HapticTab.tsx`:
- Line 5: The HapticTab component is currently a named export (export function
HapticTab) but must follow the codebase rule of using a default-exported
function component; change its declaration to a default export function (export
default function HapticTab(props: BottomTabBarButtonProps) { ...) and update any
imports/usages that expect the named export to use the default import form.
Ensure the symbol HapticTab is updated consistently across files that import it
so type annotations and props remain unchanged.

In `@apps/web/src/app/layout.tsx`:
- Around line 8-24: The localFont configuration for the Pretendard font (const
pretendard) should expose a CSS variable so Tailwind/theme integration can
consume it; update the pretendard localFont call to include the variable option
(e.g., variable: '--font-pretendard') while keeping existing src, display,
weight, preload, and fallback settings so the font is available as the CSS
variable for globals.css/@theme and Tailwind.

In `@apps/web/src/styles/reset.css`:
- Around line 86-93: The video selector intentionally uses "font-size: 100%"
followed by "font: inherit" (Meyer reset) and triggers stylelint's
declaration-block-no-shorthand-property-overrides; preserve the original
property order but silence the linter for this block by adding a stylelint
disable directive specifically for
declaration-block-no-shorthand-property-overrides around the video rule (so the
selector 'video' with properties 'font-size: 100%' and 'font: inherit' remains
unchanged).

In `@eslint.config.mjs`:
- Around line 24-31: The turbo plugin configuration currently applies to all
files (plugins: { turbo: turboPlugin }) so the 'turbo/no-undeclared-env-vars'
rule runs on non-JS/TS files; update the turbo plugin options to include a files
filter (e.g. patterns like **/*.{js,jsx,ts,tsx}) so the plugin only targets
JS/TS sources and avoids matching JSON/MD and other non-vital files; adjust the
plugin invocation that defines turboPlugin or the object where
plugins.rules['turbo/no-undeclared-env-vars'] is set to include that files
pattern.
- Around line 35-43: The ESLint parser's languageOptions currently set
ecmaVersion: 2020 which is older than the project's base TS target (ES2022) and
can cause parsing errors for ES2021/ES2022 syntax; update
languageOptions.ecmaVersion to 2022 or 'latest' (in eslint.config.mjs) so it
matches the base tsconfig target and avoids syntax parsing issues when using
features like ??= or Object.hasOwn.
- Around line 39-42: The parserOptions currently sets tsconfigRootDir to
process.cwd(), which makes ESLint behavior depend on where it’s executed; change
tsconfigRootDir to a stable directory resolved from the config file instead of
process.cwd(). For Node ≥20.11 prefer import.meta.dirname (or new URL('.',
import.meta.url)) to derive the config file directory; for older Node use
fileURLToPath(import.meta.url) + path.dirname to compute the directory. Update
the parserOptions.tsconfigRootDir assignment (the tsconfigRootDir property in
the parserOptions block) to use that resolved directory rather than
process.cwd().

In `@packages/core/tsconfig.json`:
- Line 7: The tsconfig.json's "exclude" list currently omits "dist", which can
cause built artifacts to be included in TypeScript compilation later; update the
"exclude" array (the "exclude" property in packages/core/tsconfig.json) to
include "dist" alongside "node_modules" so that compiled output is ignored by
tsc and accidental recompilation or caching issues are prevented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9059b513-3352-4906-86b7-ee51440a15a1

📥 Commits

Reviewing files that changed from the base of the PR and between b4ae6d7 and 947fd1c.

⛔ Files ignored due to path filters (12)
  • apps/web/public/file-text.svg is excluded by !**/*.svg
  • apps/web/public/globe.svg is excluded by !**/*.svg
  • apps/web/public/next.svg is excluded by !**/*.svg
  • apps/web/public/turborepo-dark.svg is excluded by !**/*.svg
  • apps/web/public/turborepo-light.svg is excluded by !**/*.svg
  • apps/web/public/vercel.svg is excluded by !**/*.svg
  • apps/web/public/window.svg is excluded by !**/*.svg
  • apps/web/src/app/favicon.ico is excluded by !**/*.ico
  • apps/web/src/app/fonts/GeistMonoVF.woff is excluded by !**/*.woff
  • apps/web/src/app/fonts/GeistVF.woff is excluded by !**/*.woff
  • apps/web/src/assets/fonts/PretendardVariable.woff2 is excluded by !**/*.woff2
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (46)
  • .github/PULL_REQUEST_TEMPLATE.md
  • .github/workflows/slack-pr-bot.yml
  • README.md
  • apps/app/app/_layout.tsx
  • apps/app/app/index.tsx
  • apps/app/app/modal.tsx
  • apps/app/components/ExternalLink.tsx
  • apps/app/components/HapticTab.tsx
  • apps/app/eslint.config.js
  • apps/app/eslint.config.mjs
  • apps/app/scripts/reset-project.js
  • apps/web/eslint.config.mjs
  • apps/web/package.json
  • apps/web/public/.gitkeep
  • apps/web/src/app/layout.tsx
  • apps/web/src/app/page.module.css
  • apps/web/src/app/page.tsx
  • apps/web/src/app/providers.tsx
  • apps/web/src/components/Providers.tsx
  • apps/web/src/styles/globals.css
  • apps/web/src/styles/reset.css
  • apps/web/src/utils/cn.ts
  • apps/web/src/utils/queryClient.ts
  • eslint.config.mjs
  • package.json
  • packages/core/package.json
  • packages/core/src/hooks/.gitkeep
  • packages/core/src/hooks/example.ts
  • packages/core/src/index.ts
  • packages/core/src/types/.gitkeep
  • packages/core/src/types/example.ts
  • packages/core/src/utils/.gitkeep
  • packages/core/src/utils/example.ts
  • packages/core/tsconfig.json
  • packages/eslint-config/README.md
  • packages/eslint-config/base.js
  • packages/eslint-config/next.js
  • packages/eslint-config/package.json
  • packages/eslint-config/react-internal.js
  • packages/typescript-config/nextjs.json
  • packages/ui/eslint.config.mjs
  • packages/ui/package.json
  • packages/ui/src/button.tsx
  • packages/ui/src/card.tsx
  • packages/ui/src/code.tsx
  • pnpm-workspace.yaml
💤 Files with no reviewable changes (18)
  • packages/eslint-config/README.md
  • apps/app/eslint.config.js
  • apps/web/src/app/page.module.css
  • packages/ui/src/code.tsx
  • packages/core/src/hooks/example.ts
  • packages/ui/src/button.tsx
  • packages/core/src/types/example.ts
  • packages/eslint-config/base.js
  • packages/ui/src/card.tsx
  • packages/eslint-config/package.json
  • packages/eslint-config/next.js
  • README.md
  • packages/eslint-config/react-internal.js
  • packages/ui/package.json
  • apps/web/src/app/providers.tsx
  • packages/core/src/utils/example.ts
  • packages/core/src/index.ts
  • packages/ui/eslint.config.mjs

Comment on lines +6 to +12
export default defineConfig([
...baseConfig,
expoConfig,
{
ignores: ['dist/**'],
},
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# eslint-config-expo/flat.js의 export 형태 확인
fd -t f 'flat.js' -p 'node_modules/eslint-config-expo' 2>/dev/null | head -3
fd -t d 'eslint-config-expo' node_modules 2>/dev/null | head
# 패키지 내부 export 형태 살펴보기
rg -n --type=js 'module.exports|export default' -g '**/eslint-config-expo/flat.*' 2>/dev/null

Repository: depromeet/18th-team3-client

Length of output: 53


🏁 Script executed:

cat -n apps/app/eslint.config.mjs

Repository: depromeet/18th-team3-client

Length of output: 406


🏁 Script executed:

# Search for expoConfig definition or import
rg -n 'expoConfig' apps/app/ -A 2 -B 2

Repository: depromeet/18th-team3-client

Length of output: 498


🏁 Script executed:

# Check if there are any other references to eslint-config-expo in the codebase
rg -n 'eslint-config-expo' . --type=json --type=mjs --type=js | head -20

Repository: depromeet/18th-team3-client

Length of output: 99


🌐 Web query:

eslint-config-expo flat.js export format array

💡 Result:

The eslint-config-expo package provides flat ESLint configuration support via "eslint-config-expo/flat", which exports an array of config objects. In your eslint.config.js (flat config file), import it and spread the array into your main config array. Example from official npm docs and Expo PR #34653: const expoConfig = require("eslint-config-expo/flat"); module.exports = [ ...expoConfig, // your other config objects, e.g. { ignores: ["dist/"], }, ]; Some docs show defineConfig([expoConfig, ...]) without spread, which works because ESLint flat configs flatten nested arrays automatically. However, using spread (...expoConfig) is explicitly recommended in the implementation test plan and early canary README for consistency. This is supported in eslint-config-expo ~8.1.0+ (from Feb 2025) and later versions like 55.0.0, for ESLint v9 flat config. Legacy .eslintrc support remains via extends: ["expo"]. No specific "flat.js" file is mentioned; the flat config is provided by the "/flat" export, likely from index/flat.js or similar in the package.

Citations:


expoConfig을 스프레드 연산자로 펼쳐야 합니다.

eslint-config-expo/flat은 config 객체 배열을 내보냅니다. 공식 Expo 문서와 npm 패키지 가이드에서는 배열을 스프레드하여 사용하도록 권장합니다. 현재 코드는 ESLint의 자동 배열 평탄화로 동작하지만, 일관성을 위해 baseConfig처럼 스프레드하는 것이 best practice입니다.

♻️ 수정 제안
 export default defineConfig([
   ...baseConfig,
-  expoConfig,
+  ...expoConfig,
   {
     ignores: ['dist/**'],
   },
 ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/app/eslint.config.mjs` around lines 6 - 12, The config array should
spread expoConfig instead of including it as a single element; update the call
to defineConfig (used with baseConfig and expoConfig) to use ...expoConfig so
the exported array flattens consistently like ...baseConfig; locate the usage of
defineConfig and replace the expoConfig entry with a spread (...expoConfig) to
follow the recommended pattern.

Comment on lines +9 to +11
const fs = require('fs');
const path = require('path');
const readline = require('readline');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

현재 설정 기준으로 이 스크립트는 lint 에러(no-undef / no-require-imports)로 막힐 가능성이 큽니다.

Node CLI 스크립트인데 require/process/console가 전역으로 인식되지 않아 오류가 발생하고 있습니다. 루트 ESLint 통합 목적과 충돌하므로, 이 파일에 Node 환경 오버라이드(또는 ESM 전환)를 명시해 lint를 통과시켜야 합니다.

제안 수정안 (예: 파일 단위 Node 환경 명시)
+/* eslint-env node */
 #!/usr/bin/env node
As per coding guidelines, "If no referenced file content has been included, you can assume that the reference file is not relevant to this review." and project lint rules in this PR context require unified root lint compatibility.

Also applies to: 13-13, 44-45, 53-53, 63-63, 66-66, 69-69, 76-76, 81-81, 86-86, 88-89, 97-97, 108-108

🧰 Tools
🪛 ESLint

[error] 9-9: A require() style import is forbidden.

(@typescript-eslint/no-require-imports)


[error] 9-9: 'require' is not defined.

(no-undef)


[error] 10-10: A require() style import is forbidden.

(@typescript-eslint/no-require-imports)


[error] 10-10: 'require' is not defined.

(no-undef)


[error] 11-11: A require() style import is forbidden.

(@typescript-eslint/no-require-imports)


[error] 11-11: 'require' is not defined.

(no-undef)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/app/scripts/reset-project.js` around lines 9 - 11, This file uses Node
built-ins (fs, path, readline) but causes ESLint no-undef/no-require-imports
errors; fix it by adding a file-level ESLint Node environment override at the
very top (e.g. add the comment "/* eslint-env node */") so
require/process/console are treated as Node globals; apply the same change to
other affected CLI files that use require (references: variables fs, path,
readline in reset-project.js) or alternatively convert the module to ESM if
preferred by the codebase.

await fs.promises.mkdir(exampleDirPath, { recursive: true })
console.log(`📁 /${exampleDir} directory created.`)
await fs.promises.mkdir(exampleDirPath, { recursive: true });
console.log(`📁 /${exampleDir} directory created.`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

console.log 사용은 저장소 규칙 위반입니다.

해당 라인들은 console.warn/console.error 또는 별도 로거로 교체가 필요합니다.

As per coding guidelines, "Disable console.log usage - only console.warn and console.error are permitted".

Also applies to: 63-63, 66-66, 69-69, 76-76, 81-81, 86-86, 88-89, 108-108

🧰 Tools
🪛 ESLint

[error] 53-53: 'console' is not defined.

(no-undef)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/app/scripts/reset-project.js` at line 53, Replace all naked console.log
calls in reset-project.js (e.g., the message using `console.log(\`📁
/\${exampleDir} directory created.\`)` and the other occurrences at the lines
called out) with the appropriate allowed logger: use console.warn for
informational/non-error messages and console.error for error cases, or wire them
into the project's centralized logger if available; update each call site to
keep the same message content but call console.warn or console.error (or the
logger function) instead of console.log so it complies with the "no console.log"
rule.

import React from 'react';
import type { Metadata } from 'next';
import localFont from 'next/font/local';
import React from 'react';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

React는 타입 용도로만 쓰이므로 import type으로 변경.

본문에서는 React.ReactNode만 사용되므로 default 런타임 import 대신 type-only import로 바꿔야 @typescript-eslint/consistent-type-imports 규칙(루트 baseConfig에서 error)에 위배되지 않습니다.

♻️ 제안 변경
-import React from 'react';
+import type { ReactNode } from 'react';

그리고 props 타입도 함께 정리:

 export default function RootLayout({
   children,
 }: Readonly<{
-  children: React.ReactNode;
+  children: ReactNode;
 }>) {

As per coding guidelines: "Use import type for type imports to enable esbuild optimization" 및 @typescript-eslint/consistent-type-imports: error.

Also applies to: 31-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/layout.tsx` at line 3, Change the runtime import of React to
a type-only import because only types are used: replace the default import
"import React from 'react';" with a type import and update the component props
type to reference React.ReactNode via the type import (e.g., use "import type {
ReactNode } from 'react';" and update any prop declarations that use
React.ReactNode to ReactNode) so the file (and other occurrences at lines
~31-35) conforms to `@typescript-eslint/consistent-type-imports` and esbuild
optimization.

import localFont from 'next/font/local';
import React from 'react';

import Providers from '../components/Providers';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Providers import는 절대 경로 alias(@/) 사용.

apps/web/src/** 내부에서는 절대 경로 alias @/*를 사용하도록 되어 있습니다.

♻️ 제안 변경
-import Providers from '../components/Providers';
+import Providers from '@/components/Providers';

As per coding guidelines: "Use @/* path alias for absolute imports within apps/web" (apps/web/src/**/*.{ts,tsx}).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import Providers from '../components/Providers';
import Providers from '@/components/Providers';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/layout.tsx` at line 5, Import in layout.tsx uses a relative
path; change the import of Providers to use the apps/web absolute path alias.
Replace "import Providers from '../components/Providers';" with "import
Providers from '@/components/Providers';" so the Providers symbol in layout.tsx
follows the `@/`* alias convention for apps/web sources.

Comment thread apps/web/src/utils/cn.ts
Comment on lines +4 to +6
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

유틸 함수는 화살표 함수로 선언 권장.

utils/ 디렉토리의 함수는 화살표 함수로 작성하는 컨벤션입니다.

♻️ 제안 변경
-export function cn(...inputs: ClassValue[]) {
-  return twMerge(clsx(inputs));
-}
+export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));

As per coding guidelines: "Use arrow functions for utility and hook functions" (**/{hooks,utils}/**/*.{ts,tsx}).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/utils/cn.ts` around lines 4 - 6, The exported utility function
cn is declared as a function declaration but your repo convention requires arrow
functions for utils; change the declaration to an exported arrow function (e.g.,
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));) keeping
the same parameters and return behavior and preserving references to ClassValue,
twMerge and clsx so callers and types remain unchanged.

@ychany
ychany merged commit 04c8c2d into dev Apr 25, 2026
3 checks passed
@iOdiO89
iOdiO89 deleted the chore/12-web-setting branch June 10, 2026 07:46
sevineleven added a commit that referenced this pull request Jul 4, 2026
* chore: PR 봇을 Slack 에서 Discord 로 이관

slack-pr-bot.yml 제거 + discord-pr-bot.yml 추가 (서버 PIKI-Server 검증본 기반).
리뷰어 멘션(initial_reviewers + requested_reviewers)·연관 이슈(close #N) 로직 동일.

클라 slack 커밋 이력(#14·#17·#18·#20)을 추적해 서버와 다른 클라 특화를 반영:
- 트리거·부모 메시지 조건: ready_for_review → reopened (#17, 기존 slack 봇과 동일).
- 요약 파싱 섹션명: Task → 작업 요약 (클라 PR 템플릿이 '## 작업 요약'). 클라 slack 은 두 요약 함수의
  섹션명이 '작업 내용'/'작업 요약' 으로 불일치(#20 이 한쪽만 수정)해 부모 메시지 요약이 항상 비던 버그가
  있었는데, 이관본은 둘 다 '작업 요약' 으로 통일해 함께 고친다.
- 요약에서 중첩 리스트('  -') skip (#18).

alert-direct-push job 제외(클라 범위 밖). 배포는 클라가 Vercel 이라 이 이관 범위 밖.
secret(DISCORD_BOT_TOKEN 공유·DISCORD_PR_CHANNEL_ID 전용·DISCORD_USER_MAP) 설정 완료.

* PR 봇 부모 메시지 라벨을 갱신 메시지와 같은 볼드·대문자 서식으로 통일

부모(최초) 메시지는 '🔥 연관 이슈'·'**branch**' 처럼 볼드 없는 소문자였고
갱신 메시지는 '🔥 **연관 이슈**'·'**Branch**' 로 달라, 첫 갱신 때 라벨 서식이
바뀌어 보였다. 부모 쪽을 갱신 쪽 서식에 맞춰 일치시킨다.

* synchronize 알림의 커밋 수·최신 커밋을 페이지네이션에 견고하게 계산

PR 커밋 API 는 30개/페이지(오래된→최신 순)라 첫 페이지만 받아 length 로 세면
31개 이상 PR 에서 커밋 수가 30 으로 잘리고, 첫 페이지 .[-1] 도 최신 커밋이 아니었다.
커밋 수는 이벤트 payload 의 .pull_request.commits 로, 최신 커밋은 마지막 페이지
(per_page=1 & page=총개수)로 집어 정확하게 만든다.
iOdiO89 pushed a commit that referenced this pull request Jul 6, 2026
* chore: PR 봇을 Slack 에서 Discord 로 이관

slack-pr-bot.yml 제거 + discord-pr-bot.yml 추가 (서버 PIKI-Server 검증본 기반).
리뷰어 멘션(initial_reviewers + requested_reviewers)·연관 이슈(close #N) 로직 동일.

클라 slack 커밋 이력(#14·#17·#18·#20)을 추적해 서버와 다른 클라 특화를 반영:
- 트리거·부모 메시지 조건: ready_for_review → reopened (#17, 기존 slack 봇과 동일).
- 요약 파싱 섹션명: Task → 작업 요약 (클라 PR 템플릿이 '## 작업 요약'). 클라 slack 은 두 요약 함수의
  섹션명이 '작업 내용'/'작업 요약' 으로 불일치(#20 이 한쪽만 수정)해 부모 메시지 요약이 항상 비던 버그가
  있었는데, 이관본은 둘 다 '작업 요약' 으로 통일해 함께 고친다.
- 요약에서 중첩 리스트('  -') skip (#18).

alert-direct-push job 제외(클라 범위 밖). 배포는 클라가 Vercel 이라 이 이관 범위 밖.
secret(DISCORD_BOT_TOKEN 공유·DISCORD_PR_CHANNEL_ID 전용·DISCORD_USER_MAP) 설정 완료.

* PR 봇 부모 메시지 라벨을 갱신 메시지와 같은 볼드·대문자 서식으로 통일

부모(최초) 메시지는 '🔥 연관 이슈'·'**branch**' 처럼 볼드 없는 소문자였고
갱신 메시지는 '🔥 **연관 이슈**'·'**Branch**' 로 달라, 첫 갱신 때 라벨 서식이
바뀌어 보였다. 부모 쪽을 갱신 쪽 서식에 맞춰 일치시킨다.

* synchronize 알림의 커밋 수·최신 커밋을 페이지네이션에 견고하게 계산

PR 커밋 API 는 30개/페이지(오래된→최신 순)라 첫 페이지만 받아 length 로 세면
31개 이상 PR 에서 커밋 수가 30 으로 잘리고, 첫 페이지 .[-1] 도 최신 커밋이 아니었다.
커밋 수는 이벤트 payload 의 .pull_request.commits 로, 최신 커밋은 마지막 페이지
(per_page=1 & page=총개수)로 집어 정확하게 만든다.
iOdiO89 pushed a commit that referenced this pull request Jul 10, 2026
* chore: PR 봇을 Slack 에서 Discord 로 이관

slack-pr-bot.yml 제거 + discord-pr-bot.yml 추가 (서버 PIKI-Server 검증본 기반).
리뷰어 멘션(initial_reviewers + requested_reviewers)·연관 이슈(close #N) 로직 동일.

클라 slack 커밋 이력(#14·#17·#18·#20)을 추적해 서버와 다른 클라 특화를 반영:
- 트리거·부모 메시지 조건: ready_for_review → reopened (#17, 기존 slack 봇과 동일).
- 요약 파싱 섹션명: Task → 작업 요약 (클라 PR 템플릿이 '## 작업 요약'). 클라 slack 은 두 요약 함수의
  섹션명이 '작업 내용'/'작업 요약' 으로 불일치(#20 이 한쪽만 수정)해 부모 메시지 요약이 항상 비던 버그가
  있었는데, 이관본은 둘 다 '작업 요약' 으로 통일해 함께 고친다.
- 요약에서 중첩 리스트('  -') skip (#18).

alert-direct-push job 제외(클라 범위 밖). 배포는 클라가 Vercel 이라 이 이관 범위 밖.
secret(DISCORD_BOT_TOKEN 공유·DISCORD_PR_CHANNEL_ID 전용·DISCORD_USER_MAP) 설정 완료.

* PR 봇 부모 메시지 라벨을 갱신 메시지와 같은 볼드·대문자 서식으로 통일

부모(최초) 메시지는 '🔥 연관 이슈'·'**branch**' 처럼 볼드 없는 소문자였고
갱신 메시지는 '🔥 **연관 이슈**'·'**Branch**' 로 달라, 첫 갱신 때 라벨 서식이
바뀌어 보였다. 부모 쪽을 갱신 쪽 서식에 맞춰 일치시킨다.

* synchronize 알림의 커밋 수·최신 커밋을 페이지네이션에 견고하게 계산

PR 커밋 API 는 30개/페이지(오래된→최신 순)라 첫 페이지만 받아 length 로 세면
31개 이상 PR 에서 커밋 수가 30 으로 잘리고, 첫 페이지 .[-1] 도 최신 커밋이 아니었다.
커밋 수는 이벤트 payload 의 .pull_request.commits 로, 최신 커밋은 마지막 페이지
(per_page=1 & page=총개수)로 집어 정확하게 만든다.
iOdiO89 added a commit that referenced this pull request Jul 10, 2026
* docs: 역할 분배 문서 추가

* docs: 현재 api 에러 대응 현황 문서 추가

* docs: 에러 처리 정책 문서 추가

* feat: 공통 API 에러 메시지 유틸 추가

* feat: 전역 mutation/query 에러 안전망 도입

* refactor: 비어있는 개별 onError 삭제 (전역 안전망 위임)

* refactor: 개별 훅 5xx 토스트 분기 제거 (이중 토스트 방지)

* PR 봇을 Slack 에서 Discord 로 이관 (#308)

* chore: PR 봇을 Slack 에서 Discord 로 이관

slack-pr-bot.yml 제거 + discord-pr-bot.yml 추가 (서버 PIKI-Server 검증본 기반).
리뷰어 멘션(initial_reviewers + requested_reviewers)·연관 이슈(close #N) 로직 동일.

클라 slack 커밋 이력(#14·#17·#18·#20)을 추적해 서버와 다른 클라 특화를 반영:
- 트리거·부모 메시지 조건: ready_for_review → reopened (#17, 기존 slack 봇과 동일).
- 요약 파싱 섹션명: Task → 작업 요약 (클라 PR 템플릿이 '## 작업 요약'). 클라 slack 은 두 요약 함수의
  섹션명이 '작업 내용'/'작업 요약' 으로 불일치(#20 이 한쪽만 수정)해 부모 메시지 요약이 항상 비던 버그가
  있었는데, 이관본은 둘 다 '작업 요약' 으로 통일해 함께 고친다.
- 요약에서 중첩 리스트('  -') skip (#18).

alert-direct-push job 제외(클라 범위 밖). 배포는 클라가 Vercel 이라 이 이관 범위 밖.
secret(DISCORD_BOT_TOKEN 공유·DISCORD_PR_CHANNEL_ID 전용·DISCORD_USER_MAP) 설정 완료.

* PR 봇 부모 메시지 라벨을 갱신 메시지와 같은 볼드·대문자 서식으로 통일

부모(최초) 메시지는 '🔥 연관 이슈'·'**branch**' 처럼 볼드 없는 소문자였고
갱신 메시지는 '🔥 **연관 이슈**'·'**Branch**' 로 달라, 첫 갱신 때 라벨 서식이
바뀌어 보였다. 부모 쪽을 갱신 쪽 서식에 맞춰 일치시킨다.

* synchronize 알림의 커밋 수·최신 커밋을 페이지네이션에 견고하게 계산

PR 커밋 API 는 30개/페이지(오래된→최신 순)라 첫 페이지만 받아 length 로 세면
31개 이상 PR 에서 커밋 수가 30 으로 잘리고, 첫 페이지 .[-1] 도 최신 커밋이 아니었다.
커밋 수는 이벤트 payload 의 .pull_request.commits 로, 최신 커밋은 마지막 페이지
(per_page=1 & page=총개수)로 집어 정확하게 만든다.

* PR 봇 서버 개선 반영 (상태 리액션·부모 고정·스레드 정리·라벨·헬퍼 공유) (#310)

* PR 봇에 상태 리액션·부모 고정·스레드 정리·라벨 표시·헬퍼 공유 반영

이관 이후 서버 봇에 쌓인 개선을 클라 봇에 맞춰 반영한다.

- 상태 리액션: 부모 메시지에 열림/작업중/머지/종료를 이모지로 전환 표시(429 rate limit 재시도 포함)
- 부모 고정: 열린(리뷰 대기) PR 부모를 채널 상단에 고정, 종료 시 해제
- 스레드 정리: 종료 시 스레드명에 결과 프리픽스(✅/🗑️)+archive+lock
- 부모 카드에 라벨 표시(build_label_text)
- find_meta 가 메타 주석을 JSON 마커로 정확히 매칭 — 파일명을 언급한 리뷰 코멘트를
  집어 THREAD_ID 를 못 읽고 이후 스텝이 전부 skip 되던 버그(머지 알림 누락) 해소
- 헬퍼 4개를 $RUNNER_TEMP 공유 스크립트로 추출해 스텝 간 중복 제거(checkout 불필요)
- 커밋 수는 .pull_request.commits 로 정확화

클라 적응: push 트리거·alert-direct-push 잡 제외, ready_for_review→reopened,
작업 요약 섹션 매칭+중첩 불릿 스킵, 부모 카드 라벨 서식을 갱신 카드와 통일.
최신 커밋 메시지는 contents:read 없이 동작하도록 PR commits 엔드포인트로 조회.

* 재오픈된 PR 의 부모 재고정·스레드 복구 (reopened 흐름 정상화)

reopened 는 메타 주석이 이미 있어 post_parent 스텝이 스킵되는데, 부모 고정·상태
복구가 그 스텝 출력에만 의존해 재오픈 시 깨졌다. reopened 는 서버 봇엔 없고 클라
트리거라, 이 흐름 버그는 클라 전용이다.

- 부모 재고정: pin 스텝의 부모 id 를 post_parent → find_meta 로 폴백하고 조건도
  find_meta.message_id 를 포함하도록 넓혀, 재오픈 시 상단 고정이 복구되게 한다.
- 스레드 복구: 종료 시 archived·locked 로 잠그고 이름에 🗑️ 프리픽스를 다는데,
  재오픈 시 되돌리는 경로가 없어 이후 synchronize 답글이 잠긴 스레드라 실패했다.
  reopened 에 unarchive·unlock + 이름 원복 스텝을 추가한다.

* fix: 게스트 인증 플로우 버그 2건 수정 + 관련 UX 개선 (#291)

* fix: 게스트 MEMBER_ONLY 접근 시 로그인 무한 루프 수정

회원(MEMBER) 토큰일 때만 로그인 페이지를 건너뛰도록 제한.
게스트는 로그인에 남아 archive↔login 무한 루프 방지.

* fix: 게스트 자동 로그인 시 현재 요청부터 token 적용되도록 수정

* refactor: 로그인 약관 안내 문구를 page로 분리

* feat: 게스트 세션 살아있는 경우 게스트 로그인 선택 시 리프레시 진행

* style: 토스트 위치 오류 수정

* feat: 게스트 로그인 redirect path가 /archive인 경우 /home으로 이동 및 안내 토스트 노출

* fix: 앱 부팅 시 refresh cookie 죽는 문제 해결

* fix: proxy에서 앱 auth token 도 처리 가능하도록 수정

* refactor: 앱 token refresh timeout 추가

* fix: 앱 토큰 만료(401) 시 WebView 쿠키도 정리

* fix: 앱 refresh 응답 snake_case 파싱 수정

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* fix: iOS 26 Safari 노치 영역이 흰색으로 보이는 문제 수정 (#293)

Co-authored-by: Jung Sun A <amber0809@naver.com>

* refactor: 로그인 후 리다이렉트 로직 유틸로 추출

- 로그인 성공 후 경로 계산 중복 제거 (getPostLoginRedirectPath)

* fix: 공유 위시 등록 시 refresh 401이면 죽은 토큰 정리

Share Extension이 만료된 토큰으로 재시도를 반복하지 않도록 refresh 401 시 clearTokens 호출

---------

Co-authored-by: 조영찬 <tigerbone@naver.com>

* feat: Sentry 에러 모니터링 도입 (#296)

* feat: web Sentry 에러 모니터링 도입

* feat: app Sentry 에러 모니터링 도입

* feat: web/app sentry 에러 로깅 규약 추가

- app: @sentry/react-native 셋업(_layout init+wrap, metro, 플러그인, eas 환경별 주입)
- 공용 captureError 유틸(web/app) — 태그/컨텍스트 일관 부착
- API 5xx·네트워크 에러 중앙 수집(axios 인터셉터 / app fetch)
- 유저 식별 setUser(id), 세션 만료 시 해제
- error.tsx 캡처, ignoreErrors 노이즈 필터
- app WebView onError/onHttpError, 백그라운드 FCM 핸들러 수집

* test: 에러 검증용 테스트 페이지 생성

* fix: SENTRY_AUTH_TOKEN turbo passThroughEnv 추가 (소스맵 업로드)

* feat: Session Replay 마스킹 완화 (이메일만 마스킹, 나머지 노출)

* feat: API 에러 리포트 제목을 'API {status} {method} {path}' 형식으로 개선

* Revert "test: 에러 검증용 테스트 페이지 생성"

This reverts commit 87f9e53.

* fix: 소셜 로그인 5xx 응답 JSON 파싱 실패 시 Sentry 수집 누락 수정

* fix: 로그아웃 시 Sentry 유저 컨텍스트 해제

* fix: Session Replay 타이핑 입력창만 마스킹 (maskAllInputs)

* fix: 소셜 로그인 2xx 응답 본문 파싱 실패도 Sentry 수집

* feat: mutation 오류 발생 시 Sentry 로깅 추가

* docs: 역할정리 수정

* docs: 역할 관련 문서 업데이트

---------

Co-authored-by: sevineleven <117634128+sevineleven@users.noreply.github.com>
Co-authored-by: 조영찬 <tigerbone@naver.com>
iOdiO89 added a commit that referenced this pull request Jul 11, 2026
* docs: 역할 분배 문서 추가

* docs: 현재 api 에러 대응 현황 문서 추가

* docs: 에러 처리 정책 문서 추가

* feat: 공통 API 에러 메시지 유틸 추가

* feat: 전역 mutation/query 에러 안전망 도입

* refactor: 비어있는 개별 onError 삭제 (전역 안전망 위임)

* refactor: 개별 훅 5xx 토스트 분기 제거 (이중 토스트 방지)

* PR 봇을 Slack 에서 Discord 로 이관 (#308)

* chore: PR 봇을 Slack 에서 Discord 로 이관

slack-pr-bot.yml 제거 + discord-pr-bot.yml 추가 (서버 PIKI-Server 검증본 기반).
리뷰어 멘션(initial_reviewers + requested_reviewers)·연관 이슈(close #N) 로직 동일.

클라 slack 커밋 이력(#14·#17·#18·#20)을 추적해 서버와 다른 클라 특화를 반영:
- 트리거·부모 메시지 조건: ready_for_review → reopened (#17, 기존 slack 봇과 동일).
- 요약 파싱 섹션명: Task → 작업 요약 (클라 PR 템플릿이 '## 작업 요약'). 클라 slack 은 두 요약 함수의
  섹션명이 '작업 내용'/'작업 요약' 으로 불일치(#20 이 한쪽만 수정)해 부모 메시지 요약이 항상 비던 버그가
  있었는데, 이관본은 둘 다 '작업 요약' 으로 통일해 함께 고친다.
- 요약에서 중첩 리스트('  -') skip (#18).

alert-direct-push job 제외(클라 범위 밖). 배포는 클라가 Vercel 이라 이 이관 범위 밖.
secret(DISCORD_BOT_TOKEN 공유·DISCORD_PR_CHANNEL_ID 전용·DISCORD_USER_MAP) 설정 완료.

* PR 봇 부모 메시지 라벨을 갱신 메시지와 같은 볼드·대문자 서식으로 통일

부모(최초) 메시지는 '🔥 연관 이슈'·'**branch**' 처럼 볼드 없는 소문자였고
갱신 메시지는 '🔥 **연관 이슈**'·'**Branch**' 로 달라, 첫 갱신 때 라벨 서식이
바뀌어 보였다. 부모 쪽을 갱신 쪽 서식에 맞춰 일치시킨다.

* synchronize 알림의 커밋 수·최신 커밋을 페이지네이션에 견고하게 계산

PR 커밋 API 는 30개/페이지(오래된→최신 순)라 첫 페이지만 받아 length 로 세면
31개 이상 PR 에서 커밋 수가 30 으로 잘리고, 첫 페이지 .[-1] 도 최신 커밋이 아니었다.
커밋 수는 이벤트 payload 의 .pull_request.commits 로, 최신 커밋은 마지막 페이지
(per_page=1 & page=총개수)로 집어 정확하게 만든다.

* PR 봇 서버 개선 반영 (상태 리액션·부모 고정·스레드 정리·라벨·헬퍼 공유) (#310)

* PR 봇에 상태 리액션·부모 고정·스레드 정리·라벨 표시·헬퍼 공유 반영

이관 이후 서버 봇에 쌓인 개선을 클라 봇에 맞춰 반영한다.

- 상태 리액션: 부모 메시지에 열림/작업중/머지/종료를 이모지로 전환 표시(429 rate limit 재시도 포함)
- 부모 고정: 열린(리뷰 대기) PR 부모를 채널 상단에 고정, 종료 시 해제
- 스레드 정리: 종료 시 스레드명에 결과 프리픽스(✅/🗑️)+archive+lock
- 부모 카드에 라벨 표시(build_label_text)
- find_meta 가 메타 주석을 JSON 마커로 정확히 매칭 — 파일명을 언급한 리뷰 코멘트를
  집어 THREAD_ID 를 못 읽고 이후 스텝이 전부 skip 되던 버그(머지 알림 누락) 해소
- 헬퍼 4개를 $RUNNER_TEMP 공유 스크립트로 추출해 스텝 간 중복 제거(checkout 불필요)
- 커밋 수는 .pull_request.commits 로 정확화

클라 적응: push 트리거·alert-direct-push 잡 제외, ready_for_review→reopened,
작업 요약 섹션 매칭+중첩 불릿 스킵, 부모 카드 라벨 서식을 갱신 카드와 통일.
최신 커밋 메시지는 contents:read 없이 동작하도록 PR commits 엔드포인트로 조회.

* 재오픈된 PR 의 부모 재고정·스레드 복구 (reopened 흐름 정상화)

reopened 는 메타 주석이 이미 있어 post_parent 스텝이 스킵되는데, 부모 고정·상태
복구가 그 스텝 출력에만 의존해 재오픈 시 깨졌다. reopened 는 서버 봇엔 없고 클라
트리거라, 이 흐름 버그는 클라 전용이다.

- 부모 재고정: pin 스텝의 부모 id 를 post_parent → find_meta 로 폴백하고 조건도
  find_meta.message_id 를 포함하도록 넓혀, 재오픈 시 상단 고정이 복구되게 한다.
- 스레드 복구: 종료 시 archived·locked 로 잠그고 이름에 🗑️ 프리픽스를 다는데,
  재오픈 시 되돌리는 경로가 없어 이후 synchronize 답글이 잠긴 스레드라 실패했다.
  reopened 에 unarchive·unlock + 이름 원복 스텝을 추가한다.

* fix: 게스트 인증 플로우 버그 2건 수정 + 관련 UX 개선 (#291)

* fix: 게스트 MEMBER_ONLY 접근 시 로그인 무한 루프 수정

회원(MEMBER) 토큰일 때만 로그인 페이지를 건너뛰도록 제한.
게스트는 로그인에 남아 archive↔login 무한 루프 방지.

* fix: 게스트 자동 로그인 시 현재 요청부터 token 적용되도록 수정

* refactor: 로그인 약관 안내 문구를 page로 분리

* feat: 게스트 세션 살아있는 경우 게스트 로그인 선택 시 리프레시 진행

* style: 토스트 위치 오류 수정

* feat: 게스트 로그인 redirect path가 /archive인 경우 /home으로 이동 및 안내 토스트 노출

* fix: 앱 부팅 시 refresh cookie 죽는 문제 해결

* fix: proxy에서 앱 auth token 도 처리 가능하도록 수정

* refactor: 앱 token refresh timeout 추가

* fix: 앱 토큰 만료(401) 시 WebView 쿠키도 정리

* fix: 앱 refresh 응답 snake_case 파싱 수정

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* fix: iOS 26 Safari 노치 영역이 흰색으로 보이는 문제 수정 (#293)

Co-authored-by: Jung Sun A <amber0809@naver.com>

* refactor: 로그인 후 리다이렉트 로직 유틸로 추출

- 로그인 성공 후 경로 계산 중복 제거 (getPostLoginRedirectPath)

* fix: 공유 위시 등록 시 refresh 401이면 죽은 토큰 정리

Share Extension이 만료된 토큰으로 재시도를 반복하지 않도록 refresh 401 시 clearTokens 호출

---------

Co-authored-by: 조영찬 <tigerbone@naver.com>

* feat: Sentry 에러 모니터링 도입 (#296)

* feat: web Sentry 에러 모니터링 도입

* feat: app Sentry 에러 모니터링 도입

* feat: web/app sentry 에러 로깅 규약 추가

- app: @sentry/react-native 셋업(_layout init+wrap, metro, 플러그인, eas 환경별 주입)
- 공용 captureError 유틸(web/app) — 태그/컨텍스트 일관 부착
- API 5xx·네트워크 에러 중앙 수집(axios 인터셉터 / app fetch)
- 유저 식별 setUser(id), 세션 만료 시 해제
- error.tsx 캡처, ignoreErrors 노이즈 필터
- app WebView onError/onHttpError, 백그라운드 FCM 핸들러 수집

* test: 에러 검증용 테스트 페이지 생성

* fix: SENTRY_AUTH_TOKEN turbo passThroughEnv 추가 (소스맵 업로드)

* feat: Session Replay 마스킹 완화 (이메일만 마스킹, 나머지 노출)

* feat: API 에러 리포트 제목을 'API {status} {method} {path}' 형식으로 개선

* Revert "test: 에러 검증용 테스트 페이지 생성"

This reverts commit 87f9e53.

* fix: 소셜 로그인 5xx 응답 JSON 파싱 실패 시 Sentry 수집 누락 수정

* fix: 로그아웃 시 Sentry 유저 컨텍스트 해제

* fix: Session Replay 타이핑 입력창만 마스킹 (maskAllInputs)

* fix: 소셜 로그인 2xx 응답 본문 파싱 실패도 Sentry 수집

* feat: mutation 오류 발생 시 Sentry 로깅 추가

* docs: 역할정리 수정

* docs: 역할 관련 문서 업데이트

---------

Co-authored-by: sevineleven <117634128+sevineleven@users.noreply.github.com>
Co-authored-by: 조영찬 <tigerbone@naver.com>
iOdiO89 added a commit that referenced this pull request Jul 11, 2026
* chore: iOS Smart Banner 설정

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: iOS Universal Link 설정 및 딥링크 라우팅 확장

- AASA 파일 추가 (콜백/api 경로는 exclude)
- iOS associatedDomains 설정
- Universal Link/Smart Banner app-argument의 https URL을 WebView 경로로 라우팅

* feat: android app link 관련 config 추가

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* fix: iOS 26 Safari 노치 영역이 흰색으로 보이는 문제 수정 (#293)

Co-authored-by: Jung Sun A <amber0809@naver.com>

* PR 봇을 Slack 에서 Discord 로 이관 (#308)

* chore: PR 봇을 Slack 에서 Discord 로 이관

slack-pr-bot.yml 제거 + discord-pr-bot.yml 추가 (서버 PIKI-Server 검증본 기반).
리뷰어 멘션(initial_reviewers + requested_reviewers)·연관 이슈(close #N) 로직 동일.

클라 slack 커밋 이력(#14·#17·#18·#20)을 추적해 서버와 다른 클라 특화를 반영:
- 트리거·부모 메시지 조건: ready_for_review → reopened (#17, 기존 slack 봇과 동일).
- 요약 파싱 섹션명: Task → 작업 요약 (클라 PR 템플릿이 '## 작업 요약'). 클라 slack 은 두 요약 함수의
  섹션명이 '작업 내용'/'작업 요약' 으로 불일치(#20 이 한쪽만 수정)해 부모 메시지 요약이 항상 비던 버그가
  있었는데, 이관본은 둘 다 '작업 요약' 으로 통일해 함께 고친다.
- 요약에서 중첩 리스트('  -') skip (#18).

alert-direct-push job 제외(클라 범위 밖). 배포는 클라가 Vercel 이라 이 이관 범위 밖.
secret(DISCORD_BOT_TOKEN 공유·DISCORD_PR_CHANNEL_ID 전용·DISCORD_USER_MAP) 설정 완료.

* PR 봇 부모 메시지 라벨을 갱신 메시지와 같은 볼드·대문자 서식으로 통일

부모(최초) 메시지는 '🔥 연관 이슈'·'**branch**' 처럼 볼드 없는 소문자였고
갱신 메시지는 '🔥 **연관 이슈**'·'**Branch**' 로 달라, 첫 갱신 때 라벨 서식이
바뀌어 보였다. 부모 쪽을 갱신 쪽 서식에 맞춰 일치시킨다.

* synchronize 알림의 커밋 수·최신 커밋을 페이지네이션에 견고하게 계산

PR 커밋 API 는 30개/페이지(오래된→최신 순)라 첫 페이지만 받아 length 로 세면
31개 이상 PR 에서 커밋 수가 30 으로 잘리고, 첫 페이지 .[-1] 도 최신 커밋이 아니었다.
커밋 수는 이벤트 payload 의 .pull_request.commits 로, 최신 커밋은 마지막 페이지
(per_page=1 & page=총개수)로 집어 정확하게 만든다.

* PR 봇 서버 개선 반영 (상태 리액션·부모 고정·스레드 정리·라벨·헬퍼 공유) (#310)

* PR 봇에 상태 리액션·부모 고정·스레드 정리·라벨 표시·헬퍼 공유 반영

이관 이후 서버 봇에 쌓인 개선을 클라 봇에 맞춰 반영한다.

- 상태 리액션: 부모 메시지에 열림/작업중/머지/종료를 이모지로 전환 표시(429 rate limit 재시도 포함)
- 부모 고정: 열린(리뷰 대기) PR 부모를 채널 상단에 고정, 종료 시 해제
- 스레드 정리: 종료 시 스레드명에 결과 프리픽스(✅/🗑️)+archive+lock
- 부모 카드에 라벨 표시(build_label_text)
- find_meta 가 메타 주석을 JSON 마커로 정확히 매칭 — 파일명을 언급한 리뷰 코멘트를
  집어 THREAD_ID 를 못 읽고 이후 스텝이 전부 skip 되던 버그(머지 알림 누락) 해소
- 헬퍼 4개를 $RUNNER_TEMP 공유 스크립트로 추출해 스텝 간 중복 제거(checkout 불필요)
- 커밋 수는 .pull_request.commits 로 정확화

클라 적응: push 트리거·alert-direct-push 잡 제외, ready_for_review→reopened,
작업 요약 섹션 매칭+중첩 불릿 스킵, 부모 카드 라벨 서식을 갱신 카드와 통일.
최신 커밋 메시지는 contents:read 없이 동작하도록 PR commits 엔드포인트로 조회.

* 재오픈된 PR 의 부모 재고정·스레드 복구 (reopened 흐름 정상화)

reopened 는 메타 주석이 이미 있어 post_parent 스텝이 스킵되는데, 부모 고정·상태
복구가 그 스텝 출력에만 의존해 재오픈 시 깨졌다. reopened 는 서버 봇엔 없고 클라
트리거라, 이 흐름 버그는 클라 전용이다.

- 부모 재고정: pin 스텝의 부모 id 를 post_parent → find_meta 로 폴백하고 조건도
  find_meta.message_id 를 포함하도록 넓혀, 재오픈 시 상단 고정이 복구되게 한다.
- 스레드 복구: 종료 시 archived·locked 로 잠그고 이름에 🗑️ 프리픽스를 다는데,
  재오픈 시 되돌리는 경로가 없어 이후 synchronize 답글이 잠긴 스레드라 실패했다.
  reopened 에 unarchive·unlock + 이름 원복 스텝을 추가한다.

* fix: 게스트 인증 플로우 버그 2건 수정 + 관련 UX 개선 (#291)

* fix: 게스트 MEMBER_ONLY 접근 시 로그인 무한 루프 수정

회원(MEMBER) 토큰일 때만 로그인 페이지를 건너뛰도록 제한.
게스트는 로그인에 남아 archive↔login 무한 루프 방지.

* fix: 게스트 자동 로그인 시 현재 요청부터 token 적용되도록 수정

* refactor: 로그인 약관 안내 문구를 page로 분리

* feat: 게스트 세션 살아있는 경우 게스트 로그인 선택 시 리프레시 진행

* style: 토스트 위치 오류 수정

* feat: 게스트 로그인 redirect path가 /archive인 경우 /home으로 이동 및 안내 토스트 노출

* fix: 앱 부팅 시 refresh cookie 죽는 문제 해결

* fix: proxy에서 앱 auth token 도 처리 가능하도록 수정

* refactor: 앱 token refresh timeout 추가

* fix: 앱 토큰 만료(401) 시 WebView 쿠키도 정리

* fix: 앱 refresh 응답 snake_case 파싱 수정

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* fix: iOS 26 Safari 노치 영역이 흰색으로 보이는 문제 수정 (#293)

Co-authored-by: Jung Sun A <amber0809@naver.com>

* refactor: 로그인 후 리다이렉트 로직 유틸로 추출

- 로그인 성공 후 경로 계산 중복 제거 (getPostLoginRedirectPath)

* fix: 공유 위시 등록 시 refresh 401이면 죽은 토큰 정리

Share Extension이 만료된 토큰으로 재시도를 반복하지 않도록 refresh 401 시 clearTokens 호출

---------

Co-authored-by: 조영찬 <tigerbone@naver.com>

* feat: Sentry 에러 모니터링 도입 (#296)

* feat: web Sentry 에러 모니터링 도입

* feat: app Sentry 에러 모니터링 도입

* feat: web/app sentry 에러 로깅 규약 추가

- app: @sentry/react-native 셋업(_layout init+wrap, metro, 플러그인, eas 환경별 주입)
- 공용 captureError 유틸(web/app) — 태그/컨텍스트 일관 부착
- API 5xx·네트워크 에러 중앙 수집(axios 인터셉터 / app fetch)
- 유저 식별 setUser(id), 세션 만료 시 해제
- error.tsx 캡처, ignoreErrors 노이즈 필터
- app WebView onError/onHttpError, 백그라운드 FCM 핸들러 수집

* test: 에러 검증용 테스트 페이지 생성

* fix: SENTRY_AUTH_TOKEN turbo passThroughEnv 추가 (소스맵 업로드)

* feat: Session Replay 마스킹 완화 (이메일만 마스킹, 나머지 노출)

* feat: API 에러 리포트 제목을 'API {status} {method} {path}' 형식으로 개선

* Revert "test: 에러 검증용 테스트 페이지 생성"

This reverts commit 87f9e53.

* fix: 소셜 로그인 5xx 응답 JSON 파싱 실패 시 Sentry 수집 누락 수정

* fix: 로그아웃 시 Sentry 유저 컨텍스트 해제

* fix: Session Replay 타이핑 입력창만 마스킹 (maskAllInputs)

* fix: 소셜 로그인 2xx 응답 본문 파싱 실패도 Sentry 수집

* fix: 페이지별 토스트 위치 커스터마이즈 (탭바 페이지 토스트 겹침 수정) (#309)

* feat: 토스트 offset 페이지별 override 메커니즘 추가

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: 홈 탭바 위로 토스트 위치 조정

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: 보관 페이지 하단 바 위로 토스트 위치 조정

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: 토스트 offset 수치 조정

* refactor: 토스트 offset zustand 스토어를 CSS :has() 마커 방식으로 대체

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat: 딥링크 전환 로딩 오버레이 추가

* feat: 앱 링크 접속시 warm start 기능 추가

* PR 봇 스레드를 팀 전원이 볼 수 있게 (스레드 멤버 자동 추가) (#313)

* PR 봇 스레드에 팀원 자동 추가로 팀 전원 사이드바 노출

Discord 공개 스레드는 멤버(생성자·초대·멘션된 사람)만 사이드바에 뜬다. 부모 카드가
작성자만 @멘션해 작성자만 자기 PR 스레드가 보이고 남의 스레드는 안 떴다.

- 스레드 생성 직후 DISCORD_USER_MAP 의 팀원 전원을 thread-members 로 추가해 새 PR
  스레드가 팀 전원 사이드바에 뜨게 한다(멘션과 달리 조용히 목록에만 올림).
- 이 변경 이전 열린 PR 스레드용으로 backfill-thread-members 잡을 추가한다
  (workflow_dispatch 수동 실행). notify-discord 는 pull_request 로 가드해 dispatch 시엔 안 돈다.

* 연관 이슈 링크의 Discord 임베드 카드 억제

부모 카드의 연관 이슈 링크가 GitHub 임베드 카드로 커지게 떠서 본문(작업 요약 등)을
아래로 밀어냈다. 이슈 URL 을 <> 로 감싸 임베드만 억제하고 클릭 링크는 유지한다.
PR 타이틀 링크는 그대로 둬 PR 카드는 계속 뜬다.

* 작업 요약 추출이 STAR 의 Task 섹션도 인식

FE 팀 PR 은 ## 작업 요약, 서버 STAR 로 쓴 PR 은 ## Task 를 쓴다. build_summary 가
작업 요약만 찾아 STAR PR 은 '요약 없음'이 됐다. 두 섹션명을 모두 인식하게 해
어느 관례로 쓰든 요약이 채워지게 한다.

* 작업 요약의 Task 섹션 인식 제거

FE 팀은 ## 작업 요약을 쓰고 봇이 이미 이를 매칭하므로, STAR 의 ## Task 인식 추가는
불필요해 되돌린다. 요약은 PR 본문에 ## 작업 요약 을 두는 팀 관례로 채운다.

* PR 봇 재싱크: 머지 스레드 잠금 해제 + 종합 백필 (서버 #703 반영) (#315)

서버 봇(#703)과 갈라진 두 가지를 클라에도 반영해 동작을 일치시킨다.

- 머지 시 close 정리에서 locked 제거(archived 만) — 닫히되 글을 쓸 수 있게(archived 만이라
  글을 쓰면 다시 열림). 머지된 PR 스레드가 잠겨 글을 못 쓰던 문제 해소.
- 백필 잡을 종합형(backfill-threads)으로 교체: 열린 PR→팀원 추가+열림, 머지 PR→✅+닫힘,
  미머지 닫힘→🗑️+닫힘. 기존엔 열린 스레드 팀원 추가만 했다. 과거 find_meta 버그로 안 닫힌
  스레드까지 소급 정리(멱등).

* refactor: 링크 입력창 모바일 키보드를 일반 텍스트 키보드로 변경 (#325)

* feat: 전역 API 에러 안전망 도입 및 개별 훅 에러 처리 정리 (#311)

* docs: 역할 분배 문서 추가

* docs: 현재 api 에러 대응 현황 문서 추가

* docs: 에러 처리 정책 문서 추가

* feat: 공통 API 에러 메시지 유틸 추가

* feat: 전역 mutation/query 에러 안전망 도입

* refactor: 비어있는 개별 onError 삭제 (전역 안전망 위임)

* refactor: 개별 훅 5xx 토스트 분기 제거 (이중 토스트 방지)

* PR 봇을 Slack 에서 Discord 로 이관 (#308)

* chore: PR 봇을 Slack 에서 Discord 로 이관

slack-pr-bot.yml 제거 + discord-pr-bot.yml 추가 (서버 PIKI-Server 검증본 기반).
리뷰어 멘션(initial_reviewers + requested_reviewers)·연관 이슈(close #N) 로직 동일.

클라 slack 커밋 이력(#14·#17·#18·#20)을 추적해 서버와 다른 클라 특화를 반영:
- 트리거·부모 메시지 조건: ready_for_review → reopened (#17, 기존 slack 봇과 동일).
- 요약 파싱 섹션명: Task → 작업 요약 (클라 PR 템플릿이 '## 작업 요약'). 클라 slack 은 두 요약 함수의
  섹션명이 '작업 내용'/'작업 요약' 으로 불일치(#20 이 한쪽만 수정)해 부모 메시지 요약이 항상 비던 버그가
  있었는데, 이관본은 둘 다 '작업 요약' 으로 통일해 함께 고친다.
- 요약에서 중첩 리스트('  -') skip (#18).

alert-direct-push job 제외(클라 범위 밖). 배포는 클라가 Vercel 이라 이 이관 범위 밖.
secret(DISCORD_BOT_TOKEN 공유·DISCORD_PR_CHANNEL_ID 전용·DISCORD_USER_MAP) 설정 완료.

* PR 봇 부모 메시지 라벨을 갱신 메시지와 같은 볼드·대문자 서식으로 통일

부모(최초) 메시지는 '🔥 연관 이슈'·'**branch**' 처럼 볼드 없는 소문자였고
갱신 메시지는 '🔥 **연관 이슈**'·'**Branch**' 로 달라, 첫 갱신 때 라벨 서식이
바뀌어 보였다. 부모 쪽을 갱신 쪽 서식에 맞춰 일치시킨다.

* synchronize 알림의 커밋 수·최신 커밋을 페이지네이션에 견고하게 계산

PR 커밋 API 는 30개/페이지(오래된→최신 순)라 첫 페이지만 받아 length 로 세면
31개 이상 PR 에서 커밋 수가 30 으로 잘리고, 첫 페이지 .[-1] 도 최신 커밋이 아니었다.
커밋 수는 이벤트 payload 의 .pull_request.commits 로, 최신 커밋은 마지막 페이지
(per_page=1 & page=총개수)로 집어 정확하게 만든다.

* PR 봇 서버 개선 반영 (상태 리액션·부모 고정·스레드 정리·라벨·헬퍼 공유) (#310)

* PR 봇에 상태 리액션·부모 고정·스레드 정리·라벨 표시·헬퍼 공유 반영

이관 이후 서버 봇에 쌓인 개선을 클라 봇에 맞춰 반영한다.

- 상태 리액션: 부모 메시지에 열림/작업중/머지/종료를 이모지로 전환 표시(429 rate limit 재시도 포함)
- 부모 고정: 열린(리뷰 대기) PR 부모를 채널 상단에 고정, 종료 시 해제
- 스레드 정리: 종료 시 스레드명에 결과 프리픽스(✅/🗑️)+archive+lock
- 부모 카드에 라벨 표시(build_label_text)
- find_meta 가 메타 주석을 JSON 마커로 정확히 매칭 — 파일명을 언급한 리뷰 코멘트를
  집어 THREAD_ID 를 못 읽고 이후 스텝이 전부 skip 되던 버그(머지 알림 누락) 해소
- 헬퍼 4개를 $RUNNER_TEMP 공유 스크립트로 추출해 스텝 간 중복 제거(checkout 불필요)
- 커밋 수는 .pull_request.commits 로 정확화

클라 적응: push 트리거·alert-direct-push 잡 제외, ready_for_review→reopened,
작업 요약 섹션 매칭+중첩 불릿 스킵, 부모 카드 라벨 서식을 갱신 카드와 통일.
최신 커밋 메시지는 contents:read 없이 동작하도록 PR commits 엔드포인트로 조회.

* 재오픈된 PR 의 부모 재고정·스레드 복구 (reopened 흐름 정상화)

reopened 는 메타 주석이 이미 있어 post_parent 스텝이 스킵되는데, 부모 고정·상태
복구가 그 스텝 출력에만 의존해 재오픈 시 깨졌다. reopened 는 서버 봇엔 없고 클라
트리거라, 이 흐름 버그는 클라 전용이다.

- 부모 재고정: pin 스텝의 부모 id 를 post_parent → find_meta 로 폴백하고 조건도
  find_meta.message_id 를 포함하도록 넓혀, 재오픈 시 상단 고정이 복구되게 한다.
- 스레드 복구: 종료 시 archived·locked 로 잠그고 이름에 🗑️ 프리픽스를 다는데,
  재오픈 시 되돌리는 경로가 없어 이후 synchronize 답글이 잠긴 스레드라 실패했다.
  reopened 에 unarchive·unlock + 이름 원복 스텝을 추가한다.

* fix: 게스트 인증 플로우 버그 2건 수정 + 관련 UX 개선 (#291)

* fix: 게스트 MEMBER_ONLY 접근 시 로그인 무한 루프 수정

회원(MEMBER) 토큰일 때만 로그인 페이지를 건너뛰도록 제한.
게스트는 로그인에 남아 archive↔login 무한 루프 방지.

* fix: 게스트 자동 로그인 시 현재 요청부터 token 적용되도록 수정

* refactor: 로그인 약관 안내 문구를 page로 분리

* feat: 게스트 세션 살아있는 경우 게스트 로그인 선택 시 리프레시 진행

* style: 토스트 위치 오류 수정

* feat: 게스트 로그인 redirect path가 /archive인 경우 /home으로 이동 및 안내 토스트 노출

* fix: 앱 부팅 시 refresh cookie 죽는 문제 해결

* fix: proxy에서 앱 auth token 도 처리 가능하도록 수정

* refactor: 앱 token refresh timeout 추가

* fix: 앱 토큰 만료(401) 시 WebView 쿠키도 정리

* fix: 앱 refresh 응답 snake_case 파싱 수정

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* chore: proxy.ts refresh 401 임시 안전망 제거 (백엔드 rotation grace 적용) (#295)

* fix: iOS 26 Safari 노치 영역이 흰색으로 보이는 문제 수정 (#293)

Co-authored-by: Jung Sun A <amber0809@naver.com>

* refactor: 로그인 후 리다이렉트 로직 유틸로 추출

- 로그인 성공 후 경로 계산 중복 제거 (getPostLoginRedirectPath)

* fix: 공유 위시 등록 시 refresh 401이면 죽은 토큰 정리

Share Extension이 만료된 토큰으로 재시도를 반복하지 않도록 refresh 401 시 clearTokens 호출

---------

Co-authored-by: 조영찬 <tigerbone@naver.com>

* feat: Sentry 에러 모니터링 도입 (#296)

* feat: web Sentry 에러 모니터링 도입

* feat: app Sentry 에러 모니터링 도입

* feat: web/app sentry 에러 로깅 규약 추가

- app: @sentry/react-native 셋업(_layout init+wrap, metro, 플러그인, eas 환경별 주입)
- 공용 captureError 유틸(web/app) — 태그/컨텍스트 일관 부착
- API 5xx·네트워크 에러 중앙 수집(axios 인터셉터 / app fetch)
- 유저 식별 setUser(id), 세션 만료 시 해제
- error.tsx 캡처, ignoreErrors 노이즈 필터
- app WebView onError/onHttpError, 백그라운드 FCM 핸들러 수집

* test: 에러 검증용 테스트 페이지 생성

* fix: SENTRY_AUTH_TOKEN turbo passThroughEnv 추가 (소스맵 업로드)

* feat: Session Replay 마스킹 완화 (이메일만 마스킹, 나머지 노출)

* feat: API 에러 리포트 제목을 'API {status} {method} {path}' 형식으로 개선

* Revert "test: 에러 검증용 테스트 페이지 생성"

This reverts commit 87f9e53.

* fix: 소셜 로그인 5xx 응답 JSON 파싱 실패 시 Sentry 수집 누락 수정

* fix: 로그아웃 시 Sentry 유저 컨텍스트 해제

* fix: Session Replay 타이핑 입력창만 마스킹 (maskAllInputs)

* fix: 소셜 로그인 2xx 응답 본문 파싱 실패도 Sentry 수집

* feat: mutation 오류 발생 시 Sentry 로깅 추가

* docs: 역할정리 수정

* docs: 역할 관련 문서 업데이트

---------

Co-authored-by: sevineleven <117634128+sevineleven@users.noreply.github.com>
Co-authored-by: 조영찬 <tigerbone@naver.com>

* refactor: 게스트 참여 dead code 제거 (#321)

* refactor: 게스트 참여 dead code 제거

* refactor: 게스트 참여 dead code 제거 2

---------

Co-authored-by: Jung Sun A <amber0809@naver.com>

* chore: update app version

* chore: eas config 삭제

* refactor: 링크 입력 시 텍스트에서 URL 자동 추출 (#323)

* refactor: 링크 입력 시 텍스트에서 URL 자동 추출

* fix: URL 패턴 anchor 추가로 트레일링 텍스트 제출 방지

* fix: warm start 딥링크 보류 처리

* fix: 앱 내비게이션 타이머 갱신

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: 조영찬 <tigerbone@naver.com>
Co-authored-by: sevineleven <117634128+sevineleven@users.noreply.github.com>
Co-authored-by: kanghaeun <145974230+kanghaeun@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web 추가 기초 설정

4 participants