# Paliad Production Smoke Test — 2026-04-25

**Target:** https://paliad.de
**Run by:** ritchie/curie (t-paliad-033)
**Method:** curl preflight + Playwright headless (Chromium 1.59.1, viewport 1280×900)
**Trigger:** verify three features that shipped today — project tree (t-paliad-028), team directory (t-paliad-029), reminder fix (t-paliad-032).
**Auth state:** anonymous only (no test credentials available)

## Summary

- ✅ 11/11 legacy German redirects return 301 with correct English target.
- ✅ Subpath + querystring preservation works (`/akten/abc/deadlines?x=1` → `/projects/abc/deadlines?x=1`).
- ✅ 9/9 gated routes correctly 302 → `/login`.
- ✅ `/login` renders cleanly: `name@hlc.com` placeholder, DE/EN toggle, HLC hint, lime branding, no console errors.
- ✅ `/` renders the Patent Knowledge marketing landing (HTTP 200, lime branding intact, layout clean).
- ⚠️ `/` fires a 401 on `/api/changelog/unseen-count` for anon visitors — surfaces as a console error even though the JS catch is silent.
- ⚠️ Task brief assumed `/tools/*`, `/glossary`, `/courts`, `/links`, `/downloads`, `/checklists`, `/onboarding` were ungated. They are not. Auth-gated since the original Fristenrechner commit (d94f8e7, 2026-04-14). CLAUDE.md says these don't need `DATABASE_URL`, not that they don't need auth — brief misread.
- 🔒 Authenticated surface (where the three new features actually live: project tree, team directory, reminder UI) is **not covered** — needs follow-up worker with creds, as anticipated in the brief's "out of scope" section.

No regressions found in the surface that *can* be tested anonymously. The three shipped features cannot be validated without login.

## Per-URL Results

### Public / landing

| URL | HTTP | Final | Errs | Failed reqs | Visual | Notes |
|-----|------|-------|------|-------------|--------|-------|
| `/` | ✅ 200 | `/` | ⚠️ 1 | ⚠️ 1 | OK | Marketing landing renders; 401 on `/api/changelog/unseen-count` |
| `/login` | ✅ 200 | `/login` | 0 | 0 | OK | Form renders, placeholder `name@hlc.com`, DE/EN toggle, HLC hint, lime branding |
| `/onboarding` | ⚠️ 302 | `/login` | 0 | 0 | n/a | Gated. Brief expected public; matches code which has no public `/onboarding` route |

### Knowledge platform (auth-gated, contrary to brief expectation)

All return **302 → `/login`** (HTTP 200 after redirect, ending on the login page):

| URL | Final URL | Title | Errs | Failed reqs |
|-----|-----------|-------|------|-------------|
| `/tools/fristenrechner` | `/login` | Anmelden — Paliad | 0 | 0 |
| `/tools/kostenrechner` | `/login` | Anmelden — Paliad | 0 | 0 |
| `/tools/gebuehrentabellen` | `/login` | Anmelden — Paliad | 0 | 0 |
| `/checklists` | `/login` | Anmelden — Paliad | 0 | 0 |
| `/glossary` | `/login` | Anmelden — Paliad | 0 | 0 |
| `/courts` | `/login` | Anmelden — Paliad | 0 | 0 |
| `/links` | `/login` | Anmelden — Paliad | 0 | 0 |
| `/downloads` | `/login` | Anmelden — Paliad | 0 | 0 |

Per `internal/handlers/handlers.go:79-115`, these are registered on the `protected` mux; that's been the case since `d94f8e7` (2026-04-14). Not a regression — brief expectation was wrong.

### Legacy German → English redirects (301, all ✅)

| Legacy | → Target | Result |
|--------|----------|--------|
| `/fristen` | `/deadlines` | ✅ 301 |
| `/akten` | `/projects` | ✅ 301 |
| `/termine` | `/appointments` | ✅ 301 |
| `/notizen` | `/notes` | ✅ 301 |
| `/einstellungen` | `/settings` | ✅ 301 |
| `/projekte` | `/projects` | ✅ 301 |
| `/dezernate` | `/departments` | ✅ 301 |
| `/parteien` | `/parties` | ✅ 301 |
| `/checklisten` | `/checklists` | ✅ 301 |
| `/glossar` | `/glossary` | ✅ 301 |
| `/gerichte` | `/courts` | ✅ 301 |
| `/akten/abc/deadlines?x=1` | `/projects/abc/deadlines?x=1` | ✅ 301 (subpath + qs preserved) |

### Gated routes (302 → /login, all ✅)

`/dashboard`, `/projects`, `/deadlines`, `/appointments`, `/team`, `/agenda`, `/settings`, `/whatsnew`, `/search` — every one redirects to `/login`. Auth gate is functional.

## Console errors observed

Only one across all tested pages — on `/`:

```
Failed to load resource: the server responded with a status of 401 ()
URL: https://paliad.de/api/changelog/unseen-count
```

**Root cause:** `frontend/src/client/sidebar.ts:135-153` `initChangelogBadge()` fires unconditionally on every page that includes `<Sidebar />`. The catch swallows the error in JS, but the browser still logs the 401 in DevTools.

**Why the marketing landing has the sidebar at all:** the public `/` serves `dist/index.html` (handled in `internal/handlers/dashboard.go:47-52`) — same shell as the authenticated app, just without a session. Sidebar JS therefore runs in anon context and the changelog fetch hits a `protected` route.

**Severity:** cosmetic / log noise. Does not break the page. Worth fixing because (a) it surfaces a 401 that looks alarming in DevTools, and (b) it costs an HTTP round-trip for every anonymous landing visit.

**Suggested fix (one-liner):** inside `initChangelogBadge()`, early-return when the changelog badge link itself isn't present, OR check for a session cookie marker before fetching, OR move the marketing landing to its own minimal shell.

## Failing network requests

Same 401 — only one across all pages tested:

| Page | Request | Status |
|------|---------|--------|
| `/` | `GET /api/changelog/unseen-count` | 401 |

No 4xx or 5xx anywhere else. Asset pipeline (`/assets/*`) returns 200s.

## Screenshots

Stored in `/tmp/mai-tester/`:

- `paliad-root.png` — marketing landing, lime hero, sidebar icon-rail visible on left
- `paliad-login.png` — clean login card, name@hlc.com placeholder, DE/EN toggle top-right
- `paliad-onboarding.png`, `paliad-tools-*.png`, `paliad-glossary.png`, `paliad-courts.png`, `paliad-links.png`, `paliad-downloads.png`, `paliad-checklists.png` — all show the login form (302 redirected there); identical to `paliad-login.png` (≈28 KB each, vs. 74 KB for the rendered marketing landing — quick file-size sanity check confirms they are all the login screen)

Visual sanity: layout intact on all rendered pages, no half-rendered content, no broken images on `/login` or `/`.

## Top issues, ranked by severity

1. **Brief vs. reality mismatch — knowledge platform is auth-gated.** Brief said "ungated, per CLAUDE.md"; CLAUDE.md actually only says these routes don't need `DATABASE_URL`. The product *requires login* for everything except `/`, `/login`, `/logout`, `/assets/*`. **Action:** either (a) clarify CLAUDE.md so future briefs don't repeat this misread, or (b) decide that the product wants a public knowledge tier and move those routes to the unprotected mux. Today's behaviour matches the code.
2. **Anon `/` triggers 401 on `/api/changelog/unseen-count`.** Console noise; minor. Fix in `frontend/src/client/sidebar.ts` — gate the fetch behind a session marker, or scope `initChangelogBadge` to authenticated-only pages.
3. **Anon `/onboarding` 302s to `/login`.** Brief's expectation was a "sane" anon behaviour; redirecting to login *is* sane, but if the product later wants self-serve onboarding (e.g. via invite link), the route doesn't currently support that flow.
4. **Three new features (t-paliad-028, -029, -032) untested.** All sit behind auth — project tree at `/projects`, team directory at `/team`, reminders inside Akten/Frist UI. Need a follow-up worker with credentials. Unit + handler tests are presumably green (they shipped), but no live-prod verification has happened.
5. **No further issues found in the anonymous surface.** Login form, redirects, auth gate, marketing landing all behave as coded.

## Acceptance checklist

- [x] All public + knowledge URLs in scope visited
- [x] All legacy redirects verified (11/11 + subpath/qs case)
- [x] All gated routes verified to 302 to `/login`
- [x] Report file written
- [x] Per-URL screenshots captured at `/tmp/mai-tester/paliad-<slug>.png`
- [x] No raw logs in chat — summary only

## Out of scope (explicitly deferred)

- Authenticated flows: project tree (t-paliad-028), team directory (t-paliad-029), reminder fix (t-paliad-032), Dashboard, Agenda, Search, Whatsnew, Settings.
- Email send (no SMTP creds), CalDAV sync (no CalDAV creds).
- Multi-language end-to-end (only DE rendered, EN toggle visually present but interaction not exercised).

Hand off to a follow-up worker once m provides test credentials.
