Skip to content

Conversation

@drifter089
Copy link
Owner

@drifter089 drifter089 commented Dec 21, 2025

Summary

Redesigned the member profile page with a Japanese Bento grid-inspired, minimalist UI featuring compact spacing, clean borders, and subtle hover states.

Changes

  • New MemberStatsCard component for displaying roles, teams, and effort points in a 3-column grid
  • Replaced Card components with bordered divs for a cleaner Bento grid aesthetic
  • 12-column responsive grid layout with gap-3 spacing for compact presentation
  • Compact header with smaller avatar (16x16) and simplified typography
  • Redesigned charts with cleaner borders, uppercase headers, and optimized heights
  • Updated role cards with vertical sections and subtle hover effects
  • Simplified team sections using button triggers instead of Card headers
  • Moved chart logic inline in main page component to support grid layout
  • Removed unused MemberChartsSection wrapper component

Design Details

  • 0 border-radius applied via existing global CSS configuration
  • Consistent use of border-border/60 and hover:bg-accent/30 for subtle interactions
  • Compact padding: px-4 py-3 throughout all components
  • Maintains dark/light mode compatibility via CSS variables
  • No logic changes - purely styling and layout improvements

Summary by CodeRabbit

  • UI Improvements
    • Redesigned member profile page with improved layout and organization
    • Added statistics card displaying role count, team count, and total effort points
    • Enhanced effort tracking visualization with pie chart
    • Improved goal progress display with updated chart rendering
    • Simplified member header and team section designs
    • Added purpose field display to role cards

✏️ Tip: You can customize this high-level summary in your review settings.

…yling

- Add MemberStatsCard component for stats display (roles, teams, points)
- Replace Card components with bordered divs for Bento aesthetic
- Implement 12-column grid layout with compact spacing (px-4 py-3, gap-3)
- Update header with smaller avatar and simplified UI
- Add subtle hover states (hover:bg-accent/30, hover:border-border)
- Redesign effort and goals charts with cleaner borders
- Compact role cards with vertical sections and hover effects
- Simplify team section triggering with button instead of Card header
- Inline chart logic in main page component
- Remove member-charts-section wrapper component
@vercel
Copy link

vercel bot commented Dec 21, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
org-os Ready Ready Preview, Comment Dec 21, 2025 1:58am

@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

Walkthrough

This pull request redesigns the member detail page by decomposing MemberChartsSection into standalone chart components, replacing Card-based layouts with div-based designs, adding a new MemberStatsCard, simplifying the MemberHeader props, and migrating chart rendering from DashboardMetrics to Recharts components.

Changes

Cohort / File(s) Change Summary
Chart Components Refactoring
src/app/member/[id]/_components/member-effort-chart.tsx, src/app/member/[id]/_components/member-goals-chart.tsx
Replaced Dashboard-based chart components with custom Recharts-based rendering (PieChart and RadarChart). Added new type definitions, implemented custom chart containers, legends, and tooltips. Introduced data/config generation logic.
MemberChartsSection Removal
src/app/member/[id]/_components/member-charts-section.tsx
Deleted entire component that previously composed MemberEffortChart and MemberGoalsChart.
Header Simplification
src/app/member/[id]/_components/member-header.tsx
Removed roleCount and teamCount props, replaced Card-based layout with borderless inline header, simplified avatar and button styling, removed Badge and icon imports.
Page Layout Restructuring
src/app/member/[id]/_components/member-page-client.tsx
Replaced MemberChartsSection with individual chart components, added MemberStatsCard integration, expanded grid layout for responsive design, introduced goalsData preprocessing, updated loading and empty states to reflect new component structure.
Role Card Redesign
src/app/member/[id]/_components/member-role-card.tsx
Converted from Card-based to div-based layout, added purpose section rendering, replaced KPI rendering logic with conditional rendering (DashboardMetricCard vs. lightweight KPI row vs. "No KPI" message), updated styling utilities.
New Statistics Component
src/app/member/[id]/_components/member-stats-card.tsx
Added new client component displaying roleCount, teamCount, and totalEffortPoints in a 3-column layout with pluralized labels and lucide-react icons.
Team Section Update
src/app/member/[id]/_components/team-section.tsx
Replaced Card-based header with button-triggered collapsible header showing team name and role count, updated CollapsibleContent container to bordered div, removed Badge and Users icon imports.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

  • Chart migration logic: Verify Recharts PieChart and RadarChart configurations, data mapping, and custom Label/Legend implementations in member-effort-chart.tsx and member-goals-chart.tsx
  • Prop changes and compatibility: Ensure MemberHeader removal of roleCount/teamCount does not break calling code; verify MemberEffortChart and MemberGoalsChart new prop signatures are correctly passed from member-page-client.tsx
  • Layout grid and responsive design: Review expanded grid structure in member-page-client.tsx for proper column spans and loading/empty state placeholders
  • GoalsData preprocessing: Validate goalsData extraction and transformation logic in member-page-client.tsx for correct metric goal data feeding to MemberGoalsChart

Possibly related PRs

Poem

🐰 Charts split and scattered, from one to many more,
Cards fade to divs, as designs explore,
Stats gathered round, a header stands lean,
This rabbit hops faster through layouts pristine!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective: a member page redesign using Bento grid layout and compact styling, which is reflected throughout the changeset with grid reorganization, component restructuring, and styling updates.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch team-chart-legend-fix-small

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.

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

Caution

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

⚠️ Outside diff range comments (1)
src/app/member/[id]/_components/member-effort-chart.tsx (1)

51-57: Potential key collision if role titles are not unique.

Using role.title as the chartConfig key could cause data loss if two roles share the same title. Consider using role.id as the key instead.

🔎 Proposed fix
-  const chartConfig: ChartConfig = rolesWithEffort.reduce((acc, role) => {
-    acc[role.title] = {
-      label: role.title,
-      color: role.color,
-    };
-    return acc;
-  }, {} as ChartConfig);
+  const chartConfig: ChartConfig = rolesWithEffort.reduce((acc, role) => {
+    acc[role.id] = {
+      label: role.title,
+      color: role.color,
+    };
+    return acc;
+  }, {} as ChartConfig);

You would also need to update the chartData mapping to use role.id as the name key if making this change, ensuring consistency between the config and data.

🧹 Nitpick comments (3)
src/app/member/[id]/_components/member-goals-chart.tsx (1)

76-89: Consider adding PolarRadiusAxis for better chart readability.

The radar chart lacks a radial axis, which would help users understand the scale of progress values (0-100%). Without it, the chart shows relative proportions but not absolute progress.

🔎 Proposed improvement
 import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from "recharts";
+import { PolarRadiusAxis } from "recharts";
 ...
           <RadarChart data={chartData}>
             <ChartTooltip cursor={false} content={<ChartTooltipContent />} />
             <PolarAngleAxis dataKey="goal" />
             <PolarGrid />
+            <PolarRadiusAxis angle={30} domain={[0, 100]} tick={false} axisLine={false} />
             <Radar
src/app/member/[id]/_components/member-header.tsx (1)

47-52: Consider using asChild pattern for Link+Button composition.

Nesting a Button inside a Link creates two focusable elements. The recommended pattern in this codebase (per Radix UI conventions) is to use asChild on the Link or render the Button with an anchor element.

🔎 Alternative approach
-      <Link href={`/check-in/${member.id}`} className="shrink-0">
-        <Button variant="default" size="sm" className="gap-1.5">
-          <ClipboardCheck className="h-3.5 w-3.5" />
-          Check-in
-        </Button>
-      </Link>
+      <Button variant="default" size="sm" className="gap-1.5 shrink-0" asChild>
+        <Link href={`/check-in/${member.id}`}>
+          <ClipboardCheck className="h-3.5 w-3.5" />
+          Check-in
+        </Link>
+      </Button>
src/app/member/[id]/_components/member-stats-card.tsx (1)

42-42: Add explicit React import for React.ReactNode type.

The React.ReactNode type is used on line 42, but React is not imported. While this may work in some setups due to the new JSX transform, explicit imports are safer for type annotations.

🔎 Proposed fix
 "use client";
 
+import type React from "react";
 import { Briefcase, Target, Users } from "lucide-react";
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ad1643 and d50fa13.

📒 Files selected for processing (8)
  • src/app/member/[id]/_components/member-charts-section.tsx (0 hunks)
  • src/app/member/[id]/_components/member-effort-chart.tsx (3 hunks)
  • src/app/member/[id]/_components/member-goals-chart.tsx (3 hunks)
  • src/app/member/[id]/_components/member-header.tsx (2 hunks)
  • src/app/member/[id]/_components/member-page-client.tsx (3 hunks)
  • src/app/member/[id]/_components/member-role-card.tsx (1 hunks)
  • src/app/member/[id]/_components/member-stats-card.tsx (1 hunks)
  • src/app/member/[id]/_components/team-section.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • src/app/member/[id]/_components/member-charts-section.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use the tRPC dual API pattern: for server components, directly call tRPC server API (10x faster); for client components, use React hooks with TanStack Query
Use inline type imports with @trivago/prettier-plugin-sort-imports for consistent import ordering

Use TypeScript 5.9 with strict type checking for all frontend and backend code

Files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
  • src/app/member/[id]/_components/team-section.tsx
  • src/app/member/[id]/_components/member-header.tsx
src/**/*.tsx

📄 CodeRabbit inference engine (GEMINI.md)

Prefer Server Components for initial data fetching; use Client Components ('use client') only for interactivity

Files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
  • src/app/member/[id]/_components/team-section.tsx
  • src/app/member/[id]/_components/member-header.tsx
src/**/*/*.tsx

📄 CodeRabbit inference engine (GEMINI.md)

Client Components must use import { api } from '@/trpc/react' for standard HTTP/Hooks wrapper

Files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
  • src/app/member/[id]/_components/team-section.tsx
  • src/app/member/[id]/_components/member-header.tsx
**/*.tsx

📄 CodeRabbit inference engine (GEMINI.md)

Use Tailwind CSS 4 for styling with shadcn/ui and Radix UI primitive components

Files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
  • src/app/member/[id]/_components/team-section.tsx
  • src/app/member/[id]/_components/member-header.tsx
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/dashboard/**/*.tsx : Consolidate dashboard metric card duplication: add readOnly mode to dashboard-metric-card.tsx instead of maintaining separate public-dashboard-metric-card.tsx component
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/dashboard/**/*.{ts,tsx} : Three-stage metrics transformation pipeline: Stage 1 (API → DataPoints via DataIngestionTransformer), Stage 2 (DataPoints → ChartConfig via ChartTransformer), Stage 3 (ChartConfig → UI via DashboardMetricChart with Recharts)
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/teams/[teamId]/**/*.tsx : Consolidate role node duplication: extract shared RoleNodeTemplate from role-node.tsx and public-role-node.tsx with isEditable prop instead of maintaining separate components

Applied to files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
  • src/app/member/[id]/_components/team-section.tsx
  • src/app/member/[id]/_components/member-header.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/dashboard/**/*.tsx : Consolidate dashboard metric card duplication: add readOnly mode to dashboard-metric-card.tsx instead of maintaining separate public-dashboard-metric-card.tsx component

Applied to files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
  • src/app/member/[id]/_components/team-section.tsx
  • src/app/member/[id]/_components/member-header.tsx
📚 Learning: 2025-12-20T22:12:00.566Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.566Z
Learning: Applies to src/app/teams/[teamId]/**/*.tsx : React Flow nodes must store minimal data (e.g., just `roleId`); fetch full Role data from TanStack Query cache in the Node component to keep canvas and sidebars in sync

Applied to files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
  • src/app/member/[id]/_components/team-section.tsx
  • src/app/member/[id]/_components/member-header.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/metric/_components/**/*.tsx : Consider factory pattern for metric dialogs to reduce the 5 nearly identical wrapper components (currently duplicated across providers)

Applied to files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/metric/_components/**/*.tsx : Use MetricDialogBase from base/ for shared metric dialog functionality to reduce duplication across provider-specific dialogs

Applied to files:

  • src/app/member/[id]/_components/member-stats-card.tsx
  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/dashboard/**/*.{ts,tsx} : Three-stage metrics transformation pipeline: Stage 1 (API → DataPoints via DataIngestionTransformer), Stage 2 (DataPoints → ChartConfig via ChartTransformer), Stage 3 (ChartConfig → UI via DashboardMetricChart with Recharts)

Applied to files:

  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/member-effort-chart.tsx
  • src/app/member/[id]/_components/member-goals-chart.tsx
📚 Learning: 2025-12-20T22:12:00.566Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.566Z
Learning: Applies to src/app/teams/[teamId]/**/*.tsx : Avoid modifying `enrichNodesWithRoleData` flow without understanding the complete canvas serialization logic for saving/loading React Flow nodes to the database

Applied to files:

  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/team-section.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/teams/[teamId]/**/*.{ts,tsx} : For new canvas node types: create component in teams/[teamId]/_components/, add to TeamNode union in types/canvas.ts, register in nodeTypes in team-canvas.tsx, and update serialization in canvas-serialization.ts

Applied to files:

  • src/app/member/[id]/_components/member-page-client.tsx
  • src/app/member/[id]/_components/team-section.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/teams/[teamId]/**/*.{ts,tsx} : Cache-first node pattern: Role nodes store only roleId; fetch display data from TanStack Query cache using useRoleData hook rather than storing denormalized data

Applied to files:

  • src/app/member/[id]/_components/member-role-card.tsx
  • src/app/member/[id]/_components/team-section.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/metric/_components/**/*.tsx : Metric dialogs should follow the pattern: [Provider]MetricDialog.tsx (dialog wrapper) + [Provider]MetricContent.tsx (form content) and be registered in src/app/metric/_components/index.ts

Applied to files:

  • src/app/member/[id]/_components/member-role-card.tsx
📚 Learning: 2025-12-20T22:12:00.566Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.566Z
Learning: Applies to src/**/*.tsx : Prefer Server Components for initial data fetching; use Client Components ('use client') only for interactivity

Applied to files:

  • src/app/member/[id]/_components/member-effort-chart.tsx
📚 Learning: 2025-12-20T22:12:00.566Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.566Z
Learning: Applies to src/**/*/*.tsx : Client Components must use `import { api } from '@/trpc/react'` for standard HTTP/Hooks wrapper

Applied to files:

  • src/app/member/[id]/_components/member-effort-chart.tsx
📚 Learning: 2025-12-20T16:32:46.818Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T16:32:46.818Z
Learning: Applies to src/app/teams/[teamId]/**/*.{ts,tsx} : Use Zustand store with Context pattern (TeamStoreContext) and access via useTeamStore hook. For callbacks that need current state, use useTeamStoreApi() to avoid stale closures.

Applied to files:

  • src/app/member/[id]/_components/team-section.tsx
📚 Learning: 2025-12-20T22:12:00.566Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.566Z
Learning: Applies to src/app/teams/[teamId]/**/*.ts : Canvas changes must be debounced (2s) and saved via `editSession` logic to handle concurrent team edits safely

Applied to files:

  • src/app/member/[id]/_components/team-section.tsx
🧬 Code graph analysis (3)
src/app/member/[id]/_components/member-page-client.tsx (6)
src/app/member/[id]/_components/member-header.tsx (1)
  • MemberHeader (16-55)
src/components/ui/skeleton.tsx (1)
  • Skeleton (13-13)
src/app/member/[id]/_components/member-stats-card.tsx (1)
  • MemberStatsCard (11-35)
src/app/member/[id]/_components/member-effort-chart.tsx (1)
  • MemberEffortChart (22-129)
src/app/member/[id]/_components/member-goals-chart.tsx (1)
  • MemberGoalsChart (23-94)
src/app/member/[id]/_components/team-section.tsx (1)
  • TeamSection (25-86)
src/app/member/[id]/_components/team-section.tsx (1)
src/components/ui/collapsible.tsx (2)
  • CollapsibleTrigger (33-33)
  • CollapsibleContent (33-33)
src/app/member/[id]/_components/member-header.tsx (3)
src/trpc/react.tsx (1)
  • RouterOutputs (42-42)
src/components/ui/avatar.tsx (2)
  • Avatar (54-54)
  • AvatarFallback (54-54)
src/components/ui/button.tsx (1)
  • Button (62-62)
⏰ 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: test
🔇 Additional comments (12)
src/app/member/[id]/_components/member-goals-chart.tsx (2)

23-42: LGTM!

The empty state handling is clean with consistent styling. The early return pattern keeps the component readable.


44-54: LGTM!

Good defensive coding with the progress value clamping (Math.max(0, Math.min(100, ...))), ensuring the radar chart renders correctly even with unexpected input values.

src/app/member/[id]/_components/member-header.tsx (2)

16-26: LGTM!

The fallback logic for initials and userName handles edge cases well, with a sensible cascade from full name → email → default value.


44-44: Remove the optional chaining from line 20—email is guaranteed to be a string.

The Member type defines email: string (required, non-null), so line 44's unconditional render of {member.email} is correct. Line 20's defensive member.email?.[0]?.toUpperCase() is unnecessary. Simplify to member.email[0].toUpperCase().

src/app/member/[id]/_components/team-section.tsx (2)

41-63: LGTM!

Good accessibility implementation with dynamic aria-label for the expand/collapse button. The visual feedback with chevron icons and the compact header layout align well with the Bento grid aesthetic.


67-81: LGTM!

The responsive grid layout (grid-cols-1 lg:grid-cols-2) and consistent gap spacing work well for the role cards.

src/app/member/[id]/_components/member-stats-card.tsx (1)

11-35: LGTM!

Clean component structure with good separation of concerns. The StatBlock extraction keeps the main component readable, and the pluralization logic handles edge cases correctly.

src/app/member/[id]/_components/member-effort-chart.tsx (2)

93-121: LGTM!

The custom center label implementation using SVG <text> and <tspan> elements is well-structured and provides a clear total display. The viewBox guard ensures safe property access.


30-43: LGTM!

Consistent empty state styling matching the other chart components in this PR.

src/app/member/[id]/_components/member-role-card.tsx (1)

1-72: Clean Bento grid refactor with proper sanitization.

The restructuring from Card-based to div-based layout successfully achieves the minimalist Bento aesthetic described in the PR objectives. The stripHtml sanitization on line 18 properly prevents XSS when rendering role.purpose. The conditional rendering logic for purpose, charts, and KPIs is clear and handles all edge cases appropriately.

src/app/member/[id]/_components/member-page-client.tsx (2)

23-42: Well-structured loading state.

The expanded skeleton layout properly reflects the final multi-block responsive grid structure, providing users with an accurate preview of the content layout during loading.


132-175: Excellent responsive grid layout with clear structure.

The 12-column grid layout with explicit block comments (Header, Stats, Charts, Team Sections) makes the page structure immediately clear. The gap-3 spacing and responsive breakpoints align well with the compact Bento grid design goals.

Comment on lines +116 to +130
// Process goals data for the radar chart
const goalsData = roles
.filter((role) => {
if (!role.metricId) return false;
const chart = chartsByMetricId.get(role.metricId);
return chart?.goalProgress != null;
})
.map((role) => {
const chart = chartsByMetricId.get(role.metricId!)!;
return {
goalName: chart.metric.name ?? role.metric?.name ?? "Unknown",
progressPercent: chart.goalProgress!.progressPercent,
status: chart.goalProgress!.status,
};
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsafe non-null assertions in goalsData preprocessing.

The chained .filter().map() pattern uses multiple non-null assertion operators (!) that bypass TypeScript's type safety:

  • Line 124: chartsByMetricId.get(role.metricId!)! — double assertion assumes both metricId exists and .get() returns a value
  • Lines 127-128: chart.goalProgress! — assertion assumes goalProgress is non-null

While the filter checks these conditions, TypeScript's type narrowing doesn't carry from .filter() to .map() in chains. This creates brittle code that could crash if the Map structure changes or filter logic is modified.

🔎 Safer implementation without non-null assertions
-  // Process goals data for the radar chart
-  const goalsData = roles
-    .filter((role) => {
-      if (!role.metricId) return false;
-      const chart = chartsByMetricId.get(role.metricId);
-      return chart?.goalProgress != null;
-    })
-    .map((role) => {
-      const chart = chartsByMetricId.get(role.metricId!)!;
-      return {
-        goalName: chart.metric.name ?? role.metric?.name ?? "Unknown",
-        progressPercent: chart.goalProgress!.progressPercent,
-        status: chart.goalProgress!.status,
-      };
-    });
+  // Process goals data for the radar chart
+  const goalsData = roles
+    .map((role) => {
+      if (!role.metricId) return null;
+      const chart = chartsByMetricId.get(role.metricId);
+      if (!chart?.goalProgress) return null;
+      return {
+        goalName: chart.metric.name ?? role.metric?.name ?? "Unknown",
+        progressPercent: chart.goalProgress.progressPercent,
+        status: chart.goalProgress.status,
+      };
+    })
+    .filter((goal): goal is NonNullable<typeof goal> => goal !== null);
📝 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
// Process goals data for the radar chart
const goalsData = roles
.filter((role) => {
if (!role.metricId) return false;
const chart = chartsByMetricId.get(role.metricId);
return chart?.goalProgress != null;
})
.map((role) => {
const chart = chartsByMetricId.get(role.metricId!)!;
return {
goalName: chart.metric.name ?? role.metric?.name ?? "Unknown",
progressPercent: chart.goalProgress!.progressPercent,
status: chart.goalProgress!.status,
};
});
// Process goals data for the radar chart
const goalsData = roles
.map((role) => {
if (!role.metricId) return null;
const chart = chartsByMetricId.get(role.metricId);
if (!chart?.goalProgress) return null;
return {
goalName: chart.metric.name ?? role.metric?.name ?? "Unknown",
progressPercent: chart.goalProgress.progressPercent,
status: chart.goalProgress.status,
};
})
.filter((goal): goal is NonNullable<typeof goal> => goal !== null);
🤖 Prompt for AI Agents
In src/app/member/[id]/_components/member-page-client.tsx around lines 116 to
130, replace the current chained filter/map that uses non-null assertion
operators with a single pass that explicitly checks for missing values: iterate
roles (e.g., with reduce or flatMap), for each role do const metricId =
role.metricId; if (!metricId) continue/skip; const chart =
chartsByMetricId.get(metricId); if (!chart || chart.goalProgress == null)
continue/skip; then push an object using chart.metric?.name ?? role.metric?.name
?? "Unknown" and chart.goalProgress.progressPercent and
chart.goalProgress.status (no ! assertions). This preserves the original output
but avoids unsafe non-null assertions and keeps TypeScript type-safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants