Skip to content

Comments

Feat(client): tree 레벨 컴포넌트 구현#54

Merged
jjangminii merged 13 commits intodevelopfrom
feat/#53/tree-ststus
Sep 5, 2025
Merged

Feat(client): tree 레벨 컴포넌트 구현#54
jjangminii merged 13 commits intodevelopfrom
feat/#53/tree-ststus

Conversation

@jjangminii
Copy link
Collaborator

@jjangminii jjangminii commented Sep 3, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

  • tree ststus 컴포넌트 구현
  • tree ststus level 유틸 분리
  • 레벨 info카드

⭐ PR Point (To Reviewer)

📷 Screenshot

image image

Summary by CodeRabbit

  • 신규 기능
    • 레벨 페이지의 레이아웃이 업데이트되어 배경·레이아웃 스타일이 적용되었습니다.
    • 레벨 가이드 카드(레벨별 정보) 추가로 레벨 이름·범위·아이콘과 안내 문구를 제공합니다.
    • 나무 진행 카드 추가: 도토리 수 기반 레벨 계산, 진행 퍼센트·잔여량 표시 및 ARIA 레이블로 접근성 개선.
  • 기타
    • 디자인 시스템에 레벨 툴팁용 아이콘 5종이 추가되었습니다.

@jjangminii jjangminii linked an issue Sep 3, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Sep 3, 2025

Walkthrough

Level 페이지 레イ아웃을 수직 스택으로 변경하고, 도토리 기반 레벨/진행률 유틸(getTreeLevel)과 이를 사용하는 TreeStatusCardLevelInfoCard 컴포넌트를 추가했으며 디자인시스템 아이콘명(tooltip_1~tooltip_5)과 IconName 타입을 재수출했습니다.

Changes

Cohort / File(s) Summary of Changes
Level 페이지 레이아웃
apps/client/src/pages/level/Level.tsx
플레이스홀더 제거; 페이지 루트에 bg-secondary flex flex-col gap-[2rem] p-[1rem] 클래스 적용 및 LevelInfoCard/TreeStatusCard 렌더 구조 반영.
트리 상태 카드 컴포넌트
apps/client/src/pages/level/components/TreeStatusCard.tsx
신규 컴포넌트 추가: TreeStatusCardProps { acorns: number }. getTreeLevel로 레벨·진행률 계산 후 Level·Progress 렌더(aria-label 포함).
레벨 정보 카드 컴포넌트
apps/client/src/pages/level/components/LevelInfoCard.tsx
신규 컴포넌트 추가: TREE_LEVEL_TABLE 역순 표시, 레벨-툴팁 아이콘 매핑, 레벨 배지·이름·구간 라벨 및 안내 배너 렌더링.
트리 레벨 유틸리티
apps/client/src/pages/level/utils/treeLevel.ts
신규 모듈 추가: TREE_LEVEL_TABLE, 타입(TreeLevel, TreeLevelRow, TreeLevelResult) 및 getTreeLevel(acorns) 구현(현재 레벨·progressToNext·nextMin·remaining 계산).
디자인 시스템 아이콘명 변경
packages/design-system/src/icons/iconNames.ts
iconNames'tooltip_1' ~ 'tooltip_5' 5개 리터럴 추가(IconName 유니언 확장).
디자인 시스템 아이콘 타입 재수출
packages/design-system/src/icons/index.ts
타입 전용 재수출 추가: export type { IconName } from './iconNames'.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as 사용자
  participant LevelPage as Level 페이지
  participant Info as LevelInfoCard
  participant Card as TreeStatusCard
  participant Util as getTreeLevel
  participant DS as DesignSystem(Progress/Level/Icon)

  User->>LevelPage: 페이지 요청
  LevelPage->>Info: render LevelInfoCard
  loop 각 도토리 값에 대해
    LevelPage->>Card: render(acorns)
    Card->>Util: getTreeLevel(acorns)
    Util-->>Card: { level, name, progressToNext, nextMin, ... }
    Card->>DS: render Progress(value=progressToNext*100, variant="tree")
    Card-->>LevelPage: 카드 렌더 완료
  end
  LevelPage-->>User: 렌더된 페이지 응답
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
Progress 컴포넌트 구현 및 variant/동작 (#25) 본 PR에 Progress 컴포넌트의 정의·variant 구현 변경은 포함되어 있지 않음.
마이페이지 트리 상태 컴포넌트 구현 (#53)
칩/트리 Progress 연동 및 진행률 표시 (#53)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
아이콘 이름 추가: 'tooltip_1'~'tooltip_5' (packages/design-system/src/icons/iconNames.ts) 아이콘 리터럴 추가는 트리 컴포넌트에 필요한 아이콘명 확장이지만, #25/#53의 핵심 구현 요구(Progress 컴포넌트 구현)는 아님.
타입 재수출 추가: export type { IconName } (packages/design-system/src/icons/index.ts) 타입 재수출은 API 노출 확대에 해당하나 linked issue 목표의 코드(Progress 구현)와 직접적인 관련 없음.

Possibly related PRs

Suggested labels

🛠️ Feature, frontend

Suggested reviewers

  • jllee000
  • constantly-dev

Poem

"도토리 톡톡 쌓이면 숲이 자라요,
카드마다 빛나는 레벨과 바,
토끼가 깡총 와서 축하해요 🐇
작은 수치도 반짝이며,
함께 자라는 코드의 봄."

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#53/tree-ststus

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Sep 3, 2025
@github-actions github-actions bot requested a review from jllee000 September 3, 2025 11:15
@github-actions
Copy link

github-actions bot commented Sep 3, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (7)
apps/client/src/pages/level/Level.tsx (2)

5-15: 중복된 카드 렌더링을 배열 map으로 치환하세요.

가독성과 유지보수를 위해 하드코딩된 9개의 컴포넌트를 배열 순회로 대체하는 것을 권장합니다. key도 부여하세요.

-    <div className="bg-secondary flex flex-col gap-[2rem] p-[1rem]">
-      <TreeStatusCard acorns={0} />
-      <TreeStatusCard acorns={1} />
-      <TreeStatusCard acorns={2} />
-      <TreeStatusCard acorns={3} />
-      <TreeStatusCard acorns={4} />
-      <TreeStatusCard acorns={5} />
-      <TreeStatusCard acorns={6} />
-      <TreeStatusCard acorns={7} />
-      <TreeStatusCard acorns={987987} />
-    </div>
+    <div className="bg-secondary flex flex-col gap-[2rem] p-[1rem]">
+      {([0, 1, 2, 3, 4, 5, 6, 7, 987987] as const).map((n, i) => (
+        <TreeStatusCard key={i} acorns={n} />
+      ))}
+    </div>

14-14: 데모 값 987987는 실제 데이터인가요?

실데이터가 아니라면 예시/스토리북으로만 유지하고 화면 렌더에서는 제거하거나 상한선을 두는 것이 안전합니다. 극단값은 퍼포먼스/레이아웃에 영향을 줄 수 있습니다.

apps/client/src/pages/level/components/TreeStatusCard.tsx (3)

16-19: ARIA 라벨링을 더 견고하게 연결하세요.

role="group" + aria-label 조합도 가능하지만, 내부 제목 요소를 aria-labelledby로 참조하면 스크린리더 문맥 연결이 좋아집니다.

-    <div
-      className={cn('bg-white-bg w-[32.3rem] rounded-[1.2rem] p-[1.2rem]')}
-      role="group"
-      aria-label={`${info.name} 진행 카드`}
-    >
-      <div className="flex items-baseline">
-        <span className="head1 text-main500">{barPercent}%</span>
-      </div>
+    <div
+      className={cn('bg-white-bg w-[32.3rem] rounded-[1.2rem] p-[1.2rem]')}
+      role="group"
+      aria-labelledby={`tree-card-title-${info.level}`}
+    >
+      <div className="flex items-baseline">
+        <span id={`tree-card-title-${info.level}`} className="head1 text-main500">
+          {barPercent}%
+        </span>
+      </div>

29-35: 라벨 의미 명확화(선택).

퍼센트가 “레벨 내 진행률”이라면 라벨을 구체화하세요. 예: “다음 레벨까지 진행률”.

-        <Progress
-          value={barPercent}
-          variant="tree"
-          aria-label={`${info.name} 레벨 진행률`}
-        />
+        <Progress
+          value={barPercent}
+          variant="tree"
+          aria-label={`${info.name} 다음 레벨까지 진행률`}
+        />

12-12: 대안: 전체 레벨 진행률 유틸 추가 (옵션).

전체 5레벨 스케일에서 0%~100%를 표시하려면 utils에 공용 계산 함수를 두고 사용하세요. 마지막 레벨은 항상 100%가 됩니다.

다른 파일에 추가(설명용 코드):

// apps/client/src/pages/level/utils/treeLevel.ts
export function getOverallProgress(acorns: number): number {
  const info = getTreeLevel(acorns);
  const idx = TREE_LEVEL_TABLE.findIndex((r) => r.level === info.level);
  const steps = TREE_LEVEL_TABLE.length - 1; // 4
  const base = idx / steps;
  const within = idx < steps ? info.progressToNext / steps : 0;
  return Math.round(Math.min(1, base + within) * 100);
}

이후 본 컴포넌트에서는:

- const barPercent = Math.round(info.progressToNext * 100);
+ const barPercent = getOverallProgress(acorns);
apps/client/src/pages/level/utils/treeLevel.ts (2)

11-17: 공용 진행률 헬퍼 제공을 검토하세요.

UI에서 반복 계산하지 않도록 “전체 레벨 진행률(0~100)” 헬퍼를 유틸에 노출하면 재사용성과 일관성이 올라갑니다. 위 카드 코멘트의 구현을 이 파일에 추가하는 것을 권장합니다.

Also applies to: 25-49


25-49: 경계값 테스트 추가 제안.

다음 입력에 대한 스냅샷/유닛 테스트(0,1,2,3,4,5,6,7 및 음수, 실수)를 추가해 level, progressToNext, remainingToNext가 기대값과 일치하는지 보장하세요.

원하시면 테스트 케이스 초안을 드리겠습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between deaa027 and 2e03753.

📒 Files selected for processing (3)
  • apps/client/src/pages/level/Level.tsx (1 hunks)
  • apps/client/src/pages/level/components/TreeStatusCard.tsx (1 hunks)
  • apps/client/src/pages/level/utils/treeLevel.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/client/src/pages/level/Level.tsx (1)
apps/client/src/pages/level/components/TreeStatusCard.tsx (1)
  • TreeStatusCard (9-38)
apps/client/src/pages/level/components/TreeStatusCard.tsx (2)
apps/client/src/pages/level/utils/treeLevel.ts (1)
  • getTreeLevel (25-49)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)

export default function TreeStatusCard({ acorns }: TreeStatusCardProps) {
const info = getTreeLevel(acorns);

const barPercent = Math.min(100, info.level * 20);
Copy link

@coderabbitai coderabbitai bot Sep 3, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

진행률 계산이 의도와 다르게 동작합니다 (레벨 1에서도 20%로 표기).

현재 info.level * 20은 레벨 1에서 20%, 레벨 5에서 100%를 표시하여 0개 도토리(레벨 1 시작점)도 20%로 보이는 오류가 있습니다. getTreeLevel이 제공하는 progressToNext(0~1)를 사용해 레벨 내 진행률을 표시하세요.

-  const barPercent = Math.min(100, info.level * 20);
+  // 레벨 내 진행률(0~100)
+  const barPercent = Math.round(info.progressToNext * 100);

추가로 “전체 레벨 진행률(0~100)”이 필요하다면 utils에 계산 함수를 두고 여기서는 그 값을 사용하도록 분리하는 것을 권장합니다. 예시는 아래 코멘트에 첨부했습니다.

📝 Committable suggestion

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

Suggested change
const barPercent = Math.min(100, info.level * 20);
// 레벨 내 진행률(0~100)
const barPercent = Math.round(info.progressToNext * 100);
🤖 Prompt for AI Agents
In apps/client/src/pages/level/components/TreeStatusCard.tsx around line 12, the
progress bar percent is computed incorrectly with info.level * 20 (which shows
level 1 as 20%); replace that with the per-level progress value provided by
getTreeLevel by using info.progressToNext (0–1) multiplied by 100 (optionally
rounded and clamped with Math.min(100, ...)), so the bar reflects progress
within the current level; if you also need an overall 0–100 level progress, move
that calculation into a utils helper and consume the helper here instead.

Copy link
Collaborator Author

@jjangminii jjangminii Sep 4, 2025

Choose a reason for hiding this comment

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

레벨 1 시작점도 20%로 보이도록 의도 되었습니다

Copy link

Choose a reason for hiding this comment

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

@jjangminii 네, 이해했습니다! 레벨 1부터 20%로 시작하는 것이 의도된 디자인이군요. 항상 어느 정도의 진행률을 시각적으로 보여주는 UX 관점에서 좋은 접근입니다.

제 이전 제안을 철회하고, 현재 구현이 올바르다는 점을 확인했습니다. 명확히 설명해주셔서 감사합니다.


✏️ Learnings added
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
packages/design-system/src/icons/iconNames.ts (2)

1-1: 자동 생성 파일 직접 수정 여부 확인 필요

헤더에 “자동 생성 파일(직접 수정 금지)”로 표기되어 있습니다. 아이콘 추가가 생성 스크립트의 소스가 아닌 산출물에만 반영된 것이라면 다음 배포 시 덮어써질 수 있습니다. 생성 파이프라인(아이콘 스프라이트/매니페스트 빌드)에 동일 변경이 반영됐는지 확인 바랍니다.

원하시면 아이콘 생성 스크립트 위치 탐색/동기화 체크용 스크립트 드리겠습니다.


14-18: 아이콘 네이밍 규칙 일관성 점검(tooltip_ vs ic_*)*

기존 네이밍이 대부분 ic_* 프리픽스를 사용합니다. 신규 tooltip_1..5는 예외라 규칙 혼선을 야기할 수 있습니다.

  • 선택지 A: ic_tooltip_*로 통일(가능하면 이전 키도 alias로 병행 지원).
  • 선택지 B: 썸네일/툴팁 전용 세트라는 점을 문서화해 예외 규칙을 명시.
apps/client/src/pages/level/Level.tsx (2)

8-27: 아이콘은 장식 목적이면 접근성 속성 추가(aria-hidden) + 크기 일관화

시각적 데모 용도라면 보조기기에 노출되지 않도록 하고, 마지막 아이콘에도 동일 크기를 지정해 주세요.

       <Icon
         name={'tooltip_1'}
         width={46}
         height={46}
         className="rounded-[0.8rem]"
+        aria-hidden="true"
       />
       <Icon
         name={'ic_plus'}
         width={46}
         height={46}
         className="rounded-[0.8rem]"
+        aria-hidden="true"
       />
       <Icon
         name={'ic_plus'}
         width={46}
         height={46}
         className="rounded-[0.8rem]"
+        aria-hidden="true"
       />
-      <Icon name={'tooltip_4'} className="rounded-[0.8rem]" />
+      <Icon
+        name={'tooltip_4'}
+        width={46}
+        height={46}
+        className="rounded-[0.8rem]"
+        aria-hidden="true"
+      />

28-36: TreeStatusCard 하드코딩 → 배열 map으로 단순화(987987 테스트 값 여부 확인)

반복 렌더는 배열 기반이 가독성과 관리가 쉽습니다. 987987는 데모 값이라면 dev 환경에서만 노출하거나 제거하는 게 안전합니다.

-      <TreeStatusCard acorns={0} />
-      <TreeStatusCard acorns={1} />
-      <TreeStatusCard acorns={2} />
-      <TreeStatusCard acorns={3} />
-      <TreeStatusCard acorns={4} />
-      <TreeStatusCard acorns={5} />
-      <TreeStatusCard acorns={6} />
-      <TreeStatusCard acorns={7} />
-      <TreeStatusCard acorns={987987} />
+      {[
+        0, 1, 2, 3, 4, 5, 6, 7,
+        // NOTE: 대용량 데모 값 — 필요 시 dev 환경에서만 사용
+        ...(process.env.NODE_ENV === 'development' ? [987987] : []),
+      ].map((acorns) => (
+        <TreeStatusCard key={acorns} acorns={acorns} />
+      ))}
-const Level = () => {
-  return (
+const Level = () => {
+  return (
apps/client/src/pages/level/components/LevelInfoCard.tsx (1)

36-42: 아이콘 aria-label을 더 구체적으로

레벨명까지 포함하면 보조기기 사용자에게 맥락 전달이 더 명확합니다.

-                  aria-label={`${row.level} 썸네일 아이콘`}
+                  aria-label={`레벨 ${row.level} (${row.name}) 아이콘`}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2e03753 and b75597d.

⛔ Files ignored due to path filters (5)
  • packages/design-system/src/icons/source/tooltip_1.svg is excluded by !**/*.svg
  • packages/design-system/src/icons/source/tooltip_2.svg is excluded by !**/*.svg
  • packages/design-system/src/icons/source/tooltip_3.svg is excluded by !**/*.svg
  • packages/design-system/src/icons/source/tooltip_4.svg is excluded by !**/*.svg
  • packages/design-system/src/icons/source/tooltip_5.svg is excluded by !**/*.svg
📒 Files selected for processing (4)
  • apps/client/src/pages/level/Level.tsx (1 hunks)
  • apps/client/src/pages/level/components/LevelInfoCard.tsx (1 hunks)
  • packages/design-system/src/icons/iconNames.ts (1 hunks)
  • packages/design-system/src/icons/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.
📚 Learning: 2025-09-04T08:38:00.583Z
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.

Applied to files:

  • apps/client/src/pages/level/Level.tsx
🧬 Code graph analysis (2)
apps/client/src/pages/level/components/LevelInfoCard.tsx (2)
apps/client/src/pages/level/utils/treeLevel.ts (2)
  • TreeLevel (1-1)
  • TREE_LEVEL_TABLE (11-17)
packages/design-system/src/lib/utils.ts (1)
  • cn (108-110)
apps/client/src/pages/level/Level.tsx (2)
apps/client/src/pages/level/components/LevelInfoCard.tsx (1)
  • LevelInfoCard (14-68)
apps/client/src/pages/level/components/TreeStatusCard.tsx (1)
  • TreeStatusCard (9-38)
🔇 Additional comments (5)
packages/design-system/src/icons/iconNames.ts (2)

20-20: IconName 유니온 타입 갱신 방식 적절

(typeof iconNames)[number]로 공개 타입 자동 동기화되는 점 좋습니다.


14-18: 스프라이트 빌드/등록 로직에 tooltip_ 아이콘 포함 여부 확인*
src/icons/source/tooltip_1.svg~tooltip_5.svg 파일과 iconNames.ts 반영은 확인되었으나, 실제 번들링 시 스프라이트 생성(rollup/webpack 플러그인 등) 설정에 해당 디렉토리/패턴이 포함되는지 검증이 필요합니다. 스프라이트 생성 스크립트 또는 설정 파일에서 tooltip_* 경로가 제대로 참조되는지 직접 확인해 주세요.

packages/design-system/src/icons/index.ts (1)

2-2: type-only re-export 적절

런타임 번들이 불어나지 않으면서 소비자 측에서 IconName을 타입 안전하게 사용할 수 있습니다.

apps/client/src/pages/level/components/LevelInfoCard.tsx (2)

6-12: 타입 안전한 레벨-아이콘 매핑 좋습니다

as const + satisfies Record<TreeLevel, IconName>로 누락/오타를 컴파일 타임에 방지합니다.


14-16: 역순 정렬 시 원본 불변성 유지 OK

스프레드 후 reverse()로 원본 상수 테이블을 변형하지 않는 접근 적절합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/client/src/pages/level/utils/treeLevel.ts (1)

1-49: 중복 임계값 로직 일원화(과거 코멘트 재확인)

이 파일 외부에 computeTreeLevel 같은 중복 계산이 남아 있다면 getTreeLevel로 위임하도록 정리해 주세요. 과거 리뷰 코멘트와 동일 맥락입니다.

#!/bin/bash
# 중복 로직 존재 여부 점검
rg -nP --type=ts -C2 '\bfunction\s+computeTreeLevel\b|\bconst\s+computeTreeLevel\b|\bcomputeTreeLevel\s*\('
# getTreeLevel 사용처 요약
rg -nP --type=ts -C2 '\bgetTreeLevel\s*\('
🧹 Nitpick comments (5)
apps/client/src/pages/level/utils/treeLevel.ts (5)

28-35: find + findIndex로 동일 조건 이중 순회 → 단일 findIndex로 축소

같은 조건을 두 번 순회합니다. 한 번에 인덱스와 행을 확정하세요. 가독성·미세 성능 모두 개선됩니다.

-  const row =
-    TREE_LEVEL_TABLE.find(
-      (r) => count >= r.min && (r.max === undefined || count <= r.max)
-    ) ?? TREE_LEVEL_TABLE[0];
-
-  const idx = TREE_LEVEL_TABLE.findIndex((r) => r.level === row.level);
-  const next = TREE_LEVEL_TABLE[idx + 1];
+  const idx = TREE_LEVEL_TABLE.findIndex(
+    (r) => count >= r.min && (r.max === undefined || count <= r.max)
+  );
+  const safeIdx = idx === -1 ? 0 : idx;
+  const row = TREE_LEVEL_TABLE[safeIdx];
+  const next = TREE_LEVEL_TABLE[safeIdx + 1];

19-23: 호출부 단순화를 위해 nextLevel 제공 제안

다음 레벨을 자주 참조한다면 결과 모델에 포함하면 분기/조회가 줄어듭니다. 마지막 레벨에서는 undefined로 남습니다.

 export interface TreeLevelResult extends TreeLevelRow {
   progressToNext: number;
+  nextLevel?: TreeLevel;
   nextMin?: number;
   remainingToNext?: number;
 }

   return {
     ...row,
     progressToNext,
+    nextLevel: next.level,
     nextMin: next.min,
     remainingToNext,
   };

Also applies to: 43-48


11-17: 표현 문자열(name, rangeLabel) 하드코딩 — i18n 추출 고려

UI 문구가 유틸에 상주하면 번역/브랜딩 변경 시 배포 단위가 커집니다. 키만 보관하고 포맷터로 생성하거나, 레이블은 별도의 리소스로 분리하는 것을 고려해 주세요.


25-49: 경계값/비정상 입력 유닛 테스트 추가 권장

케이스: -1, 0, 1, 2, 3, 6, 7, 8, NaN, Infinity에 대해 level/nextMin/remaining/ratio를 스냅샷으로 검증하면 회귀를 막을 수 있습니다. 원하시면 테스트 스켈레톤을 만들어 드리겠습니다.


11-17: as const과 명시적 타입 대신 satisfies 사용
TS 4.9+에서 리터럴 좁히기와 타입 검증을 동시에 제공하므로, 다음과 같이 간결화하세요:

-export const TREE_LEVEL_TABLE: readonly TreeLevelRow[] = [
+export const TREE_LEVEL_TABLE = [
   { level: 1, name: '잊힌 기록의 숲', min: 0, max: 0, rangeLabel: '0개' },
   { level: 2, name: '햇살의 터전', min: 1, max: 2, rangeLabel: '1–2개' },
   { level: 3, name: '기록의 오솔길', min: 3, max: 4, rangeLabel: '3–4개' },
   { level: 4, name: '지식 나무 언덕', min: 5, max: 6, rangeLabel: '5–6개' },
   { level: 5, name: '도토리 만개 숲', min: 7, rangeLabel: '7개 이상' },
-] as const;
+] as const satisfies readonly TreeLevelRow[];

(TypeScript 버전 ~5.8.3에서 satisfies 사용 가능)

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b75597d and ceb5f80.

📒 Files selected for processing (1)
  • apps/client/src/pages/level/utils/treeLevel.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.
🔇 Additional comments (1)
apps/client/src/pages/level/utils/treeLevel.ts (1)

39-41: progressToNext 의미 확인(이산 카운트)

현재 정의는 “현재 레벨의 min→다음 레벨의 min까지” 연속 구간 비율입니다. 그래서 레벨 상한(예: 2개)에서도 100%가 아닌 50%가 됩니다. TreeStatusCard의 진행 바가 level * 20로 의도적으로 표시되는 점(학습 메모 반영)과 충돌하지는 않지만, 이 값을 UI 어디에 쓰는지 한 번 더 확인해 주세요. 필요하면 “레벨 내 진행률(현재 min→현재 max)” 보조 값도 노출할 수 있습니다.

Comment on lines 25 to 27
export function getTreeLevel(acorns: number): TreeLevelResult {
const count = Math.max(0, Math.floor(acorns ?? 0));

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

NaN/Infinity 입력 시 NaN 전파 가능 — 입력 정규화 보강 필요

acornsNaN/Infinity 등 비정상 값이면 progressToNext/remainingToNextNaN으로 전파될 수 있습니다. UI/ARIA에 그대로 반영되면 접근성/표시 문제가 납니다. 안전 가드 추가를 권장합니다.

-export function getTreeLevel(acorns: number): TreeLevelResult {
-  const count = Math.max(0, Math.floor(acorns ?? 0));
+export function getTreeLevel(acorns: number): TreeLevelResult {
+  const n = Number(acorns);
+  const count = Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
📝 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
export function getTreeLevel(acorns: number): TreeLevelResult {
const count = Math.max(0, Math.floor(acorns ?? 0));
export function getTreeLevel(acorns: number): TreeLevelResult {
const n = Number(acorns);
const count = Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
🤖 Prompt for AI Agents
In apps/client/src/pages/level/utils/treeLevel.ts around lines 25-27, the
function accepts acorns but doesn't guard against NaN/Infinity so downstream
calculations (progressToNext/remainingToNext) can become NaN; normalize the
input by validating Number.isFinite(acorns) and coercing to a safe numeric value
(e.g., 0) before Math.floor, clamp negatives to 0 and optionally cap to a
sensible max, then use that normalized finite integer for all subsequent
computations so no NaN/Infinity propagates to the UI/ARIA.

@jjangminii jjangminii changed the title Feat(client): tree ststus 컴포넌트 구현 Feat(client): tree 레벨 컴포넌트 구현 Sep 4, 2025
@constantly-dev constantly-dev self-requested a review September 5, 2025 04:59
Copy link
Member

@constantly-dev constantly-dev left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~~ 👍
UI 잘 구현해주셨네요! 코멘트 확인 부탁드려요!

Comment on lines 1 to 17
export type TreeLevel = 1 | 2 | 3 | 4 | 5;

export interface TreeLevelRow {
level: TreeLevel;
name: string;
min: number;
max?: number;
rangeLabel: string;
}

export const TREE_LEVEL_TABLE: readonly TreeLevelRow[] = [
{ level: 1, name: '잊힌 기록의 숲', min: 0, max: 0, rangeLabel: '0개' },
{ level: 2, name: '햇살의 터전', min: 1, max: 2, rangeLabel: '1–2개' },
{ level: 3, name: '기록의 오솔길', min: 3, max: 4, rangeLabel: '3–4개' },
{ level: 4, name: '지식 나무 언덕', min: 5, max: 6, rangeLabel: '5–6개' },
{ level: 5, name: '도토리 만개 숲', min: 7, rangeLabel: '7개 이상' },
] as const;
Copy link
Member

Choose a reason for hiding this comment

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

지금도 좋은 구조지만, 만약 level에 6이 추가된다면 TreeLevel에도 6을 추가해줘야 하고, TREE_LEVEL_TABLE에도 값을 추가해줘야 해요. 규모가 작다면 크게 문제가 될 일은 없기는 하지만, 확장성을 생각했을 때 조금 불편함이 있을 것 같아요.

그렇다면 TREE_LEVEL_TABLEas const로 그 값 자체 리터럴 타입을 갖게 해주셨으니 여기서 TreeLevel type을 뽑아내는 것은 어떨까요?

Suggested change
export type TreeLevel = 1 | 2 | 3 | 4 | 5;
export interface TreeLevelRow {
level: TreeLevel;
name: string;
min: number;
max?: number;
rangeLabel: string;
}
export const TREE_LEVEL_TABLE: readonly TreeLevelRow[] = [
{ level: 1, name: '잊힌 기록의 숲', min: 0, max: 0, rangeLabel: '0개' },
{ level: 2, name: '햇살의 터전', min: 1, max: 2, rangeLabel: '1–2개' },
{ level: 3, name: '기록의 오솔길', min: 3, max: 4, rangeLabel: '3–4개' },
{ level: 4, name: '지식 나무 언덕', min: 5, max: 6, rangeLabel: '5–6개' },
{ level: 5, name: '도토리 만개 숲', min: 7, rangeLabel: '7개 이상' },
] as const;
// TREE_LEVEL_TABLE 먼저 as const로 정의 (유일한 원본)
export const TREE_LEVEL_TABLE = [
{ level: 1, name: '잊힌 기록의 숲', min: 0, max: 0, rangeLabel: '0개' },
{ level: 2, name: '햇살의 터전', min: 1, max: 2, rangeLabel: '1–2개' },
{ level: 3, name: '기록의 오솔길', min: 3, max: 4, rangeLabel: '3–4개' },
{ level: 4, name: '지식 나무 언덕', min: 5, max: 6, rangeLabel: '5–6개' },
{ level: 5, name: '도토리 만개 숲', min: 7, rangeLabel: '7개 이상' },
] as const;
// 이렇게 row 타입도 추출하고
export type TreeLevelRow = (typeof TREE_LEVEL_TABLE)[number];
// level 타입도 추출하고!
export type TreeLevel = TreeLevelRow['level'];

이렇게 되면 원본 TREE_LEVEL_TABLE만 추가해주면 알아서 타입도 적용이 되는 거죠!

export const TREE_LEVEL_TABLE: readonly TreeLevelRow[] = [

그리고 as const가 애초에 readonly의 의미를 가지기도 하고요! 어떻게 생각하시나요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

확실히 지금 구조에서는 레벨 변동이나 추가에 있어서 불편할것같다는 생각이 드네요.. 지금 구조만 생각하고 추후 생각은 못했는데 이 부분은 진혁님 말씀처럼 수정하는게 좋을 것 같습니다-! 좋은 의견 감사합니다

}

export function getTreeLevel(acorns: number): TreeLevelResult {
const count = Math.max(0, Math.floor(acorns ?? 0));
Copy link
Member

Choose a reason for hiding this comment

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

단순 궁금증으로 acorns 개수를 floor와 max로 count를 계산하는 이유가 궁금해요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

레벨 판정을 정수화나 이상값에 대해 안정적으로 하려고했는데 들어오는값이 안정적으로 들어온다면 굳이 필요없는 부분이긴합니다-! 이 부분은 서버와 상의해보고 다시 정리해봐도 좋을것같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

서버랑 상의했습니다 이부분은 굳이 필요하지 않을거같아 수정하겠습니다

Copy link
Member

Choose a reason for hiding this comment

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

서버랑 상의했습니다 이부분은 굳이 필요하지 않을거같아 수정하겠습니다

너무 좋습니다~~ 굿굿 👍

Comment on lines 28 to 49
const row =
TREE_LEVEL_TABLE.find(
(r) => count >= r.min && (r.max === undefined || count <= r.max)
) ?? TREE_LEVEL_TABLE[0];

const idx = TREE_LEVEL_TABLE.findIndex((r) => r.level === row.level);
const next = TREE_LEVEL_TABLE[idx + 1];

if (!next) {
return { ...row, progressToNext: 1 };
}
const span = Math.max(1, next.min - row.min);
const progressToNext = Math.min(1, (count - row.min) / span);
const remainingToNext = Math.max(0, next.min - count);

return {
...row,
progressToNext,
nextMin: next.min,
remainingToNext,
};
}
Copy link
Member

Choose a reason for hiding this comment

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

저의 개인적인 생각일 뿐이지만, 저는 함수의 역할을 생각할 때 너무 길어지면 하나의 함수가 너무 많은 책임을 가지고 있다고 생각해요. 그래서 해당 함수도 어떻게 보면 여러가지 로직을 담고 있는 것 같은데 어떻게 생각하시는지 궁금해요!

Copy link
Collaborator Author

@jjangminii jjangminii Sep 5, 2025

Choose a reason for hiding this comment

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

확실히 지금 함수는 행 선택, 다음 단계 탐색, 진행도 계산을 한꺼번에 하고 있어서 책임이 많아 보이고 가독성도 떨어지네요.. 이 부분은 함수 분리하겠습니다-!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/client/src/pages/level/utils/treeLevel.ts (1)

47-50: NaN/Infinity/음수 입력 방어 누락 — NaN 전파 가능

acorns가 비정상 값이면 findLevelRow/calcProgress에서 NaN이 전파되어 UI/ARIA에 영향이 갑니다. 안전한 정규화를 추가하세요.

아래처럼 최소한의 정규화(유한수 체크 + 하한 0)만 적용하면 서버가 정수를 보장하더라도 방어적입니다.

 export function getTreeLevel(acorns: number): TreeLevelResult {
-  const { row, next } = findLevelRow(acorns, TREE_LEVEL_TABLE);
-  return { ...row, ...calcProgress(acorns, row, next) };
+  const n = Number(acorns);
+  const count = Number.isFinite(n) ? Math.max(0, n) : 0;
+  const { row, next } = findLevelRow(count, TREE_LEVEL_TABLE);
+  return { ...row, ...calcProgress(count, row, next) };
 }
🧹 Nitpick comments (2)
apps/client/src/pages/level/utils/treeLevel.ts (2)

17-19: TreeLevelRow 타입을 테이블에서 직접 추출해 타입 일관성/확장성 강화

레벨 추가 시 Shape와 실제 테이블 간 타입 괴리를 방지하려면 Row 타입도 테이블에서 추출하는 편이 안전합니다.

아래처럼 변경 제안:

 export type TreeLevel = (typeof TREE_LEVEL_TABLE)[number]['level'];
-export type TreeLevelRow = TreeLevelRowShape;
+export type TreeLevelRow = (typeof TREE_LEVEL_TABLE)[number];

36-40: 마지막 레벨 반환의 as const는 불필요

TreeLevelResult.progressToNextnumber인 상황에서 1 as const는 타입을 과도하게 축소합니다. 단순 1로 두면 가독성이 조금 더 좋습니다.

-      progressToNext: 1 as const,
+      progressToNext: 1,
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 878d41c and 0802bb4.

📒 Files selected for processing (1)
  • apps/client/src/pages/level/utils/treeLevel.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.
🔇 Additional comments (1)
apps/client/src/pages/level/utils/treeLevel.ts (1)

9-15: 데이터 주도 테이블 구성 좋습니다

as const satisfies로 원본을 리터럴+불변으로 고정한 점, 라벨과 min/max를 한 곳에서 관리하는 방향 모두 적절합니다.

Comment on lines +26 to +32
function findLevelRow(count: number, rows: readonly TreeLevelRow[]) {
const idx = rows.findIndex(
(r) => count >= r.min && (r.max === undefined || count <= r.max)
);
const i = idx === -1 ? 0 : idx;
return { row: rows[i], next: rows[i + 1] as TreeLevelRow | undefined };
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

범위 판정 로직을 max 대신 next.min 기준으로 단순화하세요

현재 (min ≤ count ≤ max)는 다음 레벨의 min과 정보 중복이며, 0과 1 사이 실수(예: 0.5)가 레벨1에 매칭되지 않는 가장자리 케이스도 있습니다. next?.min을 상한으로 쓰면 중복 제거와 함께 반열림 구간 [min, next.min)로 자연스럽게 해석됩니다.

아래처럼 변경 제안:

 function findLevelRow(count: number, rows: readonly TreeLevelRow[]) {
-  const idx = rows.findIndex(
-    (r) => count >= r.min && (r.max === undefined || count <= r.max)
-  );
+  const idx = rows.findIndex((r, i) => {
+    const next = rows[i + 1];
+    return count >= r.min && (next ? count < next.min : true);
+  });
   const i = idx === -1 ? 0 : idx;
   return { row: rows[i], next: rows[i + 1] as TreeLevelRow | undefined };
 }
📝 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
function findLevelRow(count: number, rows: readonly TreeLevelRow[]) {
const idx = rows.findIndex(
(r) => count >= r.min && (r.max === undefined || count <= r.max)
);
const i = idx === -1 ? 0 : idx;
return { row: rows[i], next: rows[i + 1] as TreeLevelRow | undefined };
}
function findLevelRow(count: number, rows: readonly TreeLevelRow[]) {
const idx = rows.findIndex((r, i) => {
const next = rows[i + 1];
// use [min, next.min) for all but last row, and [min, ∞) for the final row
return count >= r.min && (next ? count < next.min : true);
});
const i = idx === -1 ? 0 : idx;
return { row: rows[i], next: rows[i + 1] as TreeLevelRow | undefined };
}
🤖 Prompt for AI Agents
In apps/client/src/pages/level/utils/treeLevel.ts around lines 26-32, the range
check currently uses r.max and allows edge cases and duplicate info with next
row; change the logic to locate the row by comparing count >= r.min and count <
next?.min (treating upper bound as exclusive) so ranges become [min, next.min)
and remove reliance on max; if no matching index found use index 0, and return {
row: rows[i], next: rows[i+1] } as before.

Comment on lines +34 to +45
function calcProgress(count: number, row: TreeLevelRow, next?: TreeLevelRow) {
if (!next)
return {
progressToNext: 1 as const,
nextMin: undefined,
remainingToNext: undefined,
};
const span = Math.max(1, next.min - row.min);
const progressToNext = Math.min(1, (count - row.min) / span);
const remainingToNext = Math.max(0, next.min - count);
return { progressToNext, nextMin: next.min, remainingToNext };
}
Copy link

@coderabbitai coderabbitai bot Sep 5, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

progressToNext 하한 미클램프 — 음수 진행률 가능

count < row.min이면 진행률이 음수가 될 수 있습니다. UI 바인딩 안정성을 위해 0–1로 확실히 클램프하세요.

아래처럼 보정 제안:

   const span = Math.max(1, next.min - row.min);
-  const progressToNext = Math.min(1, (count - row.min) / span);
+  const progressToNext = Math.max(0, Math.min(1, (count - row.min) / span));
   const remainingToNext = Math.max(0, next.min - count);

참고: TreeStatusCard에서 진행 바는 의도적으로 level * 20을 사용한다는 러닝을 반영하고 있습니다. 본 변경은 progressToNext를 사용하는 다른 뷰/ARIA에서의 안전성 향상 목적입니다.

📝 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
function calcProgress(count: number, row: TreeLevelRow, next?: TreeLevelRow) {
if (!next)
return {
progressToNext: 1 as const,
nextMin: undefined,
remainingToNext: undefined,
};
const span = Math.max(1, next.min - row.min);
const progressToNext = Math.min(1, (count - row.min) / span);
const remainingToNext = Math.max(0, next.min - count);
return { progressToNext, nextMin: next.min, remainingToNext };
}
function calcProgress(count: number, row: TreeLevelRow, next?: TreeLevelRow) {
if (!next)
return {
progressToNext: 1 as const,
nextMin: undefined,
remainingToNext: undefined,
};
const span = Math.max(1, next.min - row.min);
const progressToNext = Math.max(0, Math.min(1, (count - row.min) / span));
const remainingToNext = Math.max(0, next.min - count);
return { progressToNext, nextMin: next.min, remainingToNext };
}
🤖 Prompt for AI Agents
In apps/client/src/pages/level/utils/treeLevel.ts around lines 34 to 45,
progressToNext can become negative when count < row.min; clamp progressToNext to
the 0–1 range to prevent negative progress values. Replace the current
computation with a clamped value (e.g., compute raw = (count - row.min) / span
and set progressToNext = Math.max(0, Math.min(1, raw))) and keep nextMin and
remainingToNext logic unchanged so other consumers/ARIA remain safe.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이 부분은 서버에서 전달해주는 값이 양의 정수라고 확정지었기에 제거했습니다

Copy link

Choose a reason for hiding this comment

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

@jjangminii 네, 서버에서 양의 정수를 보장한다면 음수 진행률 케이스는 발생하지 않겠네요. API 계약이 명확하다면 불필요한 방어 코드를 제거하는 것이 맞습니다.

시스템 아키텍처 관점에서 합리적인 결정입니다!

🐰📋
╭─────╮
│ 📝✓ │ Understanding system contracts!
╰─────╯


✏️ Learnings added
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/utils/treeLevel.ts:34-45
Timestamp: 2025-09-05T06:58:12.328Z
Learning: In the Pinback client application, the server API guarantees that acorn count values are positive integers, so defensive clamping for negative progress calculations in tree level utilities is unnecessary.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: jjangminii
PR: Pinback-Team/pinback-client#54
File: apps/client/src/pages/level/components/TreeStatusCard.tsx:12-12
Timestamp: 2025-09-04T08:38:00.583Z
Learning: In TreeStatusCard component, the progress bar calculation `info.level * 20` is intentional design - level 1 starts at 20%, level 2 at 40%, etc., up to level 5 at 100%. This ensures no level shows 0% progress for UX purposes.

Copy link
Collaborator

@jllee000 jllee000 left a comment

Choose a reason for hiding this comment

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

ui 구성이 깔끔하네요! 고생하셨습니다!

Comment on lines 9 to 18
<TreeStatusCard acorns={1} />
<TreeStatusCard acorns={2} />
<TreeStatusCard acorns={3} />
<TreeStatusCard acorns={4} />
<TreeStatusCard acorns={5} />
<TreeStatusCard acorns={6} />
<TreeStatusCard acorns={7} />
<TreeStatusCard acorns={987987} />
</div>
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

확인했씁니당! 일정 수이상 커지면 걍 최대 100%되게 잘 해두신 것 같네용
머지 전에는 이 확인용으로 불러온 코드들 삭제해주세용!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵넵-!

Comment on lines +35 to +37
if (!next)
return {
progressToNext: 1 as const,
Copy link
Collaborator

Choose a reason for hiding this comment

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

이거는 어떤경우의 조건문일까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이 조건문은 next가 없을 때 현재가 마지막 레벨일 때 실행됩니다-! 도토리가 7개 이상이면 최종 레벨(5)에 도달한 상태라 progressToNext는 1이고 다음 단계 값들은 undefined가 됩니다

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/client/src/pages/level/Level.tsx (1)

1-5: DS의 Level 컴포넌트와 네이밍 충돌 여지 — 페이지 컴포넌트명을 LevelPage로 변경 제안

같은 파일에서 DS의 Level(UI)과 페이지 Level이 함께 필요해질 경우 혼동/aliasing이 발생합니다. 초기에 분리해두는 편이 안전합니다.

-const Level = () => {
+const LevelPage = () => {
   return (
-    <main className="bg-secondary flex flex-col gap-8 p-4">
+    <main className="bg-secondary flex flex-col gap-8 p-4">
       <h1 className="text-xl font-semibold">나의 나무 레벨</h1>
       <TreeStatusCard />
       <LevelInfoCard />
     </main>
   );
 };
-export default Level;
+export default LevelPage;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0802bb4 and 152c232.

📒 Files selected for processing (1)
  • apps/client/src/pages/level/Level.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: storybook

@@ -1,5 +1,5 @@
const Level = () => {
return <div>Level</div>;
return <div className="bg-secondary flex flex-col gap-[2rem] p-[1rem]"></div>;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

빈 컨테이너만 렌더링되어 페이지가 비어 있습니다 — TreeStatusCard/LevelInfoCard를 배치해 주세요.

PR 목적(#53)에 비해 현재 UI가 노출되지 않습니다. 최소 상태 카드와 레벨 안내 카드를 렌더링해 기능을 확인 가능하게 해주세요. 아래 예시는 시맨틱 마크업(main, h1)과 DS 스페이싱 스케일을 함께 적용했습니다. 각 컴포넌트의 export 형태에 맞춰 import 문은 필요시 조정하세요.

+import TreeStatusCard from './components/TreeStatusCard';
+import LevelInfoCard from './components/LevelInfoCard';
 
-const Level = () => {
-  return <div className="bg-secondary flex flex-col gap-[2rem] p-[1rem]"></div>;
-};
+const Level = () => {
+  return (
+    <main className="bg-secondary flex flex-col gap-8 p-4">
+      <h1 className="text-xl font-semibold">나의 나무 레벨</h1>
+      <TreeStatusCard />
+      <LevelInfoCard />
+    </main>
+  );
+};
 
 export default Level;

원하시면 PR에 맞춰 실제 import 경로/타입을 반영한 패치를 바로 드릴게요.

📝 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
return <div className="bg-secondary flex flex-col gap-[2rem] p-[1rem]"></div>;
// apps/client/src/pages/level/Level.tsx
import React from 'react';
import TreeStatusCard from './components/TreeStatusCard';
import LevelInfoCard from './components/LevelInfoCard';
-const Level = () => {
- return <div className="bg-secondary flex flex-col gap-[2rem] p-[1rem]"></div>;
const Level = () => {
return (
<main className="bg-secondary flex flex-col gap-8 p-4">
<h1 className="text-xl font-semibold">나의 나무 레벨</h1>
<TreeStatusCard />
<LevelInfoCard />
</main>
);
};
export default Level;
🤖 Prompt for AI Agents
In apps/client/src/pages/level/Level.tsx around line 2, the component currently
returns an empty container so the page shows nothing; render the TreeStatusCard
and LevelInfoCard inside the container (wrap with semantic main and an h1) using
the design system spacing classes instead of raw values (e.g., gap-4, p-4 or the
project's DS equivalents) and adjust/add import statements for TreeStatusCard
and LevelInfoCard to match their exported names; if those components require
props, pass minimal mock or real props (status, level data) so they render
without errors.

💡 Verification agent

🧩 Analysis chain

임의값 대신 스케일 토큰 사용, bg-secondary 토큰 존재 확인 권장

gap-[2rem], p-[1rem] 대신 스케일(gap-8, p-4)을 권장합니다. 또한 bg-secondary가 팀 Tailwind 토큰에 정의돼 있는지 확인해 주세요.

다음 스크립트로 토큰 정의를 빠르게 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# tailwind 설정 파일 위치 확인
fd -HI 'tailwind.config*' -t f

# bg-secondary 사용처 확인
rg -nP --type=ts --type=tsx -C2 '\bbg-secondary\b' apps/client

# tailwind 설정에 secondary 색상 토큰 존재 여부 힌트
for f in $(fd -HI 'tailwind.config*' -t f); do
  echo "==> $f"
  rg -n -C2 'secondary' "$f" || true
done

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# tailwind 설정 파일에서 secondary 정의 확인
echo "==> apps/extension/tailwind.config.ts"
grep -R -nE "secondary\b" -C3 apps/extension/tailwind.config.ts || true

# 앱 내 bg-secondary 사용처 확인
grep -R -n "bg-secondary" apps/client || true

# 임의값 클래스 확인
grep -R -n "gap-\[" apps/client || true
grep -R -n "p-\[" apps/client || true

Length of output: 1861


스케일 토큰 및 bg-secondary 정의 확인 필요

  • gap-[2rem] → gap-8, p-[1rem] → p-4 로 교체
  • apps/extension/tailwind.config.ts에 secondary 색상 토큰이 정의되어 있지 않습니다. bg-secondary 사용 전 토큰 정의 여부 확인 및 필요 시 추가하세요.
🤖 Prompt for AI Agents
In apps/client/src/pages/level/Level.tsx around line 2, the returned div uses
arbitrary spacing tokens and an undefined color token; replace gap-[2rem] with
gap-8 and p-[1rem] with p-4 in the className, and then verify that
apps/extension/tailwind.config.ts defines a 'secondary' color token (add an
appropriate hex/rgb value under theme.extend.colors.secondary if it's missing)
before using bg-secondary.

@jjangminii jjangminii merged commit 16afe23 into develop Sep 5, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] tree ststus 컴포넌트 구현

3 participants