ag-grid-patterns

AG-Grid v34 integration patterns for TMNL. Invoke when implementing data grids, custom cell renderers, themes, or grid-based UI. Provides canonical file locations and pattern precedents.

16 stars

Best use case

ag-grid-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

AG-Grid v34 integration patterns for TMNL. Invoke when implementing data grids, custom cell renderers, themes, or grid-based UI. Provides canonical file locations and pattern precedents.

Teams using ag-grid-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ag-grid-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/ag-grid-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ag-grid-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ag-grid-patterns Compares

Feature / Agentag-grid-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

AG-Grid v34 integration patterns for TMNL. Invoke when implementing data grids, custom cell renderers, themes, or grid-based UI. Provides canonical file locations and pattern precedents.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# AG-Grid Patterns for TMNL

## Critical: AG-Grid v34 Module Registration

**Without this, grid renders blank. No exceptions.**

```typescript
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community'
ModuleRegistry.registerModules([AllCommunityModule])
```

No CSS imports needed when using the `theme` prop.

---

## Canonical Sources

### Core Library (v2)
- **Compound component**: `src/lib/data-grid/components/UnifiedDataGrid.tsx`
- **Context system**: `src/lib/data-grid/components/DataGridContext.tsx`
- **Theme composer**: `src/lib/data-grid/composer/theme-composer.ts`
- **Flash system**: `src/lib/data-grid/flash/index.ts`
- **Cell renderers**: `src/lib/data-grid/renderers/`
- **Variants**: `src/lib/data-grid/variants/`
- **Barrel export**: `src/lib/data-grid/index.ts`

### tldraw Integration
- **V2 shape (modern)**: `src/components/tldraw/shapes/data-grid-shape-v2.tsx`
- **V1 shape (legacy)**: `src/components/tldraw/shapes/data-grid-shape.tsx`
- **Hybrid drag**: `src/components/tldraw/shapes/data-grid-shape.tsx:366-594`

### Architecture Documentation
- **Deep dive**: `assets/documents/AG_GRID_THEMING_ARCHITECTURE.md`

---

## Pattern 1: Tmnl.DataGrid — COMPOUND COMPONENT API

**When:** Building any data grid in TMNL.

The modern API uses compound components for declarative composition.

```tsx
import { Tmnl } from '@/lib/data-grid'
import { tmnlDenseDark } from '@/lib/data-grid/variants/tmnl-dense-dark'

<Tmnl.DataGrid
  id="emitters"
  variant={tmnlDenseDark}
  rowData={data}
  columnDefs={columnDefs}
>
  <Tmnl.DataGrid.Header>
    <Tmnl.DataGrid.Title title="EMITTERS" badge={data.length} />
    <Tmnl.DataGrid.SettingsButton onClick={openSettings} />
  </Tmnl.DataGrid.Header>

  <Tmnl.DataGrid.Body />

  <Tmnl.DataGrid.StatusBar>
    <span className="text-cyan-500">V2</span>
    <span>{data.length} rows</span>
  </Tmnl.DataGrid.StatusBar>

  <Tmnl.DataGrid.CornerDecorations />
</Tmnl.DataGrid>
```

### Child Components

| Component | Purpose | Props |
|-----------|---------|-------|
| `Header` | Container for title/controls | children |
| `Title` | Grid title with optional badge | title, badge? |
| `SettingsButton` | Settings action button | onClick |
| `Body` | The actual AG-Grid | (none - uses context) |
| `StatusBar` | Footer status area | children |
| `CornerDecorations` | Visual corner accents | variant? |

### Per-Grid Runtime Isolation

Each `<Tmnl.DataGrid>` creates its own Effect runtime with isolated services:

```typescript
// Inside Tmnl.DataGrid provider
const runtime = useMemo(() => createDataGridRuntime(), [gridId])

// Child components access via context
const ctx = useDataGridContext()
const variant = ctx.variant      // Reactive variant
const gridApi = ctx.gridApi      // Reactive grid API
```

**Canonical source**: `src/lib/data-grid/components/UnifiedDataGrid.tsx`

---

## Pattern 2: GridVariant System — VARIANT-DRIVEN THEMING

**When:** Customizing grid appearance, density, or behavior.

Variants are **not just themes**. They encode:
- **Density** — Row height, font size, padding
- **Colors** — Background, text, signals, flash
- **Behavior** — Selection, sorting, drag, resize
- **Typography** — Font family, letter spacing, weight

### Variant Structure

```typescript
export const tmnlDenseDark: GridVariant = {
  id: 'tmnl-dense-dark',
  densityTier: 'dense',
  density: DENSITY_PRESETS.dense,
  colorScheme: 'dark',

  colors: {
    background: { base: '#000000', alternateRow: '#0a0a0a', header: '#0d0d0d' },
    text: { primary: '#ffffff', secondary: '#a3a3a3', muted: '#525252' },
    signal: { positive: '#22c55e', negative: '#ef4444', accent: '#00ffcc' },
    border: { primary: '#262626', muted: '#1a1a1a' },
    flash: {
      up: 'rgba(34, 197, 94, 0.4)',
      down: 'rgba(239, 68, 68, 0.4)',
      durationMs: 1500,
    },
  },

  behavior: BEHAVIOR_PRESETS.interactive,

  typography: {
    fontFamily: "'JetBrains Mono', monospace",
    headerLetterSpacing: '0.05em',
  },

  intentOverrides: {
    // Per-column styling rules
  },
}
```

### Density Presets

| Preset | Row Height | Font Size | Padding |
|--------|------------|-----------|---------|
| `dense` | 20px | 10px | 4px |
| `compact` | 24px | 11px | 6px |
| `normal` | 32px | 13px | 8px |
| `comfortable` | 40px | 14px | 12px |

### Behavior Presets

| Preset | Selection | Sorting | Drag | Resize |
|--------|-----------|---------|------|--------|
| `readonly` | none | true | false | false |
| `interactive` | single | true | true | true |
| `editable` | multiple | true | true | true |
| `minimal` | none | false | false | false |

### Variant → Theme Conversion

```typescript
import { composeAgGridTheme } from '@/lib/data-grid/composer/theme-composer'

const agTheme = composeAgGridTheme(tmnlDenseDark)
// Returns AG-Grid themeQuartz.withParams({...})
```

**Canonical source**: `src/lib/data-grid/variants/tmnl-dense-dark.ts`

---

## Pattern 3: Flash System — CELL CHANGE HIGHLIGHTING

**When:** Showing real-time data updates with visual feedback.

### Severity Mapping

| Delta | Severity | Visual Effect |
|-------|----------|---------------|
| 0 | none | No flash |
| 1-5 | low | Subtle background |
| 6-10 | medium | Visible pulse |
| 11-15 | high | Strong glow |
| 16+ | critical | Full glow + pulse |

### Flash State Structure

```typescript
interface FlashState {
  severity: 'none' | 'low' | 'medium' | 'high' | 'critical'
  intensity: number      // 0-1, logarithmic scale
  direction: 'up' | 'down' | 'neutral'
  delta: number
  timestamp: number
  isActive: boolean
}
```

### useFlashTracker Hook

```typescript
import { useFlashTracker } from '@/lib/data-grid/flash'

const { getFlashState, hasFlash, processUpdates, injectKeyframes } = useFlashTracker({
  maxDelta: 20,
  flashExpirationMs: 1500,
})

// Initialize (once)
useEffect(() => injectKeyframes(), [])

// Process row updates
useEffect(() => {
  processUpdates(newData, oldData, 'value')
}, [newData])

// In cell renderer
const flash = getFlashState(rowId, field)
const styles = generateFlashStyles(flash, { colors: variant.colors.flash })
```

### Flash in Cell Renderer

```tsx
function ValueCellRenderer(params: ICellRendererParams) {
  const ctx = useDataGridContext()
  const { getFlashState } = ctx.flash

  const flash = getFlashState(params.node.id, params.colDef.field)

  return (
    <div
      className={flash.isActive ? `flash-${flash.severity}` : ''}
      style={{
        backgroundColor: flash.isActive
          ? flash.direction === 'up'
            ? ctx.variant.colors.flash.up
            : ctx.variant.colors.flash.down
          : undefined,
      }}
    >
      {params.value}
    </div>
  )
}
```

**Canonical source**: `src/lib/data-grid/flash/index.ts`

---

## Pattern 4: DataGridContext — PER-GRID SERVICES

**When:** Child components need access to grid state, variant, or API.

### Context Shape

```typescript
interface DataGridContextValue<TData = unknown> {
  gridId: string
  runtime: DataGridRuntime           // Per-grid Effect services
  variant: GridVariantType           // Reactive variant
  rowData: TData[]
  columnDefs: ColDef<TData>[]
  getRowId?: GetRowIdFunc<TData>
  gridApi: GridApi | null            // AG-Grid API reference
  setGridApi: (api: GridApi | null) => void
  flash: FlashTrackerAPI
}
```

### Usage in Components

```typescript
// Required context (throws if missing)
const ctx = useDataGridContext()

// Optional context (returns null if outside grid)
const ctx = useDataGridContextMaybe()
```

### Color Extraction from Context

```typescript
function StatusCellRenderer(params: ICellRendererParams) {
  const ctx = useDataGridContextMaybe()

  // Variant colors with fallback
  const colors = ctx?.variant.colors ?? {
    signal: { positive: '#22c55e', negative: '#ef4444' }
  }

  return (
    <span style={{ color: colors.signal.positive }}>
      {params.value}
    </span>
  )
}
```

**Canonical source**: `src/lib/data-grid/components/DataGridContext.tsx`

---

## Pattern 5: Context-Aware Cell Renderers

**When:** Renderers need variant colors or grid services.

### Pattern: Fallback for Standalone Usage

```typescript
import { useDataGridContextMaybe } from '@/lib/data-grid'
import { COLORS } from '@/lib/tokens'

export function ValueCellRenderer(params: ValueCellRendererParams) {
  const ctx = useDataGridContextMaybe()

  // Colors from variant OR token fallback
  const textColor = ctx?.variant.colors.text.primary ?? COLORS.textPrimary
  const accentColor = ctx?.variant.colors.signal.accent ?? COLORS.accentCyan

  return (
    <div style={{ color: textColor }}>
      <span>{params.value}</span>
      <div
        style={{
          backgroundColor: accentColor,
          width: `${params.data.percentage}%`,
        }}
      />
    </div>
  )
}
```

### Pattern: Flash-Aware Renderer

```typescript
export function FlashValueRenderer(params: ICellRendererParams) {
  const ctx = useDataGridContext()
  const flash = ctx.flash.getFlashState(params.node.id, params.colDef.field!)

  return (
    <div
      className={cn(
        'transition-all duration-300',
        flash.isActive && `flash-${flash.severity}`,
        flash.direction === 'up' && 'text-green-400',
        flash.direction === 'down' && 'text-red-400',
      )}
    >
      {params.value}
    </div>
  )
}
```

**Canonical source**: `src/lib/data-grid/renderers/ValueCellRenderer.tsx`

---

## Pattern 6: Hybrid Drag System — GRID-TO-CANVAS

**When:** Dragging rows from AG-Grid onto a tldraw canvas.

### Drag Phase Transitions

```
┌─────────────────────────────────┐
│   AG-Grid Internal Drag         │
│   (rowDragManaged=true)         │
└──────────────┬──────────────────┘
               │ onRowDragMove
               ▼
        Check: outside grid bounds?
               │
        ┌──────┴──────┐
       NO            YES
        │             │
     Continue    Create ghost
     in grid     shape on canvas
        │             │
        │      onPointerMove
        │      updateGhost()
        │             │
        └─────┬───────┘
              │ onPointerUp
        spawnDataCard()
        Remove ghost
```

### Drag State Schema

```typescript
interface DragState {
  isDragging: boolean
  isOutsideGrid: boolean    // Key flag for phase transition
  rowData: DataGridRow | null
  ghostId: string | null    // tldraw shape ID
}
```

### Phase Handlers

```typescript
const onRowDragMove = useCallback((event: RowDragMoveEvent) => {
  const { clientX, clientY } = event.event
  const gridRect = gridRef.current?.getBoundingClientRect()

  const isOutside = !gridRect ||
    clientX < gridRect.left || clientX > gridRect.right ||
    clientY < gridRect.top || clientY > gridRect.bottom

  if (isOutside && !dragState.isOutsideGrid) {
    // Transition: GridInternal → CanvasTracking
    const ghostId = createGhostShape(dragState.rowData, { x: clientX, y: clientY })
    setDragState(prev => ({ ...prev, isOutsideGrid: true, ghostId }))
  } else if (!isOutside && dragState.isOutsideGrid) {
    // Transition: CanvasTracking → GridInternal
    removeGhostShape(dragState.ghostId)
    setDragState(prev => ({ ...prev, isOutsideGrid: false, ghostId: null }))
  }
}, [dragState, createGhostShape, removeGhostShape])
```

### Effect Service for Drag

```typescript
export interface GridDragServiceApi {
  readonly getState: Effect.Effect<DragState>
  readonly dispatch: (event: GridDragEvent) => Effect.Effect<void>
  readonly subscribe: (handler: (state: DragState) => void) => Effect.Effect<() => void>
  readonly isDragging: Effect.Effect<boolean>
  readonly getPhase: Effect.Effect<DragPhase>
}
```

**Canonical source**: `src/lib/data-grid/services/GridDragService.ts`

---

## Pattern 7: Theme Composition via composeAgGridTheme

**When:** Converting a GridVariant to an AG-Grid theme.

```typescript
import { themeQuartz } from 'ag-grid-community'

export function composeAgGridTheme(variant: GridVariant) {
  const { colors, density, typography } = variant

  return themeQuartz.withParams({
    // Core colors
    backgroundColor: colors.background.base,
    foregroundColor: colors.text.primary,
    accentColor: colors.signal.accent,

    // Header
    headerBackgroundColor: colors.background.header,
    headerTextColor: colors.text.secondary,

    // Rows
    oddRowBackgroundColor: colors.background.alternateRow,
    rowHoverColor: `${colors.background.base}cc`,
    selectedRowBackgroundColor: `${colors.signal.accent}15`,

    // Typography
    fontFamily: typography.fontFamily,
    fontSize: density.fontSize,
    headerFontSize: density.fontSizeXs,

    // Density-driven spacing
    rowHeight: density.rowHeight,
    headerHeight: density.headerHeight,
    cellHorizontalPaddingScale: density.paddingX / 8,

    // Borders
    borderColor: colors.border.primary,
    wrapperBorderRadius: 0,
  })
}
```

**Canonical source**: `src/lib/data-grid/composer/theme-composer.ts`

---

## Pattern 8: Color Extraction Helpers

**When:** Extracting semantic colors from variant for custom UI.

```typescript
export function extractStatusColors(variant: GridVariant) {
  return {
    active: variant.colors.signal.positive,
    pending: variant.colors.signal.warning ?? variant.colors.signal.accent,
    inactive: variant.colors.signal.neutral ?? variant.colors.text.muted,
    error: variant.colors.signal.negative,
    default: variant.colors.text.muted,
  } as const
}

export function extractFlashConfig(variant: GridVariant) {
  const flash = variant.colors.flash
  return {
    enabled: variant.behavior.microInteractions?.enableCellFlash ?? true,
    upColor: flash.up,
    downColor: flash.down,
    durationMs: flash.durationMs,
  }
}
```

---

## Pattern 9: Legacy Basic Patterns

### Theme Creation via themeQuartz (Direct)

```typescript
import { themeQuartz } from 'ag-grid-community'
import { TMNL_TOKENS } from './data-grid-theme'

export const tmnlDataGridTheme = themeQuartz.withParams({
  backgroundColor: TMNL_TOKENS.colors.background,
  foregroundColor: TMNL_TOKENS.colors.text.primary,
  borderColor: TMNL_TOKENS.colors.border,
  headerBackgroundColor: TMNL_TOKENS.colors.surface,
  // ... etc
})
```

### Basic Cell Renderer (Non-Context)

```typescript
const IdCellRenderer = (params: ICellRendererParams) => (
  <span
    style={{
      color: TMNL_TOKENS.colors.text.muted,
      fontSize: TMNL_TOKENS.typography.sizes.xs,
      fontFamily: TMNL_TOKENS.typography.fontFamily,
      letterSpacing: '0.05em',
    }}
  >
    {params.value}
  </span>
)
```

### Column Definitions

```typescript
const columnDefs: ColDef[] = [
  {
    field: 'id',
    headerName: 'ID',
    width: 80,
    cellRenderer: IdCellRenderer,
    sortable: true,
  },
  {
    field: 'value',
    headerName: 'Value',
    width: 120,
    cellRenderer: ValueCellRenderer,
    comparator: (a, b) => a - b,
  },
]
```

---

## Anti-Patterns (BANNED)

### 1. Importing CSS Files

```typescript
// BANNED - No CSS imports with v34
import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-quartz.css'
// Use theme prop instead
```

### 2. Missing Module Registration

```typescript
// BANNED - Grid will render blank
<AgGridReact rowData={data} columnDefs={cols} />
// Must register modules FIRST
```

### 3. useState for Grid Data

```typescript
// BANNED when data crosses boundaries
const [rowData, setRowData] = useState([])
// Use Atom.make + service methods instead
```

### 4. Direct Theme Customization

```typescript
// BANNED - Bypasses variant system
const theme = themeQuartz.withParams({ backgroundColor: '#123' })
// Use GridVariant + composeAgGridTheme() instead
```

### 5. Cell Renderer Without Context Fallback

```typescript
// BANNED - Breaks outside grid context
function BadRenderer(params) {
  const ctx = useDataGridContext()  // Throws if no context!
  return <span style={{ color: ctx.variant.colors.text.primary }}>...</span>
}

// CORRECT - Graceful fallback
function GoodRenderer(params) {
  const ctx = useDataGridContextMaybe()
  const color = ctx?.variant.colors.text.primary ?? COLORS.textPrimary
  return <span style={{ color }}>...</span>
}
```

### 6. Creating Atoms Inside Components

```typescript
// BANNED - Recreates on every render
function BadGrid() {
  const rowDataAtom = Atom.make([])  // BAD!
  return <AgGridReact ... />
}

// CORRECT - Module-level atoms
const rowDataAtom = Atom.make<RowData[]>([])
function GoodGrid() {
  const rowData = useAtomValue(rowDataAtom)
  return <AgGridReact rowData={rowData} ... />
}
```

---

## Decision Tree: Which API to Use

```
Building a data grid?
│
├─ Simple, one-off grid?
│  └─ Use: Direct AgGridReact with composeAgGridTheme()
│
├─ Grid with header, status bar, controls?
│  └─ Use: Tmnl.DataGrid compound component
│
├─ Grid in tldraw shape?
│  └─ Use: V2 pattern (data-grid-shape-v2.tsx)
│
├─ Need real-time flash updates?
│  └─ Use: useFlashTracker + FlashValueRenderer
│
└─ Need custom variant?
   └─ Clone tmnlDenseDark and modify
```

---

## File Locations Summary

| Component | File | Purpose |
|-----------|------|---------|
| **Tmnl.DataGrid** | `src/lib/data-grid/components/UnifiedDataGrid.tsx` | Compound component |
| **DataGridContext** | `src/lib/data-grid/components/DataGridContext.tsx` | Per-grid context |
| **composeAgGridTheme** | `src/lib/data-grid/composer/theme-composer.ts` | Variant → theme |
| **Flash system** | `src/lib/data-grid/flash/index.ts` | Cell highlighting |
| **ValueCellRenderer** | `src/lib/data-grid/renderers/ValueCellRenderer.tsx` | Context-aware |
| **tmnlDenseDark** | `src/lib/data-grid/variants/tmnl-dense-dark.ts` | Canonical variant |
| **V2 tldraw shape** | `src/components/tldraw/shapes/data-grid-shape-v2.tsx` | Modern integration |
| **Hybrid drag** | `src/components/tldraw/shapes/data-grid-shape.tsx:366-594` | Grid-to-canvas |

---

## Integration Points

- **effect-atom-integration** — Atom-as-State for rowData
- **tmnl-design-tokens** — Token fallbacks in renderers
- **common-conventions** — Barrel exports, naming patterns
- **effect-patterns** — Effect.Service for GridDragService

Related Skills

ai-sdk-patterns

16
from diegosouzapw/awesome-omni-skill

Vercel AI SDK tool patterns for dx-toolkit - input schemas for smart queries, API key handling, raw response returns

ai-cache-patterns

16
from diegosouzapw/awesome-omni-skill

Embedding/vector caching for AI cost optimization

ai-anti-patterns

16
from diegosouzapw/awesome-omni-skill

This skill should be used when reviewing AI-generated text, checking for AI writing patterns, detecting undisclosed AI content, or before finalizing any written content. Covers 12 categories of AI writing indicators from Wikipedia's comprehensive guide.

agency-workflow-patterns

16
from diegosouzapw/awesome-omni-skill

Master orchestration patterns, multi-agent coordination, and effective workflow composition using the Agency plugin's 51+ specialized agents. Activate when planning complex implementations, coordinating multiple agents, or optimizing development workflows.

advanced-typescript-patterns

16
from diegosouzapw/awesome-omni-skill

Advanced TypeScript patterns for TMNL. Covers conditional types, mapped types, branded types, generic constraints, type inference, and utility type composition. Pure TypeScript patterns beyond Effect Schema.

Advanced GetX Patterns

16
from diegosouzapw/awesome-omni-skill

Advanced GetX features including Workers, GetxService, SmartManagement, GetConnect, GetSocket, bindings composition, and testing patterns

ActiveRecord Query Patterns

16
from diegosouzapw/awesome-omni-skill

Complete guide to ActiveRecord query optimization, associations, scopes, and PostgreSQL-specific patterns. Use this skill when writing database queries, designing model associations, creating migrations, optimizing query performance, or debugging N+1 queries and grouping errors.

Action Cable & WebSocket Patterns

16
from diegosouzapw/awesome-omni-skill

Real-time WebSocket features with Action Cable in Rails. Use when: (1) Building real-time chat, (2) Live notifications/presence, (3) Broadcasting model updates, (4) WebSocket authorization. Trigger keywords: Action Cable, WebSocket, real-time, channels, broadcasting, stream, subscriptions, presence, cable

accessibility-patterns

16
from diegosouzapw/awesome-omni-skill

Build inclusive web experiences following WCAG guidelines. Covers semantic HTML, ARIA, keyboard navigation, color contrast, and testing strategies. Triggers on accessibility, a11y, WCAG, screen readers, or inclusive design requests.

abp-service-patterns

16
from diegosouzapw/awesome-omni-skill

ABP Framework application layer patterns including AppServices, DTOs, Mapperly mapping, Unit of Work, and common patterns like Filter DTOs and ResponseModel. Use when: (1) creating AppServices, (2) mapping DTOs with Mapperly, (3) implementing list filtering, (4) wrapping API responses.

abp-infrastructure-patterns

16
from diegosouzapw/awesome-omni-skill

ABP Framework cross-cutting patterns including authorization, background jobs, distributed events, multi-tenancy, and module configuration. Use when: (1) defining permissions, (2) creating background jobs, (3) publishing/handling distributed events, (4) configuring modules.

abp-entity-patterns

16
from diegosouzapw/awesome-omni-skill

ABP Framework domain layer patterns including entities, aggregates, repositories, domain services, and data seeding. Use when: (1) creating entities with proper base classes, (2) implementing custom repositories, (3) writing domain services, (4) seeding data.