The code and data behind xeiaso.net
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(skills/templ-http): refactor into progressive disclosure docs

Keep the primary skill concise and move handlers, middleware, and request/response details into scoped resource docs with templ.guide-backed guidance.

Assisted-by: openai/gpt-5.3-codex via OpenCode
Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso e1df1002 ab319de9

+187 -377
+35 -377
.claude/skills/templ-http/SKILL.md
··· 5 5 6 6 # Templ HTTP Integration 7 7 8 - ## Overview 9 - 10 - Connect templ components to Go's `net/http` server. Render components in HTTP handlers and serve dynamic HTML pages. 8 + Use progressive disclosure: begin with the handler pattern, then add routing and middleware only as needed. 11 9 12 - ## When to Use This Skill 13 - 14 - Use when: 15 - 16 - - Setting up HTTP server with templ 17 - - Creating route handlers 18 - - User mentions "serve templ", "HTTP server", "web server" 19 - - Connecting components to routes 20 - - Rendering templ in handlers 10 + ## Level 1: Minimal Integration 21 11 22 - ## Basic Integration 12 + Use this skill when serving templ from `net/http`. 23 13 24 - ### Simple Handler 14 + 1. Parse request input. 15 + 2. Build view model/data. 16 + 3. Render templ component with `r.Context()`. 17 + 4. Handle render errors and status codes. 25 18 26 19 ```go 27 - package main 28 - 29 - import ( 30 - "net/http" 31 - "myapp/components" 32 - ) 33 - 34 20 func homeHandler(w http.ResponseWriter, r *http.Request) { 35 - components.HomePage().Render(r.Context(), w) 36 - } 37 - 38 - func main() { 39 - http.HandleFunc("/", homeHandler) 40 - http.ListenAndServe(":8080", nil) 41 - } 42 - ``` 43 - 44 - ### Handler with Data 45 - 46 - ```go 47 - func userHandler(w http.ResponseWriter, r *http.Request) { 48 - user := getUserFromDB(r.URL.Query().Get("id")) 49 - 50 - components.UserProfile(user).Render(r.Context(), w) 51 - } 52 - ``` 53 - 54 - ## Rendering Patterns 55 - 56 - ### Pattern 1: Direct Render 57 - 58 - ```go 59 - func handler(w http.ResponseWriter, r *http.Request) { 60 - component := components.Page("Title") 61 - component.Render(r.Context(), w) 62 - } 63 - ``` 64 - 65 - ### Pattern 2: Error Handling 66 - 67 - ```go 68 - func handler(w http.ResponseWriter, r *http.Request) { 69 - err := components.Page("Title").Render(r.Context(), w) 70 - if err != nil { 71 - http.Error(w, "Render failed", http.StatusInternalServerError) 72 - log.Printf("Render error: %v", err) 21 + if err := components.HomePage().Render(r.Context(), w); err != nil { 22 + http.Error(w, "render failed", http.StatusInternalServerError) 23 + return 73 24 } 74 25 } 75 26 ``` 76 27 77 - ### Pattern 3: With Layout 28 + ## Level 2: Routing and Request Data 78 29 79 - ```go 80 - func pageHandler(w http.ResponseWriter, r *http.Request) { 81 - content := components.PageContent() 82 - 83 - components.Layout("Page Title", content).Render(r.Context(), w) 84 - } 85 - ``` 86 - 87 - ## Routing 88 - 89 - ### ServeMux 90 - 91 - ```go 92 - func main() { 93 - mux := http.NewServeMux() 94 - 95 - // Static pages 96 - mux.HandleFunc("/", homeHandler) 97 - mux.HandleFunc("/about", aboutHandler) 98 - 99 - // Dynamic routes 100 - mux.HandleFunc("/user/", userHandler) 101 - mux.HandleFunc("/post/", postHandler) 102 - 103 - // Static files 104 - fs := http.FileServer(http.Dir("static")) 105 - mux.Handle("/static/", http.StripPrefix("/static/", fs)) 30 + - **Routes:** start with `http.NewServeMux()` and explicit handlers. 31 + - **Methods:** branch on `r.Method` when endpoint serves multiple actions. 32 + - **Input sources:** query (`r.URL.Query()`), form (`r.ParseForm()` + `FormValue`), path segments. 33 + - **Status discipline:** set status before rendering non-200 responses. 34 + - **Post/Redirect/Get:** redirect after successful form POSTs. 106 35 107 - http.ListenAndServe(":8080", mux) 108 - } 109 - ``` 36 + ## Level 3: Production Patterns 110 37 111 - ### RESTful Routes 38 + - Add middleware for auth/logging/request IDs. 39 + - Return component-based error pages for consistent UX. 40 + - Serve static files with `http.FileServer` and a dedicated prefix. 41 + - Keep handler functions thin; move business logic into services. 112 42 113 43 ```go 114 44 func usersHandler(w http.ResponseWriter, r *http.Request) { 115 45 switch r.Method { 116 - case "GET": 117 - users := getAllUsers() 118 - components.UserList(users).Render(r.Context(), w) 119 - 120 - case "POST": 121 - // Handle create 122 - user := createUser(r) 123 - components.UserCard(user).Render(r.Context(), w) 124 - 46 + case http.MethodGet: 47 + users := listUsers(r.Context()) 48 + _ = components.UserList(users).Render(r.Context(), w) 125 49 default: 126 - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 127 - } 128 - } 129 - ``` 130 - 131 - ## Request Data 132 - 133 - ### Query Parameters 134 - 135 - ```go 136 - func searchHandler(w http.ResponseWriter, r *http.Request) { 137 - query := r.URL.Query().Get("q") 138 - page := r.URL.Query().Get("page") 139 - 140 - results := search(query, page) 141 - 142 - components.SearchResults(query, results).Render(r.Context(), w) 143 - } 144 - ``` 145 - 146 - ### Form Data 147 - 148 - ```go 149 - func loginHandler(w http.ResponseWriter, r *http.Request) { 150 - if r.Method == "GET" { 151 - components.LoginForm().Render(r.Context(), w) 152 - return 153 - } 154 - 155 - // POST 156 - r.ParseForm() 157 - email := r.FormValue("email") 158 - password := r.FormValue("password") 159 - 160 - if authenticate(email, password) { 161 - http.Redirect(w, r, "/dashboard", http.StatusSeeOther) 162 - } else { 163 - components.LoginForm("Invalid credentials").Render(r.Context(), w) 164 - } 165 - } 166 - ``` 167 - 168 - ### Path Parameters 169 - 170 - ```go 171 - // /user/123 172 - func userHandler(w http.ResponseWriter, r *http.Request) { 173 - // Extract ID from path 174 - path := strings.TrimPrefix(r.URL.Path, "/user/") 175 - userID := path 176 - 177 - user := getUserByID(userID) 178 - if user == nil { 179 - http.NotFound(w, r) 180 - return 181 - } 182 - 183 - components.UserProfile(user).Render(r.Context(), w) 184 - } 185 - ``` 186 - 187 - ## Middleware 188 - 189 - ### Logging Middleware 190 - 191 - ```go 192 - func loggingMiddleware(next http.Handler) http.Handler { 193 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 194 - log.Printf("%s %s", r.Method, r.URL.Path) 195 - next.ServeHTTP(w, r) 196 - }) 197 - } 198 - 199 - func main() { 200 - mux := http.NewServeMux() 201 - mux.HandleFunc("/", homeHandler) 202 - 203 - http.ListenAndServe(":8080", loggingMiddleware(mux)) 204 - } 205 - ``` 206 - 207 - ### Auth Middleware 208 - 209 - ```go 210 - func authMiddleware(next http.HandlerFunc) http.HandlerFunc { 211 - return func(w http.ResponseWriter, r *http.Request) { 212 - session := getSession(r) 213 - if session == nil { 214 - http.Redirect(w, r, "/login", http.StatusSeeOther) 215 - return 216 - } 217 - 218 - next(w, r) 219 - } 220 - } 221 - 222 - // Usage 223 - http.HandleFunc("/dashboard", authMiddleware(dashboardHandler)) 224 - ``` 225 - 226 - ## Error Handling 227 - 228 - ### Custom Error Pages 229 - 230 - ```go 231 - func errorHandler(w http.ResponseWriter, r *http.Request, status int, message string) { 232 - w.WriteHeader(status) 233 - components.ErrorPage(status, message).Render(r.Context(), w) 234 - } 235 - 236 - func userHandler(w http.ResponseWriter, r *http.Request) { 237 - user, err := getUserByID(r.URL.Query().Get("id")) 238 - if err != nil { 239 - errorHandler(w, r, 500, "Failed to load user") 240 - return 241 - } 242 - if user == nil { 243 - errorHandler(w, r, 404, "User not found") 244 - return 245 - } 246 - 247 - components.UserProfile(user).Render(r.Context(), w) 248 - } 249 - ``` 250 - 251 - ### Error Component 252 - 253 - ```templ 254 - // components/error.templ 255 - package components 256 - 257 - templ ErrorPage(code int, message string) { 258 - @Layout("Error") { 259 - <div class="error-page"> 260 - <h1>{ strconv.Itoa(code) }</h1> 261 - <p>{ message }</p> 262 - <a href="/">Go Home</a> 263 - </div> 264 - } 265 - } 266 - ``` 267 - 268 - ## Static Files 269 - 270 - ```go 271 - func main() { 272 - mux := http.NewServeMux() 273 - 274 - // Serve static files 275 - fs := http.FileServer(http.Dir("static")) 276 - mux.Handle("/static/", http.StripPrefix("/static/", fs)) 277 - 278 - // Routes 279 - mux.HandleFunc("/", homeHandler) 280 - 281 - http.ListenAndServe(":8080", mux) 282 - } 283 - ``` 284 - 285 - ## Context Usage 286 - 287 - ### Passing Data via Context 288 - 289 - ```go 290 - func contextMiddleware(next http.Handler) http.Handler { 291 - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 292 - ctx := context.WithValue(r.Context(), "userID", "123") 293 - next.ServeHTTP(w, r.WithContext(ctx)) 294 - }) 295 - } 296 - 297 - func handler(w http.ResponseWriter, r *http.Request) { 298 - userID := r.Context().Value("userID").(string) 299 - 300 - components.Page(userID).Render(r.Context(), w) 301 - } 302 - ``` 303 - 304 - ## Full Example 305 - 306 - ```go 307 - package main 308 - 309 - import ( 310 - "log" 311 - "net/http" 312 - "myapp/components" 313 - ) 314 - 315 - func main() { 316 - mux := http.NewServeMux() 317 - 318 - // Static files 319 - fs := http.FileServer(http.Dir("static")) 320 - mux.Handle("/static/", http.StripPrefix("/static/", fs)) 321 - 322 - // Routes 323 - mux.HandleFunc("/", homeHandler) 324 - mux.HandleFunc("/about", aboutHandler) 325 - mux.HandleFunc("/contact", contactHandler) 326 - 327 - // Start server 328 - addr := ":8080" 329 - log.Printf("Server starting on %s", addr) 330 - if err := http.ListenAndServe(addr, mux); err != nil { 331 - log.Fatal(err) 332 - } 333 - } 334 - 335 - func homeHandler(w http.ResponseWriter, r *http.Request) { 336 - components.HomePage().Render(r.Context(), w) 337 - } 338 - 339 - func aboutHandler(w http.ResponseWriter, r *http.Request) { 340 - components.AboutPage().Render(r.Context(), w) 341 - } 342 - 343 - func contactHandler(w http.ResponseWriter, r *http.Request) { 344 - if r.Method == "GET" { 345 - components.ContactForm().Render(r.Context(), w) 346 - return 50 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 347 51 } 348 - 349 - // POST 350 - r.ParseForm() 351 - name := r.FormValue("name") 352 - email := r.FormValue("email") 353 - message := r.FormValue("message") 354 - 355 - // Send email... 356 - 357 - components.ContactSuccess(name).Render(r.Context(), w) 358 52 } 359 53 ``` 360 54 361 - ## Best Practices 55 + ## Escalate to Other Skills 362 56 363 - 1. **Always use r.Context()** when rendering 364 - 2. **Handle errors** from Render() 365 - 3. **Set appropriate status codes** before rendering 366 - 4. **Use middleware** for common functionality 367 - 5. **Separate routes** from handler logic 368 - 6. **Return early** on errors 57 + - Need reusable view APIs: use `templ-components`. 58 + - Need HTMX partial responses: use `templ-htmx`. 59 + - Need syntax corrections in `.templ`: use `templ-syntax`. 369 60 370 - ## Common Patterns 371 - 372 - ### Pattern: Redirect After Post 373 - 374 - ```go 375 - func formHandler(w http.ResponseWriter, r *http.Request) { 376 - if r.Method == "GET" { 377 - components.Form().Render(r.Context(), w) 378 - return 379 - } 61 + ## References 380 62 381 - // POST - process form 382 - processForm(r) 383 - 384 - // Redirect 385 - http.Redirect(w, r, "/success", http.StatusSeeOther) 386 - } 387 - ``` 388 - 389 - ### Pattern: JSON API + HTML 390 - 391 - ```go 392 - func usersHandler(w http.ResponseWriter, r *http.Request) { 393 - users := getUsers() 394 - 395 - // Check Accept header 396 - if r.Header.Get("Accept") == "application/json" { 397 - w.Header().Set("Content-Type", "application/json") 398 - json.NewEncoder(w).Encode(users) 399 - return 400 - } 401 - 402 - // Default: HTML 403 - components.UserList(users).Render(r.Context(), w) 404 - } 405 - ``` 406 - 407 - ## Next Steps 408 - 409 - - **Add interactivity** → Use `templ-htmx` skill 63 + - Handlers and routing: `resources/handlers-and-routing.md` 64 + - Middleware and errors: `resources/middleware-and-errors.md` 65 + - Request data and response patterns: `resources/request-and-response-patterns.md` 66 + - Go `net/http`: https://pkg.go.dev/net/http 67 + - templ server-side rendering: https://templ.guide/server-side-rendering/
+52
.claude/skills/templ-http/resources/handlers-and-routing.md
··· 1 + # Templ HTTP Handlers and Routing 2 + 3 + Use `templ.Handler(...)` for simple fixed-render endpoints, and custom handlers with `.Render(...)` when request data drives output. 4 + 5 + ## templ.Handler Shortcut 6 + 7 + ```go 8 + http.Handle("/", templ.Handler( 9 + components.HomePage(), 10 + templ.WithStatus(http.StatusOK), 11 + templ.WithContentType("text/html; charset=utf-8"), 12 + )) 13 + ``` 14 + 15 + Common options include `templ.WithStatus`, `templ.WithContentType`, and `templ.WithErrorHandler`. 16 + 17 + ## Minimal Handler 18 + 19 + ```go 20 + func homeHandler(w http.ResponseWriter, r *http.Request) { 21 + if err := components.HomePage().Render(r.Context(), w); err != nil { 22 + http.Error(w, "render failed", http.StatusInternalServerError) 23 + return 24 + } 25 + } 26 + ``` 27 + 28 + ## Routing Setup 29 + 30 + ```go 31 + func routes() http.Handler { 32 + mux := http.NewServeMux() 33 + mux.HandleFunc("/", homeHandler) 34 + mux.HandleFunc("/users", usersHandler) 35 + return mux 36 + } 37 + ``` 38 + 39 + ## Method Dispatch 40 + 41 + - Branch on `r.Method` where one path supports multiple actions. 42 + - Return `http.StatusMethodNotAllowed` for unsupported methods. 43 + - Keep route registration explicit for readability. 44 + 45 + ## Fragment Responses 46 + 47 + For partial UI updates, set `ctx := templ.WithFragments(r.Context(), "fragment-name")` before rendering. 48 + 49 + ## Sources 50 + 51 + - https://templ.guide/server-side-rendering/creating-an-http-server-with-templ 52 + - https://templ.guide/syntax-and-usage/fragments
+49
.claude/skills/templ-http/resources/middleware-and-errors.md
··· 1 + # Templ HTTP Middleware and Errors 2 + 3 + ## Middleware Pattern 4 + 5 + ```go 6 + func logging(next http.Handler) http.Handler { 7 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 8 + log.Printf("%s %s", r.Method, r.URL.Path) 9 + next.ServeHTTP(w, r) 10 + }) 11 + } 12 + ``` 13 + 14 + Use middleware for shared concerns: auth, logging, tracing, request IDs. 15 + 16 + ## Component Error Pages 17 + 18 + ```go 19 + func renderError(w http.ResponseWriter, r *http.Request, code int, msg string) { 20 + w.WriteHeader(code) 21 + _ = components.ErrorPage(code, msg).Render(r.Context(), w) 22 + } 23 + ``` 24 + 25 + ## Context and Middleware 26 + 27 + - Templates can read request context, so middleware can attach request-scoped values. 28 + - Be careful with type assertions from context values; missing keys can panic. 29 + 30 + ## CSP Nonce Pattern 31 + 32 + When using strict CSP, generate a nonce per request, set the CSP header, and pass it with `templ.WithNonce(ctx, nonce)` before rendering. 33 + 34 + ## Buffered vs Streaming 35 + 36 + - Default templ handler behavior buffers output before writing, which improves status/error handling safety. 37 + - `templ.WithStreaming()` enables progressive output and `@templ.Flush()`, but headers/status are harder to change once bytes are sent. 38 + 39 + ## Status and Render Discipline 40 + 41 + - Set status codes before rendering the response body. 42 + - Return early on handler errors. 43 + - Keep handler bodies thin; call services/helpers for business logic. 44 + 45 + ## Sources 46 + 47 + - https://templ.guide/syntax-and-usage/context 48 + - https://templ.guide/security/content-security-policy 49 + - https://templ.guide/server-side-rendering/streaming
+51
.claude/skills/templ-http/resources/request-and-response-patterns.md
··· 1 + # Templ HTTP Request and Response Patterns 2 + 3 + ## Request Data Sources 4 + 5 + - Query params: `r.URL.Query().Get("q")` 6 + - Form data: `r.ParseForm(); r.FormValue("email")` 7 + - Path segments: trim or route extraction helpers 8 + 9 + ## Post/Redirect/Get 10 + 11 + ```go 12 + func contactHandler(w http.ResponseWriter, r *http.Request) { 13 + if r.Method == http.MethodGet { 14 + _ = components.ContactForm().Render(r.Context(), w) 15 + return 16 + } 17 + _ = r.ParseForm() 18 + // process form 19 + http.Redirect(w, r, "/contact/success", http.StatusSeeOther) 20 + } 21 + ``` 22 + 23 + ## Form Handling Flow 24 + 25 + Typical templ form flow: 26 + 27 + 1. `ParseForm` and decode into a request model. 28 + 2. Validate. 29 + 3. On error, re-render with user input + validation messages. 30 + 4. On success, perform action and redirect. 31 + 32 + Use view models to keep templates focused on display state instead of transport/domain objects. 33 + 34 + ## Partial Responses 35 + 36 + For targeted updates, render only named fragments with `templ.WithFragments(...)`. 37 + 38 + ## Mixed HTML/JSON Endpoint 39 + 40 + - If `Accept: application/json`, encode JSON and return. 41 + - Otherwise render templ component for HTML. 42 + 43 + ## Static Files 44 + 45 + - Serve with `http.FileServer` behind `/static/` prefix. 46 + 47 + ## Sources 48 + 49 + - https://templ.guide/syntax-and-usage/forms 50 + - https://templ.guide/core-concepts/view-models 51 + - https://templ.guide/syntax-and-usage/fragments