Unnamed Skill
React 19 and TypeScript coding standards for Portfolio Buddy 2. Use when: writing new components, reviewing code, refactoring, or ensuring consistency. Contains component patterns, TypeScript rules, and best practices.
$ 安裝
git clone https://github.com/5MinFutures/futures-arena /tmp/futures-arena && cp -r /tmp/futures-arena/.claude/skills/coding-standards ~/.claude/skills/futures-arena// tip: Run this command in your terminal to install the skill
SKILL.md
name: coding-standards description: React 19 and TypeScript coding standards for Portfolio Buddy 2. Use when: writing new components, reviewing code, refactoring, or ensuring consistency. Contains component patterns, TypeScript rules, and best practices.
Coding Standards - Portfolio Buddy 2
React 19 Patterns
Component Structure
// Good: Functional component with TypeScript
interface MetricsTableProps {
data: Metric[]
onSelect: (id: string) => void
}
export function MetricsTable({ data, onSelect }: MetricsTableProps) {
// Hooks at top
const [selected, setSelected] = useState<Set<string>>(new Set())
// Derived state with useMemo
const sortedData = useMemo(() =>
data.sort((a, b) => b.sharpe - a.sharpe),
[data]
)
// Event handlers with useCallback
const handleSelect = useCallback((id: string) => {
setSelected(prev => new Set(prev).add(id))
onSelect(id)
}, [onSelect])
// Render
return <div>...</div>
}
Hooks Rules
- Only at top level - No hooks in conditionals or loops
- Custom hooks start with
use- useMetrics, usePortfolio, useSorting - Dependencies array complete - All deps in useEffect/useMemo/useCallback
- Cleanup on unmount - Return cleanup function from useEffect
State Management
Portfolio Buddy 2 uses PLAIN REACT HOOKS ONLY:
- Local UI state →
useState - Derived state →
useMemo - Stable callbacks →
useCallback - DOM/value refs →
useRef
NO global state libraries:
- ❌ No TanStack Query
- ❌ No Zustand
- ❌ No Redux
- ❌ No Jotai
Pattern: Props down, custom hooks for shared logic
// State management example
const [files, setFiles] = useState<File[]>([])
const [dateRange, setDateRange] = useState({ start: null, end: null })
// Derived state
const filteredData = useMemo(() =>
filterByDateRange(files, dateRange),
[files, dateRange]
)
// Stable callback
const handleUpload = useCallback((newFile: File) => {
setFiles(prev => [...prev, newFile])
}, [])
TypeScript Standards
No any Types
// Bad
const data: any = fetchData()
// Good
interface TradeData {
symbol: string
date: Date
pnl: number
}
const data: TradeData[] = fetchData()
Current Violations (Tech Debt):
- usePortfolio.ts: 11 instances (trade/metrics types)
- useMetrics.ts: 4 instances (sort comparisons)
- dataUtils.ts: 1 instance (Metrics interface)
- Total: 15 violations to fix (originally 16)
Strict Null Checks
// Bad
const value = data.find(x => x.id === id)
value.name // Could be undefined!
// Good
const value = data.find(x => x.id === id)
if (value) {
value.name // Type-safe
}
// Or with optional chaining
const name = data.find(x => x.id === id)?.name
Type Inference When Obvious
// Redundant
const count: number = 5
const name: string = 'Portfolio Buddy'
// Better (TypeScript infers)
const count = 5
const name = 'Portfolio Buddy'
// Explicit when needed
const metrics: Metric[] = [] // Empty array needs type
Component Size Limits
Max 200 Lines Per Component
When component exceeds 200 lines:
- Extract sub-components
- Move logic to custom hooks
- Extract utilities to utils/
Current Violations
⚠️ MUST REFACTOR:
- PortfolioSection.tsx: 591 lines (295% of limit)
- Extract EquityChartSection
- Extract PortfolioStats
- Extract ContractControls
- Keep only orchestration logic
Should refactor:
- App.tsx: 351 lines (175% of limit)
- Extract sections into components
- MetricsTable.tsx: 242 lines (121% of limit)
- Improved from 350 lines, still over limit
Refactoring Example
// Before: 591 lines in PortfolioSection
function PortfolioSection() {
// Contract multiplier logic (50 lines)
// Date filtering logic (40 lines)
// Chart configuration (100 lines)
// Statistics calculation (80 lines)
// Rendering logic (300+ lines)
}
// After: Split into focused pieces
function PortfolioSection() {
const portfolio = usePortfolio(files, dateRange)
const contracts = useContractMultipliers(portfolio.strategies)
return (
<div>
<ContractControls {...contracts} />
<EquityChartSection data={portfolio.equity} />
<PortfolioStats metrics={portfolio.metrics} />
</div>
)
}
File Organization
Actual Directory Structure
src/
├── components/
│ └── [AllComponents].tsx (flat structure, no subdirs)
├── hooks/
│ ├── useContractMultipliers.ts
│ ├── useMetrics.ts
│ ├── usePortfolio.ts
│ └── useSorting.ts
├── utils/
│ └── dataUtils.ts (metric calculations, parsing)
├── App.tsx
└── main.tsx
Note: No ui/ or charts/ subdirectories - components are flat in components/
Naming Conventions
- Components: PascalCase -
MetricsTable.tsx,CorrelationHeatmap.tsx - Hooks: camelCase with
useprefix -useMetrics.ts,useSorting.ts - Utils: camelCase -
calculateMetrics(),parseCSV() - Types/Interfaces: PascalCase -
interface Metric,type Trade
Error Handling
Always Handle Errors
// Bad
const data = await supabase.storage.upload(file)
// Good
const { data, error } = await supabase.storage.upload(file)
if (error) {
console.error('Upload failed:', error)
toast.error('Failed to upload file')
return
}
Use Try-Catch for Parsing
// CSV parsing with error handling
try {
const parsed = parseCSV(file)
setData(parsed.data)
if (parsed.errors.length > 0) {
setErrors(parsed.errors)
}
} catch (error) {
console.error('Parse error:', error)
toast.error('Invalid CSV format')
}
Error Boundaries
Current Status: No error boundaries implemented (tech debt)
Should add:
<ErrorBoundary fallback={<ErrorMessage />}>
<PortfolioSection />
</ErrorBoundary>
Performance
Memoization
// Expensive calculations
const metrics = useMemo(
() => calculateMetrics(portfolioData, riskFreeRate),
[portfolioData, riskFreeRate]
)
// Large data transformations
const correlationMatrix = useMemo(
() => buildCorrelationMatrix(selectedStrategies),
[selectedStrategies]
)
Callback Stability
// Prevent child re-renders
const handleSort = useCallback((column: string) => {
setSortColumn(column)
setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc')
}, [])
// Pass stable callback to children
<SortableHeader onSort={handleSort} />
Avoid Premature Optimization
- Build feature first
- Measure performance if issues arise
- Optimize based on profiling data
- Don't optimize without evidence
Chart.js Integration
Pattern for Chart Components
import { Line } from 'react-chartjs-2'
import { Chart as ChartJS, registerables } from 'chart.js'
import zoomPlugin from 'chartjs-plugin-zoom'
// Register plugins once
ChartJS.register(...registerables, zoomPlugin)
function EquityChart({ data }: { data: EquityData[] }) {
const chartData = useMemo(() => ({
labels: data.map(d => d.date),
datasets: [{
label: 'Equity',
data: data.map(d => d.value),
borderColor: 'rgb(75, 192, 192)',
}]
}), [data])
const options = useMemo(() => ({
responsive: true,
plugins: {
zoom: { enabled: true }
}
}), [])
return <Line data={chartData} options={options} />
}
Chart Libraries
- ✅ Use: Chart.js + react-chartjs-2
- ❌ Don't use: Recharts (installed but unused, should remove)
Testing Standards
What to Test
- ✅ Critical calculations (Sharpe, Sortino, correlation)
- ✅ Data transformations (CSV parsing, metric calculations)
- ✅ Error states and edge cases
- ✅ Hook return values
- ❌ UI implementation details (className, DOM structure)
- ❌ Third-party library internals
Test Structure
describe('calculateMetrics', () => {
it('calculates Sharpe ratio correctly', () => {
const trades = mockTradeData()
const result = calculateMetrics(trades, 0.02)
expect(result.sharpe).toBeCloseTo(1.5, 2)
})
it('handles empty data gracefully', () => {
const result = calculateMetrics([], 0.02)
expect(result.sharpe).toBe(0)
})
})
Current Status: No tests implemented (future work)
Import Organization
Order of Imports
// 1. React and external libraries
import { useState, useMemo, useCallback } from 'react'
import { Line } from 'react-chartjs-2'
// 2. Internal hooks
import { useMetrics } from '@/hooks/useMetrics'
import { usePortfolio } from '@/hooks/usePortfolio'
// 3. Utils and helpers
import { calculateMetrics, formatCurrency } from '@/utils/dataUtils'
// 4. Types
import type { Metric, Trade } from '@/types'
// 5. Styles (if any)
import './styles.css'
Code Comments
When to Comment
// Good: Explain WHY, not WHAT
// Annualize by multiplying by sqrt(252) trading days
const sharpe = (avgReturn / stdDev) * Math.sqrt(252)
// Bad: Obvious what the code does
// Calculate Sharpe ratio
const sharpe = (avgReturn / stdDev) * Math.sqrt(252)
JSDoc for Complex Functions
/**
* Calculate Sortino Ratio using downside deviation
* @param returns - Array of daily returns
* @param riskFreeRate - Annual risk-free rate (e.g., 0.02 for 2%)
* @param targetReturn - Target return threshold (default: 0)
* @returns Annualized Sortino Ratio
*/
function calculateSortino(
returns: number[],
riskFreeRate: number,
targetReturn = 0
): number {
// Implementation
}
Git Commit Messages
Format
<type>: <subject>
<body>
Types
feat:New featurefix:Bug fixrefactor:Code restructuringperf:Performance improvementdocs:Documentationtest:Test additions/changes
Examples from Recent Commits
Fix Sortino Ratio calculation by annualizing downside deviation and correcting variance calculation
Refactor portfolio calculations and enhance Supabase client validation; add risk-free rate input and Sortino Ratio calculation
Enhance error handling and validation in Supabase data fetching; update MetricsTable and PortfolioSection to manage selectedTradeLists state
Code Review Checklist
Before submitting code:
- TypeScript strict mode passes (no
anyunless documented as tech debt) - Component under 200 lines (or has refactor plan)
- Error handling in place
- Memoization for expensive calculations
- Stable callbacks with useCallback
- Proper TypeScript types (no
any) - Imports organized by category
- JSDoc on complex functions
- Console.logs removed
- Chart.js used (not Recharts)
Repository

5MinFutures
Author
5MinFutures/futures-arena/.claude/skills/coding-standards
0
Stars
0
Forks
Updated2d ago
Added1w ago