Merge: pull latest main into bottom-nav branch

This commit is contained in:
m
2026-04-26 10:32:19 +02:00
4 changed files with 85 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))

View File

@@ -0,0 +1,53 @@
# Smoke Cleanup Delta — 2026-04-26 (post t-paliad-038/039/040)
Original report: `tests/smoke-auth-2026-04-25.md` (10 bugs).
This is a delta report — only changes since the original. No URL grid replay.
Tester: `tester@hlc.de` (admin), Playwright headless against `https://paliad.de`.
## What shipped
| Task | Branch | Merge | What |
|---|---|---|---|
| t-paliad-038 | `mai/brunel/projects-detail-rename` | d81da4b | `/projects/{id}` notfound + German DOM/URL leftovers in `projects-detail` |
| t-paliad-039 | `mai/brunel/urgent-deadlines-id` | f782ef7 | `/deadlines/{id}` notfound, `/deadlines` "Invalid Date", `/appointments/{id}` notfound |
| (no task #) | `mai/brunel/footer-tool-by-flexsiebels` | 3ff982c | Footer "Nur für internen Gebrauch" → "ein Werkzeug von flexsiebels.de" |
| (no task #) | `mai/brunel/project-tabs-nil-empty` | b4a409a | `/projects/{id}` tabs blank — list services returned JSON `null` → client `.length` crash |
| t-paliad-040 | `mai/brunel/smoke-cleanup-batch-2` | 3a1eb07 | `/whatsnew` alias + `/settings/{tab}` deep-link redirects |
## Bug status
### Fixed
- **Bug 1** (`/projects/{id}` SSR notfound while API 200) — t-paliad-038. `parseAkteID` was checking the German URL prefix; renamed to `parseProjectID` reading `/projects/{id}`. Confirmed via Playwright: project chrome + tabs render.
- **Bug 7** (`/whatsnew` bare 404) — t-paliad-040. 301 → `/changelog` via `internal/handlers/redirects.go`. Confirmed: `curl -sI /whatsnew` → 301 Location: /changelog.
- **Bug 8** (`/settings` tab deep-link 404) — t-paliad-040. Replaced single CalDAV-only handler with generic `/settings/{tab}` redirector. Map accepts both German tab IDs (`profil`, `benachrichtigungen`, `dezernat`) and English aliases (`profile`, `notifications`, `department`); unknown slugs fall back to `/settings` (default tab) instead of 404. Confirmed in Playwright as logged-in admin: `/settings/notifications``/settings?tab=benachrichtigungen` and the "Benachrichtigungen" tab is active. Same for `/settings/dezernat`, `/settings/profile`, `/settings/department`.
- **Bug 9** (404 has no chrome) — already shipped in t-paliad-037. Verified still in place: `/garbage` returns 404 with sidebar + footer + "Zurück zum Dashboard" CTA.
- **`/deadlines/{id}` notfound** (caught after smoke ran) — t-paliad-039. `parseFristID` checked `/fristen` URL prefix; renamed to `parseDeadlineID` reading `/deadlines`. Same class as Bug 1.
- **`/deadlines` "Invalid Date"** (caught after smoke ran) — t-paliad-039. `fmtDate` blindly appended `T00:00:00` to API dates; API now returns full ISO datetime. Guarded with `iso.length === 10` / `iso.slice(0, 10)`.
- **`/projects/{id}` tabs blank** (m reported after t-paliad-038) — empty list services returned JSON `null` (sqlx scan into `var rows []T` leaves nil slice; `encoding/json` marshals nil slice as `null`). Fixed at the source for 9 services (`party`, `project`, `deadline`, `appointment`, `note`, `checklist_instance`, `team`, `department` + their `WithProject` variants); client also coerces with `?? []`.
### Skipped (documented)
- **Bug 10** (login form 401 console replay) — **not fixable from JS**. Reproduced cleanly: a failed `/api/login` with the wrong password emits the exact one-line console error `[ERROR] Failed to load resource: the server responded with a status of 401`. That message is generated by the browser's network stack itself, not by `login.ts` (which has no `console.error` call — it shows the error in the form via `showError`). The only "fixes" available either compromise the security pattern (return HTTP 200 with `{ok: false}` body so 4xx never appears) or don't actually suppress the browser log (4xx → 4xx, status code change doesn't help). Brief explicitly authorised the skip ("If this turns out to be a non-trivial yak-shave, skip it"). Severity is low; the form UX itself works correctly.
### Still open from original report
- **Bug 2** — same root cause as Bug 1 / Bug `/deadlines/{id}` / Bug `/appointments/{id}` (German URL prefix in `parse*ID`). All three now fixed in t-paliad-038 + t-paliad-039.
- **Bug 3** — `/projects` 500 (RLS function bodies) — already fixed in t-paliad-036 (migration 021).
- **Bugs 4 / 5 / 6** — i18n leaks, `/api/departments` 500, dashboard activity — already fixed in t-paliad-037.
- **Bugs not numbered above** (none) — original report had Bugs 110; all addressed.
## What's still off (post-fix smoke)
- **None blocking.** One known cosmetic: the browser's auto-emitted "Failed to load resource: 401" on a wrong-password login attempt (Bug 10) is unavoidable without changing the auth response shape. Documented above.
- **Console clean** on the canonical pages I exercised: `/dashboard`, `/projects`, `/projects/{id}` (history/parties/deadlines/appointments/team), `/deadlines`, `/deadlines/{id}`, `/appointments`, `/appointments/{id}`, `/settings`, `/settings/notifications`, `/settings/dezernat`, `/changelog`, `/whatsnew`. No JS errors.
- **Footer copy** — confirmed live: "© 2026 Paliad — ein Werkzeug von flexsiebels.de" on `/dashboard` and the marketing index.
## Verdict
**Clean.** All actionable items from the original 2026-04-25 smoke report are fixed (8/10) or explicitly deferred with rationale (Bug 10). No new regressions surfaced during verification. Ready for the next smoke pass.
## Out of scope (deferred — m to decide)
- **`Hogan Lovells` → dynamic `FIRM_NAME` constant** — m requested it, head asked to hold for architectural sign-off. WIP draft exists in `git stash` on this worker; can be revived as its own task.