fix(redirects, settings): /whatsnew alias + /settings/{tab} deep-links (t-paliad-040)

Bug 7 — /whatsnew was bare 404. Sidebar uses /changelog (canonical) but
users typing /whatsnew from memory hit the not-found chrome. Added
/whatsnew → /changelog as a 301 to internal/handlers/redirects.go,
following the existing legacy-redirect pattern. Wired on the OUTER mux so
unauthenticated bookmarks redirect one-hop instead of round-tripping
through /login. /search left as-is per the brief — sidebar's global-search
overlay is the live UX, /search would only be hit via typo and falls back
to the chromed 404 from t-paliad-037.

Bug 8 — /settings/caldav worked (200 → 301 → /settings?tab=caldav) but
/settings/notifications, /settings/dezernat, /settings/profile all 404'd.
Tabs themselves were fine in-page; only the deep-link form was broken.
Replaced the single CalDAV-only handler with a generic /settings/{tab}
redirector backed by a slug→canonical map that accepts both the German
tab IDs the client TS understands (profil, benachrichtigungen, dezernat)
and intuitive English aliases (profile, notifications, department).
Unknown slugs fall back to /settings (default tab) instead of 404 so
typos don't break.

Bug 10 — login form 401 console replay: skipped per brief permission.
Reproduced in Playwright; the console message is the browser's automatic
"Failed to load resource: the server responded with a status of 401"
emitted by the network stack itself. login.ts has no console.error call.
The only workarounds (server returns 200 with {ok:false}, or 422 instead
of 401) either compromise the security pattern or don't actually suppress
the browser log. Documented in the smoke delta report.

Verified: go build/vet/test clean, bun run build clean.
This commit is contained in:
m
2026-04-26 02:14:02 +02:00
parent b4a409a013
commit d219ca7cdf
3 changed files with 32 additions and 9 deletions

View File

@@ -24,16 +24,36 @@ func handleAppointmentsCalendarPage(w http.ResponseWriter, r *http.Request) {
}
// handleSettingsPage serves the unified settings page with tabs for
// Profil / Benachrichtigungen / CalDAV. The active tab is picked client-side
// from ?tab=<name> so switching tabs doesn't round-trip.
// Profil / Benachrichtigungen / CalDAV / Dezernat. The active tab is picked
// client-side from ?tab=<name> so switching tabs doesn't round-trip.
func handleSettingsPage(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dist/settings.html")
}
// handleSettingsCalDAVRedirect keeps /settings/caldav working for
// bookmarks and any external links while the canonical URL moves to
// /settings?tab=caldav. 301 Moved Permanently — browsers cache the hop
// so the redirect only costs once per bookmark.
func handleSettingsCalDAVRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/settings?tab=caldav", http.StatusMovedPermanently)
// settingsTabAliases maps every supported /settings/<slug> deep-link to its
// canonical ?tab=<name> value the client TS understands. Both the German tab
// IDs (profil/benachrichtigungen/dezernat) and intuitive English aliases
// (profile/notifications/department) are accepted so bookmarks, smoke tests,
// and manually-typed URLs all land on the right tab.
var settingsTabAliases = map[string]string{
"profil": "profil",
"profile": "profil",
"benachrichtigungen": "benachrichtigungen",
"notifications": "benachrichtigungen",
"caldav": "caldav",
"dezernat": "dezernat",
"department": "dezernat",
}
// handleSettingsTabRedirect turns /settings/<slug> into /settings?tab=<canonical>
// as 301 Moved Permanently. Unknown slugs fall back to the bare /settings page
// (the client picks the default tab) so a typo doesn't 404.
func handleSettingsTabRedirect(w http.ResponseWriter, r *http.Request) {
slug := r.PathValue("tab")
canonical, ok := settingsTabAliases[slug]
if !ok {
http.Redirect(w, r, "/settings", http.StatusMovedPermanently)
return
}
http.Redirect(w, r, "/settings?tab="+canonical, http.StatusMovedPermanently)
}

View File

@@ -245,7 +245,7 @@ func Register(mux *http.ServeMux, client *auth.Client, giteaAPIToken string, svc
// Settings
protected.HandleFunc("GET /settings", gateOnboarded(handleSettingsPage))
protected.HandleFunc("GET /settings/caldav", handleSettingsCalDAVRedirect)
protected.HandleFunc("GET /settings/{tab}", handleSettingsTabRedirect)
// Catch-all 404 — runs for any authenticated path that no more-specific
// pattern claimed. Renders the chromed shell with HTTP 404 (Bug 9 from

View File

@@ -28,6 +28,9 @@ func registerLegacyRedirects(mux *http.ServeMux) {
"/parteien": "/parties",
"/gerichte": "/courts",
"/glossar": "/glossary",
// Memorable aliases — sidebar uses the canonical path but users
// type these from memory and would otherwise hit the 404 chrome.
"/whatsnew": "/changelog",
}
for oldPrefix, newPrefix := range prefixes {
mux.Handle("GET "+oldPrefix, redirectPrefix(oldPrefix, newPrefix))