From d219ca7cdff27c41b904a8828251993941cfb06b Mon Sep 17 00:00:00 2001 From: m Date: Sun, 26 Apr 2026 02:14:02 +0200 Subject: [PATCH 1/2] fix(redirects, settings): /whatsnew alias + /settings/{tab} deep-links (t-paliad-040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/handlers/appointments_pages.go | 36 +++++++++++++++++++------ internal/handlers/handlers.go | 2 +- internal/handlers/redirects.go | 3 +++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/internal/handlers/appointments_pages.go b/internal/handlers/appointments_pages.go index bbb0c6c..6f46ca5 100644 --- a/internal/handlers/appointments_pages.go +++ b/internal/handlers/appointments_pages.go @@ -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= so switching tabs doesn't round-trip. +// Profil / Benachrichtigungen / CalDAV / Dezernat. The active tab is picked +// client-side from ?tab= 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/ deep-link to its +// canonical ?tab= 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/ into /settings?tab= +// 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) } diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index f354c40..f0232ad 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -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 diff --git a/internal/handlers/redirects.go b/internal/handlers/redirects.go index 657b12f..6faa257 100644 --- a/internal/handlers/redirects.go +++ b/internal/handlers/redirects.go @@ -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)) From 2cf20448b372057b2b30caef0cc36a816e62b76f Mon Sep 17 00:00:00 2001 From: m Date: Sun, 26 Apr 2026 02:16:43 +0200 Subject: [PATCH 2/2] docs(tests): smoke delta report after t-paliad-038/039/040 + project-tab nil fix Wraps up the post-rename cleanup arc. Records what shipped, what's still open (none blocking), and the rationale for skipping Bug 10 (browser- emitted console error, not suppressible from JS). --- tests/smoke-auth-2026-04-26-cleanup.md | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/smoke-auth-2026-04-26-cleanup.md diff --git a/tests/smoke-auth-2026-04-26-cleanup.md b/tests/smoke-auth-2026-04-26-cleanup.md new file mode 100644 index 0000000..f069ad4 --- /dev/null +++ b/tests/smoke-auth-2026-04-26-cleanup.md @@ -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 1–10; 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.