CLAUDE.md.tmpl
148 lines1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# <<.Name>>
Built with the Congo framework (Go + HTMX + DaisyUI).
## Commands
```bash
congo dev # development server (ENV=development, in-memory DB)
congo build # production binary
congo launch # deploy to remote server
congo new <name> # add a new app to the project
congo connect # SSH into deployed server
congo destroy # tear down infrastructure
congo claude # AI-assisted development
congo status # check deployment health
congo logs # stream service logs
```
## Architecture
MVC pattern with HTMX-first server rendering.
- `<<.Dir>>/controllers/` — route handlers + template methods
- `<<.Dir>>/models/` — database models with auto-migration ORM
- `<<.Dir>>/views/` — Go HTML templates with DaisyUI
<<- if .WithFrontend>>
- `<<.Dir>>/components/` — React island components
<<- end>>
- `internal/` — vendored Congo framework (modifiable)
## Controller Pattern
```go
// Factory function returns (name, controller).
func Todos() (string, *TodosController) {
return "todos", &TodosController{}
}
type TodosController struct {
application.BaseController
}
func (c *TodosController) Setup(app *application.App) {
c.BaseController.Setup(app)
http.Handle("GET /todos", app.Serve("todos.html", nil))
http.Handle("POST /todos", app.Method(c, "Create", nil))
http.Handle("POST /todos/{id}/delete", app.Method(c, "Delete", nil))
}
// VALUE receiver creates copy for request isolation.
func (c TodosController) Handle(r *http.Request) application.Controller {
c.Request = r
return &c
}
// Template methods — accessible as {{todos.All}}.
func (c *TodosController) All() []*models.Todo {
todos, _ := models.Todos.Search("ORDER BY CreatedAt DESC")
return todos
}
// POST handler — create.
func (c *TodosController) Create(w http.ResponseWriter, r *http.Request) {
todo := &models.Todo{Title: r.FormValue("title")}
if _, err := models.Todos.Insert(todo); err != nil {
c.RenderError(w, r, err)
return
}
c.Refresh(w, r) // reload current page
}
// POST handler — delete.
func (c *TodosController) Delete(w http.ResponseWriter, r *http.Request) {
if err := models.Todos.DeleteByID(r.PathValue("id")); err != nil {
c.RenderError(w, r, err)
return
}
c.Redirect(w, r, "/todos") // navigate to different URL
}
```
Register in `<<.Dir>>/main.go`:
```go
application.WithController(controllers.Todos()),
```
## Model Pattern
```go
// <<.Dir>>/models/todo.go
type Todo struct {
database.Model // ID (string UUID), CreatedAt, UpdatedAt
Title string
Done bool
}
```
Register collection in `<<.Dir>>/models/db.go`:
```go
var Todos = database.Manage(DB, new(Todo))
```
Tables auto-create on startup. New fields auto-migrate. Collection methods: `Get(id)`, `First(where, args...)`, `Search(where, args...)`, `All()`, `Insert(entity)`, `Update(entity)`, `Delete(entity)`, `DeleteByID(id)`, `Count(where, args...)`.
## View + HTMX Pattern
```html
{{template "main.html" .}}
{{define "title"}}Todos{{end}}
{{define "content"}}
<div class="container mx-auto p-8">
{{range $todo := todos.All}}
<div class="card bg-base-100 mb-2 p-4 flex justify-between items-center">
<span>{{$todo.Title}}</span>
<button hx-post="/todos/{{$todo.ID}}/delete" hx-target="body" class="btn btn-sm btn-error">Delete</button>
</div>
{{end}}
<form hx-post="/todos" hx-target="body" class="flex gap-2 mt-4">
<input name="title" class="input input-bordered flex-1" placeholder="New todo..." required />
<button class="btn btn-primary">Add</button>
</form>
</div>
{{end}}
```
## Key Conventions
- IDs are ALWAYS strings (UUIDs), never integers
- SQL columns use PascalCase: `WHERE UserID = ?`
- Templates by filename only: `{{template "nav.html" .}}` not `"partials/nav.html"`
- `c.Render(w, r, "template.html", data)` — render a partial in POST handlers
- `c.RenderError(w, r, err)` — returns 200 with error HTML for HTMX
- `c.Redirect(w, r, "/path")` — sends HX-Location header for HTMX, 303 for regular requests
- `c.Refresh(w, r)` — sends HX-Refresh header for HTMX, 303 redirect-to-self for regular requests
- `r.PathValue("id")` in handlers, `c.PathValue("id")` in template methods
- HTMX + SameSite=Lax cookies = CSRF protection (no tokens needed)
- No custom ServeMux — all routes on `http.DefaultServeMux`
- `cmp.Or()` for env var defaults
## Application Options
```go
application.WithValue("key", value) // template function returning value
application.WithMiddleware(mw) // add middleware
application.WithHealthPath("/healthz") // custom health endpoint (default: /health)
application.WithController(controllers.X()) // register controller
```