5.2 KiB
AGENTS.md
Guidance for agentic coding agents working in this repository.
Project Overview
Essence is a Quebec gas price heatmap — a minimal, self-contained web application with two source files:
main.go— the entire Go backend (HTTP server, SQLite persistence, upstream polling)static/index.html— the entire frontend (Leaflet map, Chart.js charts, vanilla JS)
Keep this architecture. Do not split main.go into multiple files or introduce a frontend build step unless explicitly asked.
Environment Setup
The dev environment is managed with Nix flakes. Enter it with either:
nix develop # explicit
direnv allow # automatic via .envrc (uses `use flake`)
The shell provides: go, gopls, gotools (includes gofmt, goimports).
Build & Run
# Build binary
go build -o essence .
# Run directly (no build step needed for development)
go run .
# With explicit env vars (both have defaults)
PORT=8080 ESSENCE_DB=./essence.db go run .
# Build via Nix
nix build
./result/bin/essence
Environment variables:
PORT— HTTP listen port (default:8080)ESSENCE_DB— SQLite database file path (default:./essence.db)
Lint & Format
gofmt -w . # format all Go files (authoritative formatter — no config)
goimports -w . # format + organize imports (superset of gofmt)
go vet ./... # static analysis
There are no linter config files. Follow standard Go formatting conventions enforced by gofmt.
Tests
# Run all tests
go test ./...
# Run a single test by name
go test -run TestFunctionName .
# Run with verbose output
go test -v -run TestFunctionName .
No tests exist yet. When adding tests, use Go's built-in testing package — no third-party test libraries. Place test files alongside the code they test (e.g., main_test.go).
Go Code Style
Imports
Group imports into two blocks separated by a blank line: stdlib first, then third-party. This is enforced by goimports.
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
_ "modernc.org/sqlite"
)
Naming
- Exported types, functions, fields:
PascalCase - Unexported variables, functions:
camelCase - Acronyms follow Go convention:
geojsonURL,initDB,handleStations
Error Handling
Always check errors immediately. Wrap errors with context using %w. Use log.Fatalf for unrecoverable startup errors; log.Printf + early return for runtime errors.
// Startup — fatal is appropriate
db, err = initDB(dbPath)
if err != nil {
log.Fatalf("db init: %v", err)
}
// Wrapping with context
return nil, fmt.Errorf("create table: %w", err)
return nil, fmt.Errorf("fetching data: %w", err)
// Runtime — log and return, do not crash
if err != nil {
log.Printf("fetch error: %v", err)
return
}
Never silently discard errors.
Types & Structs
Use struct-based JSON serialization with json tags. Use anonymous inline structs for one-off local parsing; define named types for anything reused or returned from functions.
type Station struct {
Name string `json:"name"`
Brand string `json:"brand"`
Lat float64 `json:"lat"`
Regular float64 `json:"regular"`
}
// Inline anonymous struct for local, single-use parsing
var features []struct {
Geometry struct {
Coordinates [2]float64 `json:"coordinates"`
} `json:"geometry"`
}
Constants
Group related constants in a single const block.
const (
geojsonURL = "https://example.com/stations.geojson.gz"
defaultPort = "8080"
pollInterval = 5 * time.Minute
)
Concurrency
Use sync.RWMutex to protect shared state. Acquire the narrowest lock needed.
var (
cacheMu sync.RWMutex
cachedResp *StationsResponse
)
cacheMu.Lock()
cachedResp = resp
cacheMu.Unlock()
cacheMu.RLock()
resp := cachedResp
cacheMu.RUnlock()
Comments
Write doc-style comments on all exported types and non-trivial unexported functions. Keep comments concise and sentence-cased.
// Station holds the parsed data for a single fuel station.
type Station struct { ... }
// poller fetches upstream data immediately, then repeats every pollInterval.
func poller() { ... }
Frontend Code Style (static/index.html)
- No framework, no build step. Plain HTML, CSS, and JavaScript in a single file.
- Inline
<style>and<script>blocks — no external local JS/CSS files. - CDN dependencies only: Leaflet, leaflet.markercluster, Chart.js,
@knadh/oatCSS. camelCasefor all JS variables and function names.fetch()with.then()promise chains — notasync/await.- CSS custom properties (
--primary,--border, etc.) from theoatdesign token system. - French language — all user-facing text must be in French (
lang="fr"on<html>).
Architecture Notes
- Static assets are embedded into the binary at compile time via
//go:embed static/*. No file serving from disk at runtime. - The SQLite driver is
modernc.org/sqlite— a pure-Go, CGo-free implementation. Do not introduce CGo or replace this driver. - Deployment target is a NixOS systemd service defined in
nixos-module.nix. - The module
github.com/polen/essencetargets Go 1.25+.