Login redirect uses a plain 303 for htmx requests, nesting the login page inside the app shell
Description
When a session expires, any htmx-driven control renders the login page inside the app shell instead of navigating to it. This is a confirmed instance of the layout-nesting mechanism suspected in the duplicate-sidebar report.
The code
internal/server/ui_access.go:433-436:
func redirectUILogin(w http.ResponseWriter, r *http.Request) {
next := url.QueryEscape(safeUINext(r.URL.RequestURI()))
http.Redirect(w, r, "/login?next="+next, http.StatusSeeOther)
}
It never consults isHTMXRequest, even though that helper is defined immediately below at ui_access.go:438. grep -rn "HX-Redirect\|HX-Location\|HX-Refresh" internal/server/ matches only the bundled htmx.min.js — the app never sends any of these headers.
Call sites are internal/server/ui_auth_pages.go:196 and :207, inside uiAuthMiddleware, which wraps every authenticated UI route (ui_routes.go:28).
Steps to reproduce
- Sign in, then let the session cookie expire (or sign out in another tab).
- Click any sidebar entry — e.g. Me, which is
hx-get="/me/panel" hx-target="#main"(templates/shell_sidebar.html:23).
Actual
The XHR transparently follows the 303 to GET /login, which returns a standalone full document (templates/login.html:2). htmx extracts its body and swaps it into #main. The login card renders inside the app shell, beside the still-present sidebar and mobile app bar, while the URL bar still shows the old app URL.
Because #main (templates/shell_main.html:2) is a sibling of the sidebar inside .app-shell (templates/shell.html:24-27), any full-page response swapped into #main produces a nested layout. There are ~170 hx-target="#main" controls, so this is reachable from nearly every interactive element.
Expected
A real browser navigation to /login?next=....
Fix
if isHTMXRequest(r) {
w.Header().Set("HX-Redirect", "/login?next="+next)
w.WriteHeader(http.StatusNoContent)
return
}
Test both the htmx and plain-navigation branches.
Sub-issues
0Linked issues
0GitHub
0No branches or pull requests linked.