Files
garden-plan/CLAUDE.md
2026-01-27 10:05:53 +01:00

11 KiB

CLAUDE.md - Garden Planner Project

This document explains the architecture, patterns, and conventions used in this garden planning application. It's designed to help AI assistants (and humans) understand and modify the codebase effectively.

Project Overview

A self-hosted web application for visualizing garden planting schedules across three beds in Western Zealand, Denmark. Built with Rust backend (Axum) and vanilla JavaScript frontend.

Key Features:

  • Interactive timeline view showing planting schedules across 12 months
  • Multi-phase crop visualization (greenhouse → transplant)
  • Expandable crop details on click
  • Table view for detailed event listing
  • CSV-driven data (easy to update without code changes)

Architecture

Backend (Rust)

  • Framework: Axum web server
  • Port: 127.0.0.1:3000
  • Data Sources: garden-plan-2026.csv (events), crop-colors.json (colors)
  • Main file: src/main.rs

The backend:

  1. Parses CSV file on startup
  2. Loads crop color configuration from JSON
  3. Groups related events (greenhouse sowing + transplanting)
  4. Serves four endpoints:
    • GET / - HTML page
    • GET /api/events - Raw CSV data as JSON
    • GET /api/timeline - Processed timeline with phases
    • GET /api/colors - Crop color mappings

Frontend (JavaScript)

  • File: static/index.html
  • Style: Vanilla HTML/CSS/JavaScript (no frameworks)
  • Dynamic loading: HTML is read from disk on each request (no rebuild needed for UI changes)

The frontend:

  1. Fetches data from /api/timeline, /api/events, and /api/colors
  2. Renders timeline with color-coded crop boxes
  3. Handles click interactions for expanding crop details

File Structure

garden/
├── Cargo.toml                        # Rust dependencies
├── src/
│   └── main.rs                      # Web server, CSV parsing, event grouping
├── static/
│   └── index.html                   # Complete UI (HTML + CSS + JS)
├── garden-plan-2026.csv             # Garden data (user editable)
├── crop-colors.json                 # Crop color mappings (user editable)
├── climate-data-western-zealand.md  # Climate reference data
├── bed-layout.md                    # Documentation of bed contents
├── README.md                        # User-facing documentation
└── CLAUDE.md                        # This file

Key Data Structures

Rust Backend

// Input: Raw CSV row
struct GardenEvent {
    date: String,           // "2025-03-24"
    activity: String,       // "Greenhouse Sow", "Transplant", "Direct Sow"
    crop: String,          // "Tomato"
    variety: String,       // "Sungold + Matina"
    bed: String,           // "South", "Middle", "North"
    harvest_period: String, // "July-October"
    notes: String,         // Growing instructions
}

// Output: Grouped timeline entry
struct TimelineEvent {
    crop: String,
    variety: String,
    bed: String,
    phases: Vec<TimelinePhase>,  // Multiple phases per crop
    harvest_period: String,
}

struct TimelinePhase {
    activity: String,       // "Greenhouse Sow"
    start_date: String,     // "2025-03-24"
    end_date: Option<String>, // "2025-05-05" or harvest end
    notes: String,
}

Event Grouping Logic

The build_timeline() function groups related events:

  1. Filters out "Mulch" and "Harvest" activities
  2. Identifies greenhouse sowings
  3. Finds matching transplants (same crop + bed, later date)
  4. Groups them as phases of a single timeline entry
  5. Calculates end dates (next phase start, or harvest end)

Example: Tomato has two CSV rows (Greenhouse Sow on 2026-03-23, Transplant on 2026-05-04) → grouped into one TimelineEvent with two phases.

Frontend Patterns

Color System

Crop colors are defined in crop-colors.json and loaded dynamically via the /api/colors endpoint. The getCropColor() function looks up colors from the loaded configuration:

{
  "Tomato": "#e53e3e",
  "Basil": "#48bb78",
  "Bush Beans": "#9f7aea",
  "Cornflower": "#6495ed",
  ...
}

To add/change crop colors:

  1. Edit crop-colors.json
  2. Restart server (no rebuild needed)
  3. Refresh browser

The function returns a default blue (#4299e1) for any crop not in the configuration.

Multi-phase crops use adjustColor() to lighten the first phase by 20% (factor 0.8).

Box Expansion System

Each crop box has:

  • Unique ID: crop-${bed}-${idx}-${phaseIdx} (e.g., "crop-South-1-0")
  • Group attribute: data-crop-group="${bed}-${idx}" (e.g., "South-1")

When clicked:

  1. toggleCropDetails() finds all boxes with matching data-crop-group
  2. Collapses all other expanded crops
  3. Expands all phases of the clicked crop
  4. Each phase shows its own details independently

Timeline Positioning

dateToPosition() converts dates to percentage positions:

  • Timeline spans: 2026-03-01 to 2027-03-01 (12 months)
  • Each date maps to 0-100% of timeline width
  • Example: 2026-03-23 = ~8%, 2026-05-04 = ~22%

Common Modifications

Adding a New Crop

  1. Update CSV: Add row(s) to garden-plan-2026.csv
  2. Add color: Add entry to crop-colors.json (optional - defaults to blue if not specified)
  3. Restart server: ./target/release/garden-planner
  4. Refresh browser

Changing Crop Colors

  1. Edit colors: Modify crop-colors.json
  2. Restart server: ./target/release/garden-planner (no rebuild needed)
  3. Refresh browser

Changing Timeline Display Logic

Edit renderTimeline() function in static/index.html:

  • Modify how boxes are rendered
  • Change color calculation
  • Adjust positioning logic
  • No Rust rebuild needed (HTML loaded dynamically)

Modifying Event Grouping

Edit build_timeline() in src/main.rs:

  • Change how phases are matched
  • Adjust end date calculation
  • Modify filtering logic
  • Requires: cargo build --release

Adding New API Endpoints

// In main.rs
async fn new_endpoint(State(state): State<Arc<AppState>>) -> Json<YourType> {
    // Your logic
    Json(data)
}

// Add to router
let app = Router::new()
    .route("/api/new", get(new_endpoint))
    // ... other routes

Development Workflow

Making UI Changes

  1. Edit static/index.html
  2. Refresh browser (no rebuild)
  3. Check browser console for JS errors

Making Backend Changes

  1. Edit src/main.rs
  2. Run cargo build --release
  3. Restart server: ./target/release/garden-planner
  4. Refresh browser

Updating Garden Plan

  1. Edit garden-plan-2026.csv and/or crop-colors.json
  2. Restart server (no rebuild needed)
  3. Refresh browser

Verifying Climate Alignment

  1. Review climate-data-western-zealand.md for frost dates and temperature trends
  2. Ensure greenhouse starts are after mid-March
  3. Ensure transplants are after last frost (~April 20)
  4. Ensure fall crops are planted before first frost (~October 10)
  5. Update climate data document annually in January

Debugging

  • Backend: Add println!() or dbg!() in Rust code, check terminal
  • Frontend: Add console.log() in JS, check browser console (F12)
  • Data flow: Check Network tab in browser DevTools for API responses

Design Decisions

Why Rust?

  • Fast CSV parsing
  • Type safety for data structures
  • Low resource usage for self-hosting
  • Good learning opportunity

Why Vanilla JS?

  • No build step complexity
  • Easy to understand and modify
  • Fast iteration (no framework overhead)
  • Everything in one HTML file

Why CSV for Garden Data?

  • Easy to edit in spreadsheet apps
  • Human-readable
  • Version control friendly
  • No database needed

Why JSON for Colors?

  • Simpler than embedding in code
  • Easy to edit without touching HTML/JS
  • Centralized configuration
  • No rebuild needed to change colors
  • Can be updated independently of garden plan

Why Group Greenhouse + Transplant?

  • Shows complete crop lifecycle
  • Cleaner visual representation
  • User can see planning timeline at a glance
  • Reduces clutter (one row instead of two)

Gotchas and Edge Cases

CSV Header Names

Headers must match exactly (case-sensitive):

Date,Week,Activity,Crop,Variety,Bed,Quantity,Harvest Period,Notes

Rust structs use #[serde(rename = "Date")] to map to these headers.

CSV Field Quoting

Any field containing commas must be wrapped in double quotes:

2025-05-05,Week 18,Direct Plant,Tansy,Guldknap,Middle,6 plants,June-October,"Bed edges - native Danish wildflower, pest repellent"

Without quotes, the CSV parser will split the field and cause an "UnequalLengths" error.

Color Configuration

The crop-colors.json file must be valid JSON with crop names as keys and hex colors as values:

{
  "Crop Name": "#hexcolor"
}

Missing crops will default to #4299e1 (blue).

ID Prefix Matching

Never use [id^="crop-South-1"] - it matches both "crop-South-1-0" and "crop-South-10-0". Always use exact data-crop-group attribute matching.

Date Parsing

month_to_date() function is simple and may not handle all formats. Current supported formats:

  • "July-October" → extracts "October"
  • "May 2027" → extracts "May" + "2027"
  • Defaults to Dec 31, 2026 if unparseable

Multi-Phase Detection

Grouping only works for greenhouse → transplant pattern. If you add a third phase type, update build_timeline() logic.

Future Enhancement Ideas

If you want to extend this project:

  1. Add edit mode: Click to edit CSV data in-browser, save changes
  2. Export formats: PDF, calendar (iCal), print-friendly view
  3. Weather integration: Show frost dates, temperature ranges
  4. Mobile responsive: Better touch interactions
  5. Search/filter: Find specific crops, filter by bed
  6. Notes system: Add per-crop growing notes from previous years
  7. Harvest tracking: Mark actual harvest dates vs. planned
  8. Multi-year view: Compare plans across seasons

Dependencies

Rust (Cargo.toml)

  • axum - Web framework
  • tokio - Async runtime
  • tower & tower-http - Middleware
  • serde & serde_json - Serialization
  • csv - CSV parsing
  • chrono - Date handling
  • tracing - Logging

All dependencies are stable, well-maintained crates.

Questions to Ask When Modifying

Before making changes, consider:

  1. Is this a UI or data change?

    • UI → Edit index.html
    • Data structure → Edit main.rs
  2. Does it affect grouping logic?

    • If yes, test with crops that have multiple phases
  3. Does it change the timeline calculation?

    • Test with crops at beginning, middle, and end of season
  4. Will it break existing CSS?

    • Check both expanded and collapsed states
    • Test with beds that have many crops
  5. Is the CSV format changing?

    • Update both Rust structs and documentation

Getting Help

If you're stuck:

  1. Check the browser console for JS errors
  2. Check terminal output for Rust errors
  3. Verify CSV format matches expected structure
  4. Test API endpoints directly: curl http://localhost:3000/api/timeline
  5. Add debug logging to isolate the issue

Summary

This is a straightforward project with clear separation of concerns:

  • Rust: Data processing and serving
  • JavaScript: Rendering and interaction
  • CSV: Single source of truth

The code prioritizes simplicity and maintainability over clever abstractions. When in doubt, add explicit code rather than complex logic.