···99- `internal/auth` : mostly clerk authorization
10101111## Build, Test, and Development Commands
1212+- Sandbox-safe Go cache setup (recommended before running Go commands in restricted environments):
1313+ - `export GOCACHE=/tmp/go-build`
1414+ - `export GOMODCACHE=/tmp/go-modcache`
1515+ - Alternative persistent path inside repo: `export GOCACHE=$PWD/.cache/go-build && export GOMODCACHE=$PWD/.cache/go-modcache`
1216- `go fmt ./...` then `go vet ./...`: Baseline formatting and static checks.
1317- `golangci-lint run ./...`: For expanded go linters
1418- `export ENABLE_MOCKS=1`: to test without kroger, openai credentials
+6
README.md
···2828```
2929if you change input css or any *.html
30303131+## Frontend Approach
3232+- Prefer server-rendered HTML and HTMX for interactive behavior.
3333+- Avoid SPA-style architecture for routine page interactions.
3434+- Keep custom JavaScript minimal and focused on browser-only APIs.
3535+- Migration plan: [docs/htmx-migration-plan.md](docs/htmx-migration-plan.md)
3636+3137## Live site
32383339* Uptime https://stats.uptimerobot.com/ehEFlvlNM9
···3232//go:embed static/tailwind.css
3333var tailwindCSS []byte
34343535+//go:embed static/htmx@2.0.8.js
3636+var htmx208JS []byte
3737+3538func runServer(cfg *config.Config, logsinkCfg logsink.Config, addr string) error {
3639 cache, err := cache.MakeCache()
3740 if err != nil {
···5356 return fmt.Errorf("failed to create recipe generator: %w", err)
5457 }
55585959+ //using etags for caching because this changes somewhat often
5660 tailwindETag := fmt.Sprintf(`"%x"`, sha256.Sum256(tailwindCSS))
5761 mux.HandleFunc("/static/tailwind.css", func(w http.ResponseWriter, r *http.Request) {
5862 if r.Header.Get("If-None-Match") == tailwindETag {
···6468 w.Header().Set("ETag", tailwindETag)
6569 if _, err := w.Write(tailwindCSS); err != nil {
6670 slog.ErrorContext(r.Context(), "failed to write tailwind css", "error", err)
7171+ }
7272+ })
7373+7474+ //intentionally versioned so that we can cache aggressively
7575+ mux.HandleFunc("/static/htmx@2.0.8.js", func(w http.ResponseWriter, r *http.Request) {
7676+ w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
7777+ w.Header().Set("Cache-Control", "public, immutable")
7878+ if _, err := w.Write(htmx208JS); err != nil {
7979+ slog.ErrorContext(r.Context(), "failed to write htmx js", "error", err)
6780 }
6881 })
6982
+75
docs/htmx-migration-plan.md
···11+# HTMX Migration Plan
22+33+## Goal
44+Move interactive UI behavior toward server-rendered HTML with [HTMX](https://htmx.org/docs/) and away from custom page-level JavaScript or SPA patterns.
55+66+## Frontend Direction
77+- Prefer HTML-first server rendering.
88+- Prefer HTMX for partial updates and form interactions.
99+- Keep JavaScript only when browser APIs are required (for example clipboard access, auth SDKs, or third-party embeds).
1010+- Do not introduce SPA frameworks for routine UI interactions.
1111+1212+## Page-by-Page Plan
1313+1414+### `internal/templates/home.html`
1515+- Current state: no custom interaction JS.
1616+- Plan: optional future HTMX ZIP lookup preview, otherwise keep as-is.
1717+- Priority: low.
1818+1919+### `internal/templates/locations.html`
2020+- Current state: JavaScript-heavy interactions.
2121+- Plan:
2222+ - Use `hx-post` for favorite store updates.
2323+ - Swap updated server-rendered list fragment in place.
2424+ - Use semantic `<details>` for instruction panel toggle.
2525+- Priority: high.
2626+2727+### `internal/templates/shoppinglist.html`
2828+- Current state: most JavaScript-heavy page.
2929+- Plan:
3030+ - Phase 1: replace simple toggles with semantic HTML where possible.
3131+ - Phase 2: convert save/dismiss/finalize interactions to HTMX endpoints with fragment swaps.
3232+ - Keep minimal JS for clipboard copy (`navigator.clipboard`).
3333+- Priority: high.
3434+3535+### `internal/templates/recipe.html`
3636+- Current state: form uses full POST+redirect flow.
3737+- Plan:
3838+ - Convert question submission to HTMX.
3939+ - Return and swap/append question thread fragments from server.
4040+- Priority: medium.
4141+4242+### `internal/templates/user.html`
4343+- Current state: full page POST roundtrips.
4444+- Plan:
4545+ - Convert preference and recipe-add forms to HTMX partial updates with inline success/error messaging.
4646+- Priority: medium.
4747+4848+### `internal/templates/spinner.html`
4949+- Current state: meta refresh polling.
5050+- Plan:
5151+ - Replace meta refresh with HTMX polling against recipe status endpoint.
5252+- Priority: medium.
5353+5454+### `internal/templates/auth_establish.html` and `internal/templates/clerk_refresh.html`
5555+- Current state: required Clerk auth JavaScript.
5656+- Plan: keep JavaScript (HTMX is not a replacement for Clerk browser SDK behavior).
5757+- Priority: no migration planned.
5858+5959+### `internal/templates/mail.html`
6060+- Current state: static email template.
6161+- Plan: no HTMX relevance.
6262+- Priority: none.
6363+6464+## Suggested Rollout
6565+1. Complete `locations` migration as the reference pattern.
6666+2. Tackle `shoppinglist` in small, testable phases.
6767+3. Move `recipe` and `user` forms to HTMX partials.
6868+4. Revisit spinner polling once status endpoints are stable.
6969+7070+## Implemented Example
7171+`locations` has already been prototyped with HTMX:
7272+- Added `POST /locations/favorite` to support HTMX and non-HTMX fallback.
7373+- Replaced client-side favorite update logic with server-rendered swap.
7474+- Removed toggle script by switching instruction panel behavior to `<details>`.
7575+- Added endpoint tests for HTMX response and non-HTMX redirect paths.