fix: series removal sent its status line twice #176

Merged
sulthan merged 1 commits from fix/series-remove-double-writeheader into main 2026-08-23 14:02:45 +07:00
Owner

Problem

Every "Remove" press on /admin/series logged:

http: superfluous response.WriteHeader call from bookmarkmanager/backend/internal/httpmw.(*gzipWriter).WriteHeader (middleware.go:89)

adminSeriesRemove answers the list surface with two fragments — the removed row, then the heading with the fresh count — and both went through h.render, which sends WriteHeader each time. The middleware is only the last hop; it reports the caller frame, not the culprit.

Cosmetic on the wire (htmx swaps both fragments fine), but it masks any real double-write behind a frame that points at the middleware.

Change

  • backend/internal/web/admin_series.go: the out-of-band heading executes straight onto w via h.tmpl.ExecuteTemplate, matching writeChromeOOB's existing convention in this package. h.render stays for the row, which owns the status line.
  • backend/web_test.go: TestRemoveFromListSendsOneStatusLine runs the router under a real httptest server with a captured Config.ErrorLog. httptest.ResponseRecorder never sees this warning, which is why TestRemoveFromListAnswersRowAndFreshHeading passed through the bug.

Verification

Reverted the handler fix with the test in place: fails with the exact production line. Restored: green. go test ./... all green.

## Problem Every "Remove" press on `/admin/series` logged: ``` http: superfluous response.WriteHeader call from bookmarkmanager/backend/internal/httpmw.(*gzipWriter).WriteHeader (middleware.go:89) ``` `adminSeriesRemove` answers the list surface with two fragments — the removed row, then the heading with the fresh count — and both went through `h.render`, which sends `WriteHeader` each time. The middleware is only the last hop; it reports the caller frame, not the culprit. Cosmetic on the wire (htmx swaps both fragments fine), but it masks any real double-write behind a frame that points at the middleware. ## Change - `backend/internal/web/admin_series.go`: the out-of-band heading executes straight onto `w` via `h.tmpl.ExecuteTemplate`, matching `writeChromeOOB`'s existing convention in this package. `h.render` stays for the row, which owns the status line. - `backend/web_test.go`: `TestRemoveFromListSendsOneStatusLine` runs the router under a real `httptest` server with a captured `Config.ErrorLog`. `httptest.ResponseRecorder` never sees this warning, which is why `TestRemoveFromListAnswersRowAndFreshHeading` passed through the bug. ## Verification Reverted the handler fix with the test in place: fails with the exact production line. Restored: green. `go test ./...` all green.
sulthan added 1 commit 2026-08-23 14:02:40 +07:00
adminSeriesRemove answered the list surface with two h.render calls -- the
row fragment and the out-of-band heading -- and h.render writes a status
line each time, so every removal logged "superfluous
response.WriteHeader call". The heading is an append to a response
already committed, so it now executes straight onto w, the way
writeChromeOOB already does it.

The regression test runs the router under a real server with a captured
ErrorLog: a ResponseRecorder never sees this warning, which is why the
existing removal test did not catch it.
sulthan merged commit cddd16bcdc into main 2026-08-23 14:02:45 +07:00
Sign in to join this conversation.