Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .claude/rules/charts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Charts

## Visual Standards

**Axis Lines:**

- **ALWAYS** render both y-axis and x-axis lines unless specifically not needed
- Set `axisLine: { show: true }` for both xAxis and yAxis

**Grid Lines:**

- **NEVER** render grid lines
- Set `splitLine: { show: false }` for both xAxis and yAxis

**Axis Ranges & Intervals:**

- yMax/xMax must work with consistent, evenly-spaced intervals
- BAD: `0, 200, 400, 600, 800, 1000, 1100` (inconsistent final interval)
- GOOD: `0, 200, 400, 600, 800, 1000` OR `0, 250, 500, 750, 1000, 1250`
- Prefer setting yMax/xMax with `splitNumber` and let ECharts calculate intervals automatically
- Example: `yAxis: { max: 1000, splitNumber: 5 }` → generates [0, 200, 400, 600, 800, 1000]

**Grid Padding:**

- Minimize whitespace while ensuring labels are visible
- **Use simple explicit padding values** and let ECharts handle label positioning:
- Set `left`, `right`, `top`, `bottom` values
- No need for `containLabel`, `align`, `inside`, `margin` - let ECharts use defaults
- **Standard padding values:**
- **Left**: 60px (for y-axis labels and name)
- **Right**: 24px (minimal, 80px if dual y-axes present)
- **Top**: 16px (40px if ECharts title is used)
- **Bottom**: 50px (90px if native legend at bottom)
- Override only when absolutely necessary (e.g., dual y-axes, legends, special layouts)
- Wrappers should never override grid config unless they have very specific layout needs

**Slot Time Formatting:**

- When plotting slot time on xAxis:
- **Title**: Must be "Slot Time (s)"
- **Units**: Use seconds (not milliseconds)
- **Precision**: Round to the nearest second
- **Ticks**: Default ticks at 0, 4s, 8s, 12s (unless otherwise specified)
- **Format**: Display as "0", "4", "8", "12" (numbers only, no "s" suffix in tick labels)

## Component Architecture

**Core Chart Components** (`src/components/Charts/`):

- Define sensible defaults for all visual standards above
- Provide consistent `gridConfig`, axis styling, and formatting
- Should be production-ready without overrides

**Wrapper Components** (page-scoped chart components):

- **Defer to core components** for most settings
- Only override when directly needed for page-specific requirements
- **Do NOT override** `gridConfig` unless absolutely necessary
- Pass through props to core components rather than recreating configuration

**Example Pattern:**

```tsx
// BAD: Wrapper recreates all config
<LineChart
gridConfig={{ left: 60, right: 20, top: 60, bottom: 60 }}
xAxis={{ type: 'value', axisLine: { show: true }, splitLine: { show: false } }}
yAxis={{ type: 'value', axisLine: { show: true }, splitLine: { show: false } }}
/>

// GOOD: Wrapper defers to core component
<LineChart
data={data}
xAxisLabel="Slot Time (s)"
// Only override what's truly page-specific
/>
```

## Shared Crosshairs

Charts sharing the same x-axis can synchronize tooltips and crosshairs using the `syncGroup` prop:

- Use `syncGroup="slot-time"` for charts with slot time x-axis (0-12s)
- Use `syncGroup="slot-number"` for charts with slot number x-axis
- Omit `syncGroup` for independent charts
36 changes: 36 additions & 0 deletions .claude/rules/forms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Form Management

## Zod & URL Search Params

Zod schemas are used for **TanStack Router search param validation**, not react-hook-form resolvers:

```tsx
// In route files (e.g., src/routes/ethereum/slots/$slot.tsx)
const slotSearchSchema = z.object({
network: z.string().optional(),
tab: z.enum(['overview', 'attestations']).optional(),
});

export const Route = createFileRoute('/ethereum/slots/$slot')({
validateSearch: slotSearchSchema,
});
```

Type the search params in page components:

```tsx
type SearchParams = z.infer<typeof slotSearchSchema>;
```

## react-hook-form

- `useForm` is used for simple form state management (filters, inputs)
- One `FormProvider` per page when sharing form state across child components
- Child components access form state via `useFormContext()`
- No Zod resolvers are used with react-hook-form — Zod is for route validation only

## Placement

- Generic filter components → `src/components/Forms/`
- Page-specific form components → `src/pages/[section]/[page-name]/components/`
- Search param types → `src/pages/[section]/[page-name]/IndexPage.types.ts`
35 changes: 35 additions & 0 deletions .claude/rules/seo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Head Meta & SEO

## Meta Hierarchy

- Base meta tags defined in `src/routes/__root.tsx`
- Routes override with `head: () => ({ meta: [...] })`
- Child routes inherit and can extend parent meta
- **No variables in head**: Only use literals and `import.meta.env.VITE_*` (processed by build plugin)

## Page Feature Images

Each page should have a feature image at `public/images/[section]/[page-name].png`.

**Route implementation:**

```tsx
// In routes/[section]/[page-name].tsx
head: () => ({
meta: [
{ title: `Page Name | ${import.meta.env.VITE_BASE_TITLE}` },
{ name: 'description', content: 'Unique description of what this page does' },
{ property: 'og:image', content: '/images/[section]/[page-name].png' },
{ property: 'og:description', content: 'Unique description of what this page does' },
{ name: 'twitter:image', content: '/images/[section]/[page-name].png' },
{ name: 'twitter:description', content: 'Unique description of what this page does' },
],
})
```

## Checklist

- Always include page-specific title
- Write unique description for each page
- Update all three descriptions (meta, og:description, twitter:description)
- Provide unique feature image per page
15 changes: 15 additions & 0 deletions .claude/rules/storybook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Storybook

When creating a new story, add the following decorators:

```tsx
decorators: [
Story => (
<div className="min-w-[600px] rounded-sm bg-surface p-6">
<Story />
</div>
),
],
```

When choosing a title, use the full nested path to the story, e.g. `Components/Layout/Container`.
28 changes: 28 additions & 0 deletions .claude/rules/theming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Theming

Theme uses a **two-tier color architecture** defined in `src/index.css`:

**Tier 1:** Primitive scales (terracotta, sand, neutral) with 50-950 shades
**Tier 2:** Semantic tokens that reference Tier 1

## Semantic Tokens

- `primary`, `secondary`, `accent` - Brand colors
- `background`, `surface`, `foreground`, `muted`, `border` - UI colors
- `success`, `warning`, `danger` - State colors

## Usage

```tsx
// Always use semantic tokens
className="bg-primary text-foreground border-border"
className="hover:bg-accent text-muted"

// Programmatic access
import { useThemeColors } from '@/hooks/useThemeColors';
const colors = useThemeColors(); // { primary, background, ... }
```

**Never use primitive scales directly** (`bg-terracotta-500`, `bg-sand-100`) - only semantic tokens.

**Modify theme:** Edit semantic mappings in `src/index.css` at `@layer base` (`:root` for light, `html.dark` for dark).
Loading