# Paliad Polish Audit — Triage 2 — 2026-04-29

**Source audit:** `docs/audit-polish-2026-04-27.md` (50 findings, 41 screenshots).
**Method:** for every BATCH-tagged finding, re-verified against the current
codebase (post-PRs B/D/E + the brand palette sweep + firm-name sweep).
**Goal:** mark each finding KEEP / OBSOLETE / RESCOPED / DEFER, group the keeps
into 2–3 ship-ready PRs, and rank what m would notice first on Monday morning.

**What already shipped since the original audit:**
- t-paliad-060 (PR-B): F-05, F-06 — `lang` attr on date/time inputs.
- t-paliad-061 (PR-D): F-11, F-17, F-18, F-19, F-26, F-44, F-45.
- t-paliad-062 (PR-E): F-02, F-03, F-08, F-09.
- t-paliad-063 (palette): F-14, F-30, F-31 (explicitly superseded per commit).
- t-paliad-064 (reminder redesign): the new settings UI uses inline-flex
  `caldav-toggle-label` blocks → incidentally fixes F-22.
- t-paliad-065 (firm-name): F-01.
- Pre-audit (t-paliad-049 modal/breadcrumb polish): /projects/new already has
  a Cancel button (`btn-cancel`) → F-34 OBSOLETE.

---

## Verification table

| ID | Status | Notes |
|---|---|---|
| F-01 | OBSOLETE | Stripped by t-paliad-065 firm sweep. Curl of `/` and `/login` shows only "Paliad" + "HLC". |
| F-02 | OBSOLETE | t-paliad-062 (search input padding). |
| F-03 | OBSOLETE | t-paliad-062 (migration 024 column rename). |
| F-04 | KEEP | `frontend/src/client/deadlines-new.ts:45` still references `fristen.field.project.choose`; `i18n.ts` only has `fristen.field.akte.choose` → renders raw key. Either rename in deadlines-new.ts to `fristen.field.akte.choose`, or add the new key. |
| F-05 | OBSOLETE | t-paliad-060. |
| F-06 | OBSOLETE | t-paliad-060. |
| F-07 | KEEP | `internal/services/project_service.go:625` emits `project_type_changed` with English title "Project type changed"; description embeds raw English values (`case → litigation`). Dashboard activity widget renders these verbatim. Plus mixed-language nouns ("Note zu deadline hinzugefügt"). Same class as the t-paliad-037 sweep but for newer events. |
| F-08 | OBSOLETE | t-paliad-062. |
| F-09 | OBSOLETE | t-paliad-062. |
| F-10 | KEEP | The `inf.rejoin` raw slug renders because client-side rule lookup falls through when no matching rule label exists. Need a missing-rule label fallback (display "—" or a humanized variant) and/or i18n key for known catalog slugs. |
| F-11 | OBSOLETE | t-paliad-061. |
| F-12 | KEEP | `frontend/src/deadlines.tsx:101` and `frontend/src/appointments.tsx:101` still ship `<th data-i18n="fristen.col.akte">Akte</th>` / `termine.col.akte`; i18n.ts L541/1043 still maps to "Akte". PR-D fixed the *filter* dropdown; the *column header* was deliberately left for PR-A which never ran. |
| F-13 | KEEP | `client/appointments.ts:153` and `client/deadlines.ts:197` render `<span class="frist-project-title">` but `global.css:4716` only styles `.frist-akte-title { display: block; }` — class-name mismatch from the rename → ref + title render inline → "L-2026-001Siemens AG ./." collision. Trivial: rename the CSS or the markup. |
| F-14 | OBSOLETE | t-paliad-063 (palette sweep). |
| F-15 | KEEP | `projects-detail.tsx:334` still uses `className="btn-danger"` (red `#dc2626`) for the "Projekt archivieren" button. The destructive-modal confirm action also uses `btn-danger`, which is fine — it's the entry-point button on the working surface that screams. |
| F-16 | KEEP | `global.css:4089-4093` still ships saturated random colors per type chip (`akten-type-client` lavender, `akten-type-litigation` pink-red, `akten-type-patent` cyan, `akten-type-case` salmon, `akten-type-project` neutral-green). Same classes drive `/projects` and `/admin/team`. |
| F-17 | OBSOLETE | t-paliad-061. |
| F-18 | OBSOLETE | t-paliad-061. |
| F-19 | OBSOLETE | t-paliad-061. |
| F-20 | RESCOPED | Palette sweep harmonized the *colour* (every active tab now points at `--hlc-lime`), but the structural inconsistency remains: `.akten-tab.active` uses `font-weight: 600` + midnight text, while `.login-tab.active` and `.gebuehren-tab.active` use accent-coloured text. Drop to one rule. |
| F-21 | KEEP | `internal/services/deadline_service.go:306` still inserts events with title `"Deadline updated"` (English, in DE narrative on /projects/{id}/history). Same fix lane as F-07. |
| F-22 | OBSOLETE | t-paliad-064 PR-3/4 introduced `caldav-toggle-label` (`display: inline-flex`, `gap: 0.5rem`) for every checkbox row — labels and checkboxes are now adjacent. |
| F-23 | DEFER | Hiding STATUS when single-valued is a design call (some users like the redundancy as a trust signal). Punt to a later "table density" pass. |
| F-24 | KEEP | Mobile filter row still stacks awkwardly. Single-page CSS fix on `/projects` (and adjacent `/deadlines`/`/appointments`). |
| F-25 | DEFER | Card-layout-on-mobile is a design refactor, not a polish edit. Spans `/projects`, `/deadlines`, `/appointments`, `/admin/team`. Out of scope for polish-2; flag as a standalone t-task. |
| F-26 | OBSOLETE | t-paliad-061. |
| F-27 | KEEP | `client/projects-detail.ts:1143` always renders the breadcrumb; no path-depth check. One conditional in `renderBreadcrumb()`. |
| F-28 | KEEP | Cell empty-placeholder is split: `/admin/team` and `/projects/{id}/deadlines` use "—"; `/projects` (REFERENZ, CLIENTMATTER) and `/appointments` (ORT) render blank. Pick one and grep for empty-cell renders. |
| F-29 | KEEP | TSX has a real `<a href="/checklists">` but `i18n.ts` strings (L949/2190) ship plain text "…unter Checklisten angelegt." On runtime translation, the anchor disappears. Fix: store the link in i18n with a placeholder (`{link}`) and substitute at render, or render two strings and inject the anchor. |
| F-30 | OBSOLETE | t-paliad-063 sidebar reskin. |
| F-31 | OBSOLETE | t-paliad-063 (button restyle). |
| F-32 | DEFER | The agenda was redesigned (day-bucket section headings now exist). The per-card urgency pill is still rendered, but it now carries information *only* when the urgency disagrees with the bucket (e.g. an "Überfällig" item appearing under HEUTE). Keeping it is defensible. Mark as design-call, not polish. |
| F-33 | KEEP | One `title=` attribute per project-ref render in `dashboard.ts` and `agenda.ts`. Trivial. |
| F-34 | OBSOLETE | `projects-new.tsx:46` already ships `<a className="btn-cancel" data-i18n="projekte.cancel">Abbrechen</a>`. |
| F-35 | KEEP | `projects.tsx:34` + `i18n.ts:844` still read "Mandanten, Streitsachen, Patente und Fälle …". Actual taxonomy is Mandant / Streitsache / Patent / Verfahren / Projekt. Replace "Fälle" → "Verfahren" (and possibly mention "Projekte"). |
| F-36 | KEEP | `ProjectFormFields.tsx:19` still ships `<option value="client">Mandant</option>` first → implicit default. Switch to a "Bitte wählen…" placeholder option, or default to `case`. |
| F-37 | KEEP | `client/notes.ts` textarea has no Strg+Enter / Cmd+Enter hint, no character counter, no markdown hint. Add a small footer line. |
| F-38 | DEFER | Bottom-nav badge semantics is a design decision — needs to match the agenda urgency definition. Tackle alongside any future agenda-redesign task. |
| F-39 | KEEP | Tree view shows "11" while flat shows "11 / 11"; pick one format. One client edit. |
| F-40 | DEFER | Glossary chip language ("Litigation" / "Prosecution" vs "Allgemein") is a product decision, not a polish fix. m to call. |
| F-41 | OBSOLETE | Was tagged OK in the audit. |
| F-42 | KEEP | Same fix as F-13 (`frist-project-title` CSS class) plus a monospace ref pill style + ellipsis on title. Bundle with F-13. |
| F-43 | KEEP | Empty state on `/projects/{id}/parties` is a single line — add an empty-state CTA card (matches the pattern used elsewhere). |
| F-44 | OBSOLETE | t-paliad-061. |
| F-45 | OBSOLETE | t-paliad-061. |
| F-46 | KEEP | `i18n.ts:1906` still maps `dashboard.greeting.prefix` to "Good day". Change to "Hello" (or "Hi"). One-line. |
| F-47 | KEEP | `/settings` profile placeholder "z.B. Associate, Partner, PA" still mixed EN/DE in i18n. One-line. |
| F-48 | DEFER | `/projects/{id}/sub-projects` would 404, but the canonical `/children` URL works and tabs auto-resolve to it. Aliasing is low-value; flag the canonical path in docs instead. |
| F-49 | DEFER | Tagged DEFER in original audit. |
| F-50 | KEEP | One CSS rule on `<main>` (or `body`) — bottom-padding equal to bottom-nav height on `<768px`. |

**Summary:** 18 OBSOLETE (already shipped), 26 KEEP, 1 RESCOPED (F-20), 7 DEFER (F-23, F-25, F-32, F-38, F-40, F-48, F-49). The KEEP set is the polish-2 backlog.

---

## PR plan — 3 bundles

### PR-1 — i18n leak sweep + activity log narrative 🟡

Single concern: text rendered to a German narrative that's still English or
raw-keyed. Ship as one PR — they're touched in adjacent files and the
reviewer can verify them together by walking the dashboard and the activity
tab.

**Includes:** F-04, F-07, F-10, F-12, F-21, F-29, F-35, F-46.

- F-04 (deadlines-new picker key) — i18n key add or rename. *Pure i18n.ts.*
- F-07 (dashboard activity event types + narrative nouns) — needs three
  edits: (a) Go service-side, switch event titles from "Project type changed"
  / "Note added to deadline" to neutral identifiers; (b) i18n.ts add
  `dashboard.action.project_type_changed`, `…note_added`, etc.; (c) frontend
  dashboard renderer translates the event_type and the dynamic values
  (`case` → t("projekte.type.case")) before joining into the narrative.
  *This is the only Go-side change in the bundle.*
- F-10 (raw `inf.rejoin`) — frontend rule-label lookup falls back to "—"
  when no label exists; can be done in `client/deadlines.ts` only. Optional
  follow-up: backfill `fristen.rule.<slug>` keys.
- F-12 (AKTE column header) — flip `fristen.col.akte` and `termine.col.akte`
  to "Projekt" / "Matter" (DE/EN), plus the `data-i18n` attribute label.
- F-21 ("Deadline updated" in Verlauf) — same shape as F-07; one Go edit
  (`deadline_service.go:306` title → neutral identifier) plus i18n key add.
- F-29 (checklists empty-state link) — store the empty-state copy as two
  i18n strings or a `{link}` placeholder; render with a real `<a>`.
- F-35 (subtitle taxonomy) — flip "Fälle" → "Verfahren" in
  `projekte.subtitle` (DE+EN) and the SSR fallback in `projects.tsx:34`.
- F-46 (Good day) — one-line i18n change.

**Risk:** medium. Go-side event-emission edits need a smoke test of the
activity feed (dashboard widget + project Verlauf tab) post-deploy. Existing
events in the DB carry the *old* English titles — the renderer should
translate the event_type, not the stored title (so historical rows benefit
too). Worth calling out explicitly in the PR description.

### PR-2 — visual residue + small per-page polish 🟢

Single concern: small CSS/markup edits, mostly self-contained per page.

**Includes:** F-13, F-15, F-24, F-27, F-28, F-33, F-36, F-39, F-42, F-43,
F-47, F-50.

- F-13 (appointments AKTE collision) — rename CSS rule
  `.frist-akte-title` → `.frist-project-title` (or add the new selector).
  Same rule fixes F-42 partially.
- F-15 (red archive button) — change `className="btn-danger"` →
  `btn-secondary` (or introduce `btn-archive` with neutral/outline styling).
  Modal confirm button stays red.
- F-24 (mobile filter row wrapping) — `/projects` filter container CSS:
  `flex-direction: column` + `align-items: stretch` at `<480px`; each
  filter as its own labelled block.
- F-27 (single-child breadcrumb) — `renderBreadcrumb` early-return when
  the chain has length ≤ 1.
- F-28 (placeholder consistency) — grep cell renderers; render "—" for
  empty REFERENZ, CLIENTMATTER, ORT, REGEL, WEITERE STANDORTE.
- F-33 (truncated ref tooltip) — `title=` attr on
  `.dashboard-upcoming-project-ref` and `.agenda-item-project`.
- F-36 (Mandant default) — add a `<option value="" disabled selected>` /
  `Bitte wählen…` first row in `ProjectFormFields.tsx:18`.
- F-39 (search counter format) — match flat ("11 / 11") to tree view, or
  the other way, by editing the tree-view counter.
- F-42 (deadlines AKTE wrapping) — same CSS rename as F-13 + `text-overflow:
  ellipsis` on `.frist-project-title` with a `title=` for the full text.
- F-43 (parties empty state) — add an empty-state CTA card with a "Partei
  hinzufügen" call.
- F-47 (settings placeholder mixed) — pick all-DE or all-EN; one i18n edit.
- F-50 (mobile bottom-nav overlap) — `<main>` `padding-bottom: var(--bottom-nav-h)`
  at `<768px`.

**Risk:** low. Each change is local; the only cross-cutting bit is the F-13
+ F-42 CSS rename, but the class is referenced in exactly two TS files.

### PR-3 — visual consistency: tabs + chips + Notiz hint 🟡

Single concern: harmonization that touches several pages with one change
each.

**Includes:** F-16, F-20, F-37.

- F-16 (type pill saturated colors) — neutralize to a single chip background
  with the type icon for differentiation; reserve color-as-signal for the
  *Mandant* root (or for `/admin/team` STANDORT). Touches `global.css:4089-4093`
  + `admin-team.tsx` chip render.
- F-20 (tab styling) — collapse the three active-tab rules
  (`.akten-tab.active`, `.login-tab.active`, `.gebuehren-tab.active`) to one
  shared style. Could simply make the two minority rules `@extend` the
  canonical lime-underline + midnight-text + 600-weight pattern.
- F-37 (Notiz textarea hint) — small footer line under the textarea with
  "Strg+Enter zum Speichern" (DE) / "Cmd+Enter to save" (EN).

**Risk:** medium. Tab rule consolidation is the riskiest edit in the
backlog — it touches every `.login-tab` and `.gebuehren-tab` consumer
(login page + Gebührentabellen page). Verify both visually post-edit.
The chip change is visually larger but lower risk because the saturated
colors carry no behaviour.

---

## Top 5 — what m notices first on Monday morning

1. **F-07 — Dashboard activity log English event types.** The activity
   widget is on the landing page, every login. Reading "Test Tester
   project_type_changed" or "Note zu deadline hinzugefügt" makes the app
   feel half-baked in 30 seconds. Sibling F-21 lives on the project Verlauf
   tab (next-most-read). High visibility, medium effort.
2. **F-15 — "Projekt archivieren" red button.** Wrong affordance changes
   user behaviour: real lawyers will hesitate to archive routine matters
   because "red = scary". Trivial fix, biggest behaviour delta.
3. **F-04 — Raw `fristen.field.project.choose` key on /deadlines/new.**
   Visible, plainly broken text in a primary form. One-line fix.
4. **F-12 — AKTE column header on /deadlines and /appointments.** The
   filter dropdown was renamed to "Projekt" in PR-D; the *column header* still
   says "Akte" in the same row. Side-by-side inconsistency screams "rename
   half-done". Trivial.
5. **F-13 — `L-2026-001Siemens AG ./.` collision on /appointments AKTE
   cell.** Looks like a data-corruption bug at first glance. Caused by a
   single CSS class rename that was missed; one-line fix.

(All five fit in PR-1 + PR-2 above. Recommend shipping those two first.)

### Honourable mentions (#6–10)

6. F-46 (Good day → Hello) — one-line warmth fix on EN dashboard.
7. F-29 (empty-state link not real) — feels broken when you click the word
   and nothing happens.
8. F-16 (type pill saturated colors) — calmer chip palette makes /projects
   feel less alarming.
9. F-35 (subtitle taxonomy "Fälle") — small but visible on /projects intro.
10. F-50 (mobile bottom-nav overlap on dashboard) — only mobile, but
    immediately visible to anyone on a phone.

---

## Defer list

Findings where polish-2 isn't the right scope — either they need a design
call, span a redesign-class change, or carry low value-per-effort.

- **F-23** — STATUS column noise. Hiding when single-valued is a usability
  call (some users like the redundancy). Defer to a "table density" pass.
- **F-25** — Mobile tables → card layout. Genuine redesign across four
  pages. Should be its own t-task with screenshots and an alignment
  pass on the mobile pattern. Out of scope for "polish".
- **F-32** — Agenda redundant urgency pill. After the day-bucket redesign,
  the pill *can* still differ from the bucket (e.g. an overdue item under
  HEUTE). Keeping it is defensible; design call before changing.
- **F-38** — Bottom-nav agenda badge semantics. Needs to match the agenda
  redesign decision; tackle there.
- **F-40** — Glossary chip language (EN/DE mix). Product decision (m).
- **F-48** — `/sub-projects` URL alias. The canonical `/children` works;
  guessable-URL-alias is low value. Document the canonical path instead.
- **F-49** — Already DEFER in original audit (meta-circular changelog
  entry).

---

## Recommendation summary

- Ship **PR-1** (i18n leak sweep + activity log narrative) first — biggest
  user-visible delta, contains 3 of the top-5.
- Ship **PR-2** (small visual residue) right after — low-risk, high
  per-edit value, contains the other 2 top-5 items + F-46 / F-50.
- **PR-3** (tab + chip consistency) is worthwhile but riskier; OK to land
  after PR-1/PR-2 stabilize. Leave for later in the week or punt to a
  separate t-task if velocity is constrained.
- **DEFER list** to a later "design-pass" or "mobile-pass" task; do not
  bundle them with these PRs.

## Acceptance

- [x] Every BATCH finding (F-01..F-50) classified KEEP / OBSOLETE /
      RESCOPED / DEFER against current code state.
- [x] Keeps grouped into 3 PR bundles with effort + risk + finding IDs.
- [x] Top 5 ranked with rationale.
- [x] Defer list with reason per item.
- [ ] Head greenlights individual PRs before any coder shift.
