acx.code.assistant

Generate code scaffolds following Carbon ACX conventions for React components, TypeScript APIs, Cloudflare Workers, Python scripts, and tests.

$ Installer

git clone https://github.com/chrislyons/carbon-acx /tmp/carbon-acx && cp -r /tmp/carbon-acx/.claude/skills/project/acx-code-assistant ~/.claude/skills/carbon-acx

// tip: Run this command in your terminal to install the skill


name: acx.code.assistant description: Generate code scaffolds following Carbon ACX conventions for React components, TypeScript APIs, Cloudflare Workers, Python scripts, and tests.

acx.code.assistant

Purpose

This skill enables Claude to generate production-ready code for Carbon ACX that adheres to repository conventions, architectural patterns, and quality standards defined in AGENTS.md, CLAUDE.md, and CONTRIBUTING.md.

Capabilities:

  • Generate React components with proper TypeScript typing
  • Create Cloudflare Worker endpoints with runtime constraints
  • Scaffold Python data pipeline scripts
  • Generate test files (Vitest, pytest)
  • Create typed API client code
  • Follow monorepo structure (pnpm workspaces)
  • NEW: Generate 3D visualization components with Three.js/React Three Fiber
  • NEW: Create 2D overlay components (modals, panels) for transparency
  • NEW: Build Apache ECharts visualizations with design tokens

Quality Enforcement:

  • All code passes linters (eslint, prettier, ruff, black)
  • Includes proper error handling
  • TypeScript strict mode compliance
  • Includes basic tests
  • Follows AGENTS.md review gates
  • NEW: Uses design tokens (CSS custom properties)
  • NEW: Follows 3D+2D hybrid architecture (viz in canvas, transparency in overlays)

When to Use

Trigger Patterns:

  • "Create a React component for displaying emission trends"
  • "Generate a Cloudflare Worker endpoint for /api/emissions"
  • "Scaffold a Python script to process activity data"
  • "Write a test for the EmissionCalculator class"
  • "Create a typed API client for the carbon data service"
  • "Generate boilerplate for a new layer in the Dash app"
  • NEW: "Create a 3D sphere visualization component for..."
  • NEW: "Build a citation panel modal for emission factor sources"
  • NEW: "Generate an ECharts visualization for timeline data"
  • NEW: "Create a Zustand store slice for..."

Do NOT Use When:

  • User asks analytical questions (use carbon.data.qa)
  • User wants reports (use carbon.report.gen)
  • User wants to validate schemas (use schema.linter)
  • Modifying core architecture (requires human design review first)

Allowed Tools

  • read_file - Read existing code for context and patterns
  • write_file - Create new code files
  • edit_file - Modify existing files (with caution)
  • bash - Run linters, formatters, tests to verify code quality

Access Level: 2 (File Modification - can create/edit code files with human review)

Tool Rationale:

  • read_file: Required to understand existing patterns and conventions
  • write_file: Required to generate new code files
  • edit_file: Allowed for targeted edits (must preserve existing logic)
  • bash: Needed to run quality checks (eslint, pytest, etc.)

Explicitly Denied:

  • No direct deployment or production changes
  • No modifications to wrangler.toml, .github/workflows/, Makefile without explicit user approval (high-risk per AGENTS.md)
  • No committing or pushing code (human must review first)

Expected I/O

Input:

  • Type: Code generation request
  • Format: Natural language description of desired code
  • Required Context:
    • What to build (component, endpoint, script, test)
    • Where it fits (file path or module)
    • Key functionality
  • Optional Context:
    • Specific technologies/libraries
    • Integration points
    • Edge cases to handle

Example:

"Create a React component that displays a bar chart of emissions by layer using our existing chart library"

Output:

  • Type: Code file(s) with proper structure
  • Format: TypeScript (.tsx, .ts), Python (.py), or JavaScript (.js)
  • Location: Appropriate directory per repo structure
  • Includes:
    • Proper imports
    • TypeScript types/interfaces
    • Error handling
    • JSDoc/docstring comments
    • Basic test file (separate)
  • Validation:
    • Runs through linter without errors
    • Follows naming conventions
    • Includes type safety
    • No TODOs without tracking

Dependencies

Required:

  • Access to Carbon ACX repository structure
  • Understanding of AGENTS.md conventions
  • Knowledge of tech stack:
    • TypeScript 5.5+
    • React 18
    • Vite 5
    • Python 3.11+
    • Cloudflare Workers runtime
  • Reference files:
    • reference/conventions.md - Coding standards
    • AGENTS.md - AI development guidelines
    • CLAUDE.md - Repository-specific rules

Code Quality Tools:

  • ESLint + Prettier (TypeScript/JavaScript)
  • Ruff + Black (Python)
  • Vitest (JavaScript tests)
  • pytest (Python tests)

Examples

Example 1: React Component Generation

User: "Create a React component for displaying a list of activities with their emission factors"

Claude Process:

  1. Read existing component patterns (e.g., from apps/carbon-acx-web/src/components/)
  2. Check conventions in reference/conventions.md
  3. Generate component with:
    • Proper TypeScript types
    • Props interface
    • Error handling
    • Accessibility attributes
  4. Generate companion test file
  5. Run prettier/eslint to verify
  6. Save to appropriate directory

Output File: apps/carbon-acx-web/src/components/ActivityList.tsx

import React from 'react';

interface Activity {
  activity_id: string;
  name: string;
  emission_factor?: number;
  unit?: string;
}

interface ActivityListProps {
  activities: Activity[];
  onActivitySelect?: (activity: Activity) => void;
}

/**
 * Displays a list of activities with their emission factors
 * @param activities - Array of activity objects
 * @param onActivitySelect - Optional callback when activity is clicked
 */
export const ActivityList: React.FC<ActivityListProps> = ({
  activities,
  onActivitySelect,
}) => {
  if (!activities || activities.length === 0) {
    return <p className="text-gray-500">No activities available</p>;
  }

  return (
    <ul className="space-y-2" role="list">
      {activities.map((activity) => (
        <li
          key={activity.activity_id}
          className="p-4 border rounded hover:bg-gray-50 cursor-pointer"
          onClick={() => onActivitySelect?.(activity)}
          role="listitem"
        >
          <div className="font-semibold">{activity.name}</div>
          {activity.emission_factor && (
            <div className="text-sm text-gray-600">
              {activity.emission_factor} kgCO2e/{activity.unit || 'unit'}
            </div>
          )}
        </li>
      ))}
    </ul>
  );
};

Test File: apps/carbon-acx-web/src/components/ActivityList.test.tsx

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { ActivityList } from './ActivityList';

describe('ActivityList', () => {
  const mockActivities = [
    { activity_id: 'TEST.1', name: 'Test Activity', emission_factor: 1.5, unit: 'hour' },
  ];

  it('renders activities correctly', () => {
    render(<ActivityList activities={mockActivities} />);
    expect(screen.getByText('Test Activity')).toBeInTheDocument();
    expect(screen.getByText(/1.5 kgCO2e/)).toBeInTheDocument();
  });

  it('shows message when no activities', () => {
    render(<ActivityList activities={[]} />);
    expect(screen.getByText(/No activities available/)).toBeInTheDocument();
  });
});

Example 2: Cloudflare Worker Endpoint

User: "Create a Cloudflare Worker endpoint at /api/layers that returns all layer data"

Claude Process:

  1. Read existing Worker code in workers/compute/index.ts
  2. Understand Cloudflare runtime constraints (no Node.js APIs)
  3. Generate endpoint with:
    • Proper error handling
    • CORS headers
    • Input validation
    • Response typing
  4. Note binding requirements in comments

Output File: workers/compute/routes/layers.ts

/**
 * GET /api/layers
 * Returns all carbon accounting layers
 *
 * Runtime: Cloudflare Workers (no Node.js APIs)
 * Bindings: None required
 */

interface Layer {
  layer_id: string;
  layer_name: string;
  layer_type: string;
  description: string;
}

export async function handleLayersRequest(request: Request): Promise<Response> {
  // Validate request method
  if (request.method !== 'GET') {
    return new Response(
      JSON.stringify({ error: 'Method not allowed' }),
      { status: 405, headers: { 'Content-Type': 'application/json' } }
    );
  }

  try {
    // In production, this would load from KV or R2
    // For now, return static demo data
    const layers: Layer[] = [
      {
        layer_id: 'professional',
        layer_name: 'Professional Services',
        layer_type: 'civilian',
        description: 'Professional and consumer activities',
      },
      {
        layer_id: 'online',
        layer_name: 'Digital Infrastructure',
        layer_type: 'civilian',
        description: 'Online services and digital operations',
      },
      // Add more layers...
    ];

    return new Response(JSON.stringify({ layers, count: layers.length }), {
      status: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*', // Adjust for production
        'Cache-Control': 'public, max-age=3600',
      },
    });
  } catch (error) {
    console.error('Error fetching layers:', error);
    return new Response(
      JSON.stringify({ error: 'Internal server error' }),
      { status: 500, headers: { 'Content-Type': 'application/json' } }
    );
  }
}

Integration Note: Add route to main Worker in workers/compute/index.ts:

import { handleLayersRequest } from './routes/layers';

// In fetch handler:
if (url.pathname === '/api/layers') {
  return handleLayersRequest(request);
}

Example 3: Python Data Processing Script

User: "Create a Python script to aggregate emission factors by layer"

Output: scripts/aggregate_by_layer.py

"""
Aggregate emission factors by layer.

Reads activities.csv and emission_factors.csv, then produces a summary
of total emission factors grouped by layer_id.
"""

import pandas as pd
from pathlib import Path


def aggregate_by_layer(data_dir: Path = Path("data")) -> pd.DataFrame:
    """
    Aggregate emission factors by layer.

    Args:
        data_dir: Path to data directory containing CSV files

    Returns:
        DataFrame with columns: layer_id, layer_name, total_ef, activity_count

    Raises:
        FileNotFoundError: If required CSV files are missing
        ValueError: If data validation fails
    """
    # Load data
    activities_path = data_dir / "activities.csv"
    emission_factors_path = data_dir / "emission_factors.csv"
    layers_path = data_dir / "layers.csv"

    if not activities_path.exists():
        raise FileNotFoundError(f"Activities file not found: {activities_path}")

    activities = pd.read_csv(activities_path)
    emission_factors = pd.read_csv(emission_factors_path)
    layers = pd.read_csv(layers_path)

    # Merge activities with emission factors
    merged = activities.merge(
        emission_factors, on="activity_id", how="inner"
    )

    # Aggregate by layer
    aggregated = (
        merged.groupby("layer_id")
        .agg(
            total_ef=("emission_factor_kg", "sum"),
            activity_count=("activity_id", "count"),
        )
        .reset_index()
    )

    # Add layer names
    aggregated = aggregated.merge(
        layers[["layer_id", "layer_name"]], on="layer_id", how="left"
    )

    # Sort by total emission factor descending
    aggregated = aggregated.sort_values("total_ef", ascending=False)

    return aggregated[["layer_id", "layer_name", "total_ef", "activity_count"]]


def main():
    """CLI entry point."""
    result = aggregate_by_layer()

    print("\n=== Emission Factors by Layer ===\n")
    print(result.to_string(index=False))
    print(f"\nTotal activities: {result['activity_count'].sum()}")
    print(f"Total emission factors: {result['total_ef'].sum():.2f} kgCO2e\n")


if __name__ == "__main__":
    main()

Test File: tests/test_aggregate_by_layer.py

import pytest
import pandas as pd
from pathlib import Path
from scripts.aggregate_by_layer import aggregate_by_layer


def test_aggregate_by_layer(tmp_path):
    """Test layer aggregation with mock data."""
    # Create mock CSV files
    activities_data = pd.DataFrame({
        "activity_id": ["A1", "A2", "A3"],
        "layer_id": ["professional", "professional", "online"],
        "name": ["Activity 1", "Activity 2", "Activity 3"],
    })

    ef_data = pd.DataFrame({
        "activity_id": ["A1", "A2", "A3"],
        "emission_factor_kg": [10.5, 20.3, 15.7],
    })

    layers_data = pd.DataFrame({
        "layer_id": ["professional", "online"],
        "layer_name": ["Professional Services", "Digital Infrastructure"],
    })

    # Save to temp directory
    activities_data.to_csv(tmp_path / "activities.csv", index=False)
    ef_data.to_csv(tmp_path / "emission_factors.csv", index=False)
    layers_data.to_csv(tmp_path / "layers.csv", index=False)

    # Run aggregation
    result = aggregate_by_layer(data_dir=tmp_path)

    # Assertions
    assert len(result) == 2
    assert "professional" in result["layer_id"].values
    assert "online" in result["layer_id"].values

    prof_row = result[result["layer_id"] == "professional"].iloc[0]
    assert prof_row["total_ef"] == pytest.approx(30.8)  # 10.5 + 20.3
    assert prof_row["activity_count"] == 2

Limitations

Scope Limitations:

  • Cannot make architectural decisions (requires human design)
  • Cannot modify deployment configs without approval (per AGENTS.md)
  • Cannot bypass review gates for security-sensitive code
  • Cannot write code using technologies not in the stack

Code Quality Constraints:

  • Must pass linters (may need manual fixes for edge cases)
  • TypeScript strict mode compliance (may require additional type definitions)
  • Test coverage expectations (basic tests provided, comprehensive coverage requires expansion)

Knowledge Boundaries:

  • Code generated based on existing patterns - novel patterns may need review
  • Cloudflare Workers runtime constraints must be respected
  • Brand/style guidelines from reference/conventions.md must be followed

Validation Criteria

Success Metrics:

  • ✅ Code compiles/runs without errors
  • ✅ Passes linter (eslint, ruff, prettier, black)
  • ✅ TypeScript strict mode compliance
  • ✅ Includes proper error handling
  • ✅ Has basic test coverage
  • ✅ Follows naming conventions from reference/conventions.md
  • ✅ Includes JSDoc/docstrings for complex functions
  • ✅ No hardcoded secrets or credentials

Quality Checks:

# TypeScript/JavaScript
pnpm run lint      # ESLint
pnpm run format    # Prettier
pnpm run type-check  # TypeScript compiler

# Python
ruff check .       # Linting
black --check .    # Formatting
pytest tests/      # Tests

Failure Modes:

  • ❌ Linter errors → Fix before completing
  • ❌ Type errors → Add proper types
  • ❌ No tests → Generate basic test file
  • ❌ TODOs without issue links → Remove or link
  • ❌ Hardcoded values (should be config) → Parameterize

Recovery:

  • If linter fails: Run formatter and fix issues
  • If types incomplete: Infer from usage or use strict types
  • If patterns unclear: Read similar existing code
  • If tests fail: Debug and fix before delivery

Related Skills

Composes With:

  • schema.linter - Validate generated config files
  • carbon.data.qa - Use to understand data structure before generating data code

Not a Replacement For:

  • Human code review (per AGENTS.md)
  • Architecture decisions
  • Deployment operations

3D Universe Patterns (NEW)

Architecture Overview

3D+2D Hybrid Approach:

  • 3D Canvas: Interactive data visualization with Three.js/React Three Fiber
  • 2D Overlays: Modals, panels, and forms for transparency and data entry
  • Design Tokens: CSS custom properties for consistent styling
  • State Management: Single Zustand store (simplified from dual-store pattern)

Component Organization

Component Types:

  • System Primitives: Button, Input, Dialog, Transition (Tier 1)
  • 3D Visualizations: DataUniverse, CentralSphere, OrbitingActivity (Tier 3)
  • 2D Overlays: CitationPanel, MethodologyModal, ActivityManagement (Tier 4)
  • Domain Pages: CalculatorPage, InsightsPage, ExplorePage (Tier 4)

Example 4: 3D Visualization Component

User: "Create a component for visualizing activity emissions as orbiting spheres"

Output: apps/carbon-acx-web/src/components/viz/ActivitySphere.tsx

/**
 * ActivitySphere - 3D Visualization Component
 *
 * Renders an individual activity as an orbiting sphere in the DataUniverse.
 * Size based on emissions (logarithmic scale), color based on intensity.
 */

import * as React from 'react';
import { useFrame } from '@react-three/fiber';
import { Html } from '@react-three/drei';
import * as THREE from 'three';

interface ActivitySphereProps {
  activity: {
    id: string;
    name: string;
    annualEmissions: number;  // kg CO₂
    category?: string;
  };
  orbitRadius: number;
  orbitSpeed: number;
  phaseOffset: number;
  onClick?: () => void;
}

export function ActivitySphere({
  activity,
  orbitRadius,
  orbitSpeed,
  phaseOffset,
  onClick,
}: ActivitySphereProps) {
  const meshRef = React.useRef<THREE.Mesh>(null);
  const groupRef = React.useRef<THREE.Group>(null);
  const [hovered, setHovered] = React.useState(false);

  // Calculate size (logarithmic scale)
  const size = 0.5 + Math.log10(Math.max(activity.annualEmissions, 1)) * 0.3;

  // Calculate color based on intensity
  const color = React.useMemo(() => {
    const tonnes = activity.annualEmissions / 1000;
    if (tonnes < 1) return '#10b981';  // green (low)
    if (tonnes < 5) return '#f59e0b';  // amber (moderate)
    return '#ef4444';                  // red (high)
  }, [activity.annualEmissions]);

  // Orbital motion animation
  useFrame(() => {
    if (!groupRef.current) return;

    const time = Date.now() * orbitSpeed;
    const angle = time + phaseOffset;
    const x = Math.cos(angle) * orbitRadius;
    const z = Math.sin(angle) * orbitRadius;
    const y = Math.sin(time * 2) * 2;  // vertical wobble

    groupRef.current.position.set(x, y, z);
  });

  return (
    <group ref={groupRef}>
      <mesh
        ref={meshRef}
        onClick={onClick}
        onPointerOver={() => setHovered(true)}
        onPointerOut={() => setHovered(false)}
      >
        <sphereGeometry args={[size, 16, 16]} />
        <meshStandardMaterial
          color={color}
          emissive={color}
          emissiveIntensity={hovered ? 0.8 : 0.2}
          metalness={0.6}
          roughness={0.3}
        />
      </mesh>

      {/* Hover label */}
      {hovered && (
        <Html position={[0, size + 0.8, 0]} center>
          <div
            className="px-3 py-2 rounded-lg pointer-events-none"
            style={{
              backgroundColor: 'rgba(10, 14, 39, 0.95)',
              border: `1px solid ${color}`,
              color: 'white',
              fontSize: 'var(--font-size-xs)',
              whiteSpace: 'nowrap',
            }}
          >
            <div style={{ fontWeight: 600, marginBottom: '4px' }}>
              {activity.name}
            </div>
            <div style={{ opacity: 0.8 }}>
              {(activity.annualEmissions / 1000).toFixed(2)}t CO₂/yr
            </div>
          </div>
        </Html>
      )}
    </group>
  );
}

Example 5: 2D Overlay Component

User: "Create a modal for displaying emission factor citations"

Output: apps/carbon-acx-web/src/components/domain/CitationModal.tsx

/**
 * CitationModal - 2D Overlay Component
 *
 * Displays emission factor source information with transparency.
 * Uses Radix Dialog for accessibility.
 */

import * as React from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import { X, ExternalLink } from 'lucide-react';
import { Button } from '../system/Button';

interface Citation {
  activityId: string;
  activityName: string;
  emissionFactor: number;
  unit: string;
  source: string;
  sourceUrl?: string;
  methodology?: string;
  lastUpdated?: string;
}

interface CitationModalProps {
  citation: Citation | null;
  open: boolean;
  onClose: () => void;
}

export function CitationModal({ citation, open, onClose }: CitationModalProps) {
  if (!citation) return null;

  return (
    <Dialog.Root open={open} onOpenChange={onClose}>
      <Dialog.Portal>
        <Dialog.Overlay
          className="fixed inset-0 z-50"
          style={{
            backgroundColor: 'rgba(0, 0, 0, 0.6)',
            backdropFilter: 'blur(4px)',
          }}
        />
        <Dialog.Content
          className="fixed top-[50%] left-[50%] z-50 max-h-[85vh] w-[90vw] max-w-[600px] translate-x-[-50%] translate-y-[-50%] rounded-[var(--radius-xl)] p-[var(--space-6)] shadow-xl overflow-y-auto"
          style={{
            backgroundColor: 'var(--surface-elevated)',
            border: '1px solid var(--border-default)',
          }}
        >
          {/* Header */}
          <div className="flex items-start justify-between mb-[var(--space-6)]">
            <Dialog.Title
              className="font-bold"
              style={{
                fontSize: 'var(--font-size-2xl)',
                color: 'var(--text-primary)',
              }}
            >
              Emission Factor Source
            </Dialog.Title>
            <Dialog.Close asChild>
              <button
                className="p-[var(--space-2)] rounded-[var(--radius-md)]"
                style={{ color: 'var(--text-tertiary)' }}
              >
                <X className="w-5 h-5" />
              </button>
            </Dialog.Close>
          </div>

          {/* Activity Info */}
          <div className="space-y-[var(--space-4)]">
            <div>
              <div
                className="font-semibold mb-[var(--space-1)]"
                style={{
                  fontSize: 'var(--font-size-sm)',
                  color: 'var(--text-tertiary)',
                }}
              >
                Activity
              </div>
              <div style={{ color: 'var(--text-primary)' }}>
                {citation.activityName}
              </div>
            </div>

            <div>
              <div
                className="font-semibold mb-[var(--space-1)]"
                style={{
                  fontSize: 'var(--font-size-sm)',
                  color: 'var(--text-tertiary)',
                }}
              >
                Emission Factor
              </div>
              <div
                className="font-mono"
                style={{
                  fontSize: 'var(--font-size-lg)',
                  color: 'var(--text-primary)',
                }}
              >
                {citation.emissionFactor} kg CO₂e/{citation.unit}
              </div>
            </div>

            <div>
              <div
                className="font-semibold mb-[var(--space-1)]"
                style={{
                  fontSize: 'var(--font-size-sm)',
                  color: 'var(--text-tertiary)',
                }}
              >
                Source
              </div>
              <div style={{ color: 'var(--text-primary)' }}>
                {citation.source}
              </div>
              {citation.sourceUrl && (
                <a
                  href={citation.sourceUrl}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="inline-flex items-center gap-[var(--space-1)] mt-[var(--space-2)] text-[var(--font-size-sm)]"
                  style={{ color: 'var(--interactive-primary)' }}
                >
                  View source <ExternalLink className="w-4 h-4" />
                </a>
              )}
            </div>

            {citation.methodology && (
              <div>
                <div
                  className="font-semibold mb-[var(--space-1)]"
                  style={{
                    fontSize: 'var(--font-size-sm)',
                    color: 'var(--text-tertiary)',
                  }}
                >
                  Methodology
                </div>
                <div
                  style={{
                    fontSize: 'var(--font-size-sm)',
                    color: 'var(--text-secondary)',
                  }}
                >
                  {citation.methodology}
                </div>
              </div>
            )}
          </div>

          {/* Actions */}
          <div className="flex justify-end mt-[var(--space-6)] pt-[var(--space-4)] border-t" style={{ borderColor: 'var(--border-subtle)' }}>
            <Button variant="primary" onClick={onClose}>
              Close
            </Button>
          </div>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

Design Token Usage

Always use design tokens for styling:

// ✅ Correct - Using design tokens
<div className="text-[var(--font-size-lg)] text-[var(--text-primary)]">

// ❌ Wrong - Hardcoded values
<div className="text-lg text-gray-900">

// ✅ Correct - Using token-based spacing
<div className="space-y-[var(--space-4)]">

// ❌ Wrong - Hardcoded spacing
<div className="space-y-4">

Available Design Tokens:

  • Typography: --font-size-xs through --font-size-5xl (Major Third scale 1.250)
  • Colors: --carbon-low, --carbon-moderate, --carbon-high, --carbon-neutral
  • Story colors: --color-goal, --color-baseline, --color-improvement, --color-insight
  • Spacing: --space-1 through --space-16 (4px base)
  • Shadows: --shadow-sm, --shadow-md, --shadow-lg
  • Radii: --radius-sm, --radius-md, --radius-lg, --radius-xl
  • Motion: --motion-story-duration (600ms), --motion-story-ease

State Management Pattern

Zustand for all app state:

import { useAppStore } from '../../hooks/useAppStore';

// Get state
const activities = useAppStore((state) => state.activities);
const totalEmissions = useAppStore((state) => state.getTotalEmissions());

// Update state
const { addActivity, removeActivity } = useAppStore();

// Store example structure:
interface AppState {
  // Profile data
  profile: {
    activities: Activity[];
    goals: Goal[];
  };

  // UI state
  selectedActivityId: string | null;

  // Actions
  addActivity: (activity: Activity) => void;
  removeActivity: (id: string) => void;
  getTotalEmissions: () => number;
}

Maintenance

Owner: ACX Team Review Cycle: Weekly (high-activity skill) Last Updated: 2025-10-27 Version: 2.1.0 (3D Universe architecture update)

Maintenance Notes:

  • Update reference/conventions.md when coding standards change
  • Review generated code quality monthly
  • Keep examples synchronized with actual codebase patterns
  • Update when tech stack versions change (React, TypeScript, Python, Three.js)
  • NEW: Keep 3D Universe patterns current (Three.js, React Three Fiber, Drei helpers)
  • NEW: Update component examples as hybrid 2D+3D architecture evolves