Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
.claude/skills/aiskillstore-coding-standards/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 21% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 49% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 117% | 0% |
typescript// 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> }
use - useMetrics, usePortfolio, useSortingPortfolio Buddy 2 uses PLAIN REACT HOOKS ONLY:
useStateuseMemouseCallbackuseRefNO global state libraries:
Pattern: Props down, custom hooks for shared logic
typescript// 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]) }, [])
any Typestypescript// Bad const data: any = fetchData() // Good interface TradeData { symbol: string date: Date pnl: number } const data: TradeData[] = fetchData()
Current Violations (Tech Debt):
typescript// 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
typescript// 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
When component exceeds 200 lines:
⚠️ MUST REFACTOR:
Should refactor:
typescript// 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> ) }
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.tsxNote: No ui/ or charts/ subdirectories - components are flat in components/
MetricsTable.tsx, CorrelationHeatmap.tsxuse prefix - useMetrics.ts, useSorting.tscalculateMetrics(), parseCSV()interface Metric, type Tradetypescript// 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 }
typescript// 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') }
Current Status: No error boundaries implemented (tech debt)
Should add:
typescript<ErrorBoundary fallback={<ErrorMessage />}> <PortfolioSection /> </ErrorBoundary>
typescript// Expensive calculations const metrics = useMemo( () => calculateMetrics(portfolioData, riskFreeRate), [portfolioData, riskFreeRate] ) // Large data transformations const correlationMatrix = useMemo( () => buildCorrelationMatrix(selectedStrategies), [selectedStrategies] )
typescript// 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} />
typescriptimport { 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} /> }
typescriptdescribe('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)
typescript// 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'
typescript// 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)
typescript/** * 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 }
<type>: <subject>
<body>feat: New featurefix: Bug fixrefactor: Code restructuringperf: Performance improvementdocs: Documentationtest: Test additions/changesFix 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 stateBefore submitting code:
any unless documented as tech debt)any)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→pass | 17,934 | 10,410 | -42% | 1 | 1 | 0% | 3,330 | 4,950 | +49% | 0 | 0 | — |
case-01 | pass→pass | 22,125 | 18,856 | -15% | 1 | 1 | 0% | 2,042 | 7,053 | +245% | 0 | 0 | — |
case-02 | fail→pass | 45,806 | 10,868 | -76% | 1 | 1 | 0% | 4,193 | 5,077 | +21% | 0 | 0 | — |
case-03 | fail→pass | 24,718 | 20,891 | -15% | 1 | 1 | 0% | 5,130 | 7,628 | +49% | 0 | 0 | — |
case-05 | fail→pass | 11,858 | 8,732 | -26% | 1 | 1 | 0% | 2,418 | 4,740 | +96% | 0 | 0 | — |
case-06 | pass→pass | 7,620 | 7,511 | -1% | 1 | 1 | 0% | 1,371 | 4,215 | +207% | 0 | 0 | — |
case-07 | fail→pass | 12,753 | 3,417 | -73% | 1 | 1 | 0% | 1,638 | 3,549 | +117% | 0 | 0 | — |
case-08 | pass→fail | 10,692 | 4,241 | -60% | 1 | 1 | 0% | 2,245 | 3,132 | +40% | 0 | 0 | — |
case-14 | pass→pass | 16,025 | 8,973 | -44% | 1 | 1 | 0% | 2,788 | 4,634 | +66% | 0 | 0 | — |
case-09 | pass→pass | 4,482 | 3,416 | -24% | 1 | 1 | 0% | 799 | 3,618 | +353% | 0 | 0 | — |
case-10 | pass→fail | 6,096 | 4,804 | -21% | 1 | 1 | 0% | 1,122 | 3,684 | +228% | 0 | 0 | — |
case-11 | pass→pass | 15,395 | 9,878 | -36% | 1 | 1 | 0% | 2,895 | 4,823 | +67% | 0 | 0 | — |
case-12 | fail→fail | 6,671 | 4,431 | -34% | 1 | 1 | 0% | 1,057 | 3,618 | +242% | 0 | 0 | — |
case-13 | pass→pass | 10,093 | 6,675 | -34% | 1 | 1 | 0% | 1,877 | 4,123 | +120% | 0 | 0 | — |
case-15 | pass→pass | 10,032 | 9,034 | -10% | 1 | 1 | 0% | 1,721 | 4,722 | +174% | 0 | 0 | — |
case-16 | pass→pass | 17,591 | 17,496 | -1% | 1 | 1 | 0% | 3,257 | 6,291 | +93% | 0 | 0 | — |
case-17 | pass→pass | 10,483 | 7,690 | -27% | 1 | 1 | 0% | 1,780 | 4,228 | +138% | 0 | 0 | — |
case-18 | pass→pass | 3,768 | 2,677 | -29% | 1 | 1 | 0% | 658 | 3,256 | +395% | 0 | 0 | — |
case-19 | fail→pass | 16,736 | 4,750 | -72% | 1 | 1 | 0% | 2,476 | 3,744 | +51% | 0 | 0 | — |
case-20 | pass→pass | 10,254 | 8,690 | -15% | 1 | 1 | 0% | 1,796 | 4,587 | +155% | 0 | 0 | — |
case-21 | pass→pass | 5,317 | 4,493 | -15% | 1 | 1 | 0% | 935 | 3,662 | +292% | 0 | 0 | — |
case-22 | pass→pass | 17,675 | 19,332 | +9% | 1 | 1 | 0% | 3,627 | 7,056 | +95% | 0 | 0 | — |
case-23 | fail→pass | 14,552 | 9,766 | -33% | 1 | 1 | 0% | 2,138 | 4,668 | +118% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +22 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.