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:
- Parses CSV file on startup
- Loads crop color configuration from JSON
- Groups related events (greenhouse sowing + transplanting)
- Serves four endpoints:
GET /- HTML pageGET /api/events- Raw CSV data as JSONGET /api/timeline- Processed timeline with phasesGET /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:
- Fetches data from
/api/timeline,/api/events, and/api/colors - Renders timeline with color-coded crop boxes
- 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:
- Filters out "Mulch" and "Harvest" activities
- Identifies greenhouse sowings
- Finds matching transplants (same crop + bed, later date)
- Groups them as phases of a single timeline entry
- 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:
- Edit
crop-colors.json - Restart server (no rebuild needed)
- 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:
toggleCropDetails()finds all boxes with matchingdata-crop-group- Collapses all other expanded crops
- Expands all phases of the clicked crop
- 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
- Update CSV: Add row(s) to
garden-plan-2026.csv - Add color: Add entry to
crop-colors.json(optional - defaults to blue if not specified) - Restart server:
./target/release/garden-planner - Refresh browser
Changing Crop Colors
- Edit colors: Modify
crop-colors.json - Restart server:
./target/release/garden-planner(no rebuild needed) - 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
- Edit
static/index.html - Refresh browser (no rebuild)
- Check browser console for JS errors
Making Backend Changes
- Edit
src/main.rs - Run
cargo build --release - Restart server:
./target/release/garden-planner - Refresh browser
Updating Garden Plan
- Edit
garden-plan-2026.csvand/orcrop-colors.json - Restart server (no rebuild needed)
- Refresh browser
Verifying Climate Alignment
- Review
climate-data-western-zealand.mdfor frost dates and temperature trends - Ensure greenhouse starts are after mid-March
- Ensure transplants are after last frost (~April 20)
- Ensure fall crops are planted before first frost (~October 10)
- Update climate data document annually in January
Debugging
- Backend: Add
println!()ordbg!()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:
- Add edit mode: Click to edit CSV data in-browser, save changes
- Export formats: PDF, calendar (iCal), print-friendly view
- Weather integration: Show frost dates, temperature ranges
- Mobile responsive: Better touch interactions
- Search/filter: Find specific crops, filter by bed
- Notes system: Add per-crop growing notes from previous years
- Harvest tracking: Mark actual harvest dates vs. planned
- Multi-year view: Compare plans across seasons
Dependencies
Rust (Cargo.toml)
axum- Web frameworktokio- Async runtimetower&tower-http- Middlewareserde&serde_json- Serializationcsv- CSV parsingchrono- Date handlingtracing- Logging
All dependencies are stable, well-maintained crates.
Questions to Ask When Modifying
Before making changes, consider:
-
Is this a UI or data change?
- UI → Edit
index.html - Data structure → Edit
main.rs
- UI → Edit
-
Does it affect grouping logic?
- If yes, test with crops that have multiple phases
-
Does it change the timeline calculation?
- Test with crops at beginning, middle, and end of season
-
Will it break existing CSS?
- Check both expanded and collapsed states
- Test with beds that have many crops
-
Is the CSV format changing?
- Update both Rust structs and documentation
Getting Help
If you're stuck:
- Check the browser console for JS errors
- Check terminal output for Rust errors
- Verify CSV format matches expected structure
- Test API endpoints directly:
curl http://localhost:3000/api/timeline - 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.