Six findings from docs/audit-polish-2-2026-04-29.md DEFER list:
- F-23: hide STATUS column on /deadlines + /projects when every visible
row shares the same status. Toggled at render time via a CSS class on
the table; the column re-appears the moment filters re-introduce
variety.
- F-32: agenda urgency pill now renders only when it disagrees with
the day-bucket heading (e.g. an Überfällig deadline that lands in
HEUTE through a filter quirk). Common case drops the redundant tag.
- F-38: bottom-nav agenda badge already counted overdue+today (the
brief's option (b)); added a localized title + aria-label so the
count's semantics ("X überfällig + Y heute fällig") is no longer
ambiguous.
- F-40: glossary filter chips no longer mix EN+DE — DE shows
"Streitsachen / Erteilungsverfahren / Allgemein", EN keeps
"Litigation / Prosecution / General". Same i18n keys cover the
Suggest-modal category dropdown.
- F-48: /projects/{id}/sub-projects now 301-redirects to the canonical
/children URL via the existing redirects.go mechanism. Added a small
redirects_test.go to lock the alias in.
- F-49: dropped the meta-circular 2026-04-22 "Neuigkeiten / What's New"
changelog entry that referenced "this changelog" itself.
go build/vet/test clean, bun run build clean.
F-25 (mobile tables → card layout) is redesign-class and is scoped at
the bottom of the PR description as t-paliad-074, not implemented here.
64 lines
2.4 KiB
Go
64 lines
2.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// registerLegacyRedirects wires every historical German URL path to its
|
|
// English successor as a 301 Moved Permanently. One entry per legacy prefix;
|
|
// sub-paths are preserved verbatim in the redirect target (e.g.
|
|
// /akten/abc/deadlines → /projects/abc/deadlines).
|
|
//
|
|
// Only GET is redirected — old POST/PATCH/DELETE API endpoints are not
|
|
// mirrored. API clients must update to the English /api/* paths.
|
|
func registerLegacyRedirects(mux *http.ServeMux) {
|
|
// Prefix pairs: when the request path starts with key, it's rewritten to
|
|
// value + whatever followed the key. Order does not matter because each
|
|
// prefix is registered as its own pattern on the mux.
|
|
prefixes := map[string]string{
|
|
"/akten": "/projects",
|
|
"/projekte": "/projects",
|
|
"/fristen": "/deadlines",
|
|
"/termine": "/appointments",
|
|
"/notizen": "/notes",
|
|
"/einstellungen": "/settings",
|
|
"/checklisten": "/checklists",
|
|
"/dezernate": "/admin/partner-units",
|
|
"/departments": "/admin/partner-units",
|
|
"/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))
|
|
mux.Handle("GET "+oldPrefix+"/", redirectPrefix(oldPrefix, newPrefix))
|
|
}
|
|
|
|
// In-path aliases: redirect parameterised legacy URLs whose alias sits
|
|
// between path segments rather than at the prefix. The canonical project
|
|
// children tab is /projects/{id}/children; bookmarks to the older
|
|
// /sub-projects suffix would otherwise hit the 404 chrome.
|
|
mux.Handle("GET /projects/{id}/sub-projects", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
target := "/projects/" + r.PathValue("id") + "/children"
|
|
if r.URL.RawQuery != "" {
|
|
target += "?" + r.URL.RawQuery
|
|
}
|
|
http.Redirect(w, r, target, http.StatusMovedPermanently)
|
|
}))
|
|
}
|
|
|
|
func redirectPrefix(oldPrefix, newPrefix string) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
tail := strings.TrimPrefix(r.URL.Path, oldPrefix)
|
|
target := newPrefix + tail
|
|
if r.URL.RawQuery != "" {
|
|
target += "?" + r.URL.RawQuery
|
|
}
|
|
http.Redirect(w, r, target, http.StatusMovedPermanently)
|
|
})
|
|
}
|