PR-2 of t-paliad-115. The unified Fristen + Termine surface now lives at
/events. Old /deadlines and /appointments list URLs 301-redirect to
/events?type=deadline and /events?type=appointment so existing bookmarks
still land on the right view. Detail pages (/deadlines/{id},
/appointments/{id}) stay type-specific.
Backend (Go).
- New `GET /events` route → handleEventsListPage serves dist/events.html.
- `GET /deadlines` → handleDeadlinesListRedirect (301 → /events?type=deadline).
- `GET /appointments` → handleAppointmentsListRedirect (301 → /events?type=appointment).
- /deadlines/new, /deadlines/calendar, /deadlines/{id}, /appointments/new,
/appointments/calendar, /appointments/{id} unchanged — type-specific
detail / form / legacy-calendar surfaces stay where they are.
Frontend.
- build.ts now emits ONE events.html (not events-deadlines /
events-appointments) with defaultType="all" baked in. The page reads
?type=… and ?view=… on hydration, so /events?type=deadline lands on
the Fristen-only Cards view, /events?view=calendar opens the calendar,
and bare /events shows the Beides view.
- Sidebar Fristen / Termine entries point at /events?type=deadline and
/events?type=appointment. The SSR active-state matches exactly via
href === currentPath, so detail/new/calendar pages that pass
currentPath="/events?type=deadline" (resp. appointment) still
highlight the right entry.
- events.ts hydration adds applySidebarTypeHighlight(): on bare /events
the sidebar SSRs with neither entry lit, and we re-highlight the
matching entry whenever the in-page chip toggle changes the active
type. Sidebar stays in sync without a server round-trip.
- Updated every list-target reference: palette-actions.ts (Cmd-K
navigation), deadlines-detail.ts + appointments-detail.ts (post-delete
redirect), and the back-link / cancel hrefs in the *-new + *-detail +
*-calendar TSX templates. Detail-page Sidebar/BottomNav currentPath
also moved from "/deadlines" → "/events?type=deadline" so the new
highlight contract holds end-to-end.
Out of scope (per task brief).
- A third "Ereignisse / Alle Events" sidebar entry pointing at /events
bare. m's call: keep two entries; defer until signal.
- Removing /deadlines/calendar + /appointments/calendar standalone
pages. The new /events?view=calendar covers the same need but the
legacy URLs stay live for one cycle.
Build clean: `cd frontend && bun run build` + `go build/vet/test ./...`.
PR-1 of t-paliad-115. Separates the view axis (cards / list / calendar)
from the filter axis (event type, status, project) on EventsPage. Closes
the duplicate "Kalenderansicht" button that t-paliad-110 left behind on
the Beides view.
Bug source: frontend/src/events.tsx:53-68 rendered TWO separate
"Kalenderansicht" anchors (events-action-deadline-cal +
events-action-appointment-cal) and frontend/src/client/events.ts:533-534
unhid both when type=all. Reproduced live on paliad.de — screenshot at
.playwright-mcp/paliad-115-duplicate-kalenderansicht-before.png.
Architectural change.
- Removed the per-type calendar buttons from the page header.
- Added a 3-button segmented view selector (Karten / Liste / Kalender)
next to the type chips. The two controls now sit on a shared
events-axis-row that flexes side-by-side and stacks on narrow viewports.
- View state lives in `currentView`, defaults to "cards", reads from
?view= on init, persists to URL on change. Default ("cards") stays
out of the URL so existing bookmarks don't change.
- Cards view = original (5-card summary + table). List view = table
only (cards hidden). Calendar view = month grid; cards + table both
hidden.
Calendar view.
- Plots both deadlines (due_date) and appointments (start_at local
date) on a Mo–So month grid. Reuses the existing .frist-cal-* CSS
scaffold from /deadlines/calendar; only new addition is
.events-cal-dot-appointment for the appointment hue (--bucket-next-week).
- Inherits the page's filters — `?view=calendar&type=appointment` shows
appointment-only; `?view=calendar&status=all` shows everything; etc.
Status, type, project, event-type filters apply orthogonally to view,
matching the spec's "two axes combine" requirement.
- Click a day with items → existing modal pattern lists them with type
chip + project ref; clicking an item navigates to its detail page.
- Month nav (prev / next / today) is purely client-side — no refetch,
cheap pagination over the already-loaded items.
Out of scope (per task brief).
- Standalone /deadlines/calendar + /appointments/calendar pages stay
untouched. PR-2 (URL canonicalisation) handles that surface.
- Custom time-window controls — future iteration per t-110 spec.
Build clean: `cd frontend && bun run build` + `go build/vet/test ./...`.
Spec is explicit that Überfällig is deadline-view-only — only showing
it on type=deadline matches the rationale that 'overdue' is a deadline-
specific concept (appointments don't go overdue, they happen). The
previous logic also surfaced the card on Beides whenever the
deadline-side overdue count > 0, which would have rendered an alarm
card on a mixed view.
Caught during live verification on paliad.de.
PR-4 of the Fristen+Termine unification, closing out t-paliad-110.
Fristen rail (was 5 cards):
- Erledigt card removed (status=completed stays reachable via the EventsPage
filter dropdown — no card on the rail per the new model)
- Später card added (pending deadlines past Mon-week-after, click filters
to /deadlines?status=later)
- 4+1 final shape: Überfällig (conditional alarm) · Heute · Diese Woche ·
Nächste Woche · Später
Termine rail (new): 3 cards — Heute · Diese Woche · Später. No Überfällig
(past appointments aren't urgent), no Nächste Woche (low-value distinction
for appointments per the design rationale). Cards click through to
/appointments?status=… so users land in the matching EventsPage view.
Backend (DashboardService.loadSummary):
- DeadlineSummary.CompletedThisWeek dropped, .Later added
- AppointmentSummary added (Today / ThisWeek / Later)
- One CTE-based query computes both rails alongside MatterSummary; bucket
cutoffs share computeDeadlineBucketBounds with /api/events/summary +
/api/deadlines/summary so all three surfaces stay in lockstep
Frontend:
- dashboard.tsx: Erledigt card removed, Später card + Termine section added
- client/dashboard.ts: types updated, renderAppointmentSummary added
- 4 new i18n keys (DE+EN): dashboard.summary.later +
dashboard.appointment_summary.heading
- CSS: .dashboard-card-later (muted blue) + 3 .dashboard-card-appt-* rules
reusing the existing --bucket-* tokens
go build/vet/test ./... clean. bun run build clean (1396 keys).
PR-3 of the Fristen+Termine unification. Both routes now serve the shared
shell built by renderEvents() — the per-type pages (deadlines.tsx /
appointments.tsx and their client bundles) are deleted entirely.
Hydration is baked at build time, not at request time: build.ts emits
events-deadlines.html and events-appointments.html, each carrying an
inline `window.__PALIAD_EVENTS__={"defaultType":"…"}` script in <head>.
The Go handlers ServeFile the matching artefact, no placeholder swap
needed (cleaner than the dashboard pattern for a single static flag).
Sidebar entries unchanged — "Fristen" still points at /deadlines,
"Termine" at /appointments. Both highlight correctly because each
artefact passes the matching currentPath into <Sidebar />.
Detail / new / calendar pages stay type-specific (out of scope per
task brief). Old endpoints /api/deadlines + /api/appointments remain
live for the calendars, project-detail panes, and CalDAV consumers.
Net: -981 lines (drops the duplicated chrome of the two old pages
in favour of one shared shell).
go build/vet/test ./... clean. bun run build clean.
PR-2 of the Fristen+Termine unification. Pure additive change — the existing
deadlines.tsx + appointments.tsx pages stay live; this PR introduces the new
events.tsx shell + client/events.ts runtime that PR-3 will mount onto the
two routes.
Frontend (new):
- frontend/src/events.tsx — shared shell with the 3-chip type toggle
(Fristen / Termine / Beides), the 5-card summary row (Überfällig
conditional + 4 universal cards), the union filter row, and the unified
table that renders a discriminated row per type. Two header CTAs ("Neue
Frist" + "Neuer Termin") collapse to the relevant one in single-type mode.
- frontend/src/client/events.ts — runtime. Reads window.__PALIAD_EVENTS__
(PR-3 will inject defaultType from the Go handler), derives the rest from
?type query param. Card click sets status filter; the events endpoint
takes care of bucket-aware appointment-side date windowing so both rails
stay in sync in Beides mode. Hide-on-uniform pattern applied per column
(rule, event_type, location, appointment_type, status, row-type chip).
- frontend/build.ts — emits events-deadlines.html + events-appointments.html
from one renderEvents(currentPath) so each output gets the right Sidebar
highlight; client/events.ts bundle added.
- 16 i18n keys (DE+EN): events.toggle.*, events.summary.later,
events.col.*, events.row.type.*, events.empty.*, events.unavailable plus
the new deadlines.summary.later / deadlines.filter.later pair for the
Später bucket.
- CSS: --bucket-later (#1d4ed8 light / #60a5fa dark) for the Später card,
matching events-table--hide-* column hiders, .events-row-type-chip
styling, .event-type-chip-row spacing.
Backend tweaks (small):
- DeadlineFilterLater (`later`): pending deadlines past Mon-week-after.
Click-target for the Später card.
- EventService.ListVisibleForUser now derives an appointment-side date
window from a bucket-style status (today/this_week/next_week/later) so
card clicks filter both rails consistently. Overdue/Completed exclude
appointments entirely (no appointment analogue).
- pickLater / pickEarlier helpers intersect the bucket-derived window with
any caller-supplied from/to.
go build/vet/test ./... clean. bun run build clean (1394 keys, IIFE prologue
guard passes).
PR-1 of the Fristen+Termine unification (t-paliad-110). Backend layer
only — no frontend changes; the existing /deadlines and /appointments
pages still render the type-specific UIs.
EventService delegates to DeadlineService + AppointmentService for the
actual reads (no duplicate visibility logic, no duplicate event_type
hydration), then projects both into the discriminated EventListItem
union and merges/sorts by event_date asc. The handler exposes:
GET /api/events?type=deadline|appointment|all&status=…&project_id=…
&event_type=…&type_filter=…&from=…&to=…
GET /api/events/summary?type=…&project_id=…
Bucket model (per t-paliad-110 spec, supersedes t-106):
- four universal cards: Heute · Diese Woche · Nächste Woche · Später
- Überfällig is deadline-only, conditional, alarm-styled when > 0
- Erledigt drops from the card row; stays available as a filter option
- appointments have no completed_at — past appointments aren't bucketed
The deadline-side cutoffs reuse computeDeadlineBucketBounds so
/api/events/summary and /api/deadlines/summary can never disagree.
Existing /api/deadlines and /api/appointments stay untouched —
calendars, project-detail panes, and CalDAV consumers still call them
directly.
Design doc only — no code touched. Recommends keeping /deadlines + /appointments
URLs but rendering one EventsPage component (smallest-diff Option A1), backed
by a new EventService that delegates to existing Deadline/AppointmentService
(Option B1). Two-rail bucket summary on Beides (5 deadline + 3 appointment),
detail pages stay separate, /agenda timeline left alone. §F lists 17 questions
gating m's greenlight, including a premise correction: the brief described
/agenda as the appointment list — actually it's a pre-existing cross-type
timeline; the appointment list is /appointments.
5-bucket urgency cards (Dashboard "Fristen auf einen Blick" + /deadlines
summary) now share a single source of truth via new --bucket-* tokens:
Überfällig red → Heute orange → Diese Woche yellow → Nächste Woche green → Erledigt grey
Light values pass WCAG AA-large (3:1+) on the white surface for the
colored count (1.6–2.25rem, 600–700 weight = large text):
red #ef4444 · orange #c2410c · yellow #a16207 · green #16a34a · grey #6b7280
Dark mode lifts to bright pastels for readability on midnight:
red #fca5a5 · orange #fb923c · yellow #fbbf24 · green #4ade80 · grey #9ca3af
Both surfaces (.dashboard-card-* and .frist-card-*) now wire through the
same tokens, so the visual hierarchy reads identically. The Überfällig
alarm pulse (saturated red bg + white text, t-paliad-105/106) remains
unchanged. The dashboard-card-green border previously used --color-accent
(brand lime); it now uses --bucket-next-week to match the Fristen page.
The picker on /deadlines/new and /deadlines/{id} edit was search-only —
users had to know what to type. Add a third affordance: a "Alle anzeigen"
button next to search and "+ Neuer Typ" that opens a modal listing every
available event type grouped by category, with sticky search and
multi-select checkboxes pre-populated from the picker's current
selection. Apply replaces; Cancel discards.
Modal a11y: focus-trap (Tab cycles in modal), Esc to close, click-outside
to dismiss. Each row shows the localized label, optional jurisdiction
badge, and a checkbox. Empty-search state renders a muted message.
Side effect: added a base `.modal` rule giving every `<div class="modal">`
a proper card surface (background, padding, radius, shadow). The existing
.event-type-add-modal previously had only width — the latent gap meant
its form fields rendered floating over the dim overlay. Now both modals
share the scaffold.
Files:
- frontend/src/client/event-types.ts — openBrowseEventTypesModal +
browse button on the picker.
- frontend/src/styles/global.css — .modal base + .event-type-browse-*
+ flex-wrap on .event-type-search-row to accommodate three children.
- frontend/src/client/i18n.ts — DE/EN keys: event_types.picker.browse_all,
event_types.browse.{title,search,empty,apply,cancel,selected_count,
jurisdiction.none}.
Out of scope per brief: /deadlines + /agenda multi-select filter (already
has its own browse-style listbox-panel), admin moderation panel,
appointment forms (don't carry event types).
Both surfaces now show the same buckets with the same labels and the same
cutoffs: Überfällig (conditional, alarming) · Heute · Diese Woche ·
Nächste Woche · Erledigt. Single-source bucket math via
computeDeadlineBucketBounds — Heute = today, Diese Woche = tomorrow
through the upcoming Sunday inclusive, Nächste Woche = next Monday
through next Sunday inclusive, all disjoint. Items past next Sunday
are visible only via "All open"/"Upcoming" filters; the Überfällig
card stays hidden when count == 0 and switches to a saturated red
pulse + bold white text when count > 0.
Filter dropdown on /deadlines gains today / next_week entries; old
"upcoming" filter still works as a back-compat alias for everything
pending past this Sunday so legacy bookmarks don't 4xx.
Tests: 8 deterministic table cases for the bucket pivots (every
weekday + a 21-day disjointness walk).
Überfällig is an emergency category — never a normal-state tile. Replace the
prior `.dashboard-card-quiet` dim-but-visible behavior with two states:
- overdue === 0 → card removed from the layout (`*-overdue-hidden`).
The Dashboard summary grid and the Fristen summary cards now use
`repeat(auto-fit, minmax(180px, 1fr))` so the row re-flows to 3 cards
instead of leaving an empty 4th column.
- overdue > 0 → saturated red surface, white text, soft pulsing red ring
via `paliad-alarm-pulse`. Honors `prefers-reduced-motion`. Dark theme
uses a slightly lighter red so the alarm still pops on midnight.
Applied on both surfaces (`#dashboard-card-overdue` and the Fristen
`.frist-summary-card[data-status="overdue"]`).
Reverses the t-paliad-083 carve-out that kept the sidebar on midnight in
both themes. m's feedback: in light mode the dark midnight column read as
a separate panel bolted onto the cream body. The brand lime stripe on the
active item is preserved (decoupled from --sidebar-text-active onto
--color-accent) so the active cue stays anchored across themes.
Light mode: --sidebar-bg = --color-bg-subtle (one step up from body
cream), --sidebar-text/-muted track the body palette, --sidebar-text-active
= midnight (full contrast against muted-grey inactive items),
hover/input/scrollbar tints switch to midnight-channel alphas.
Dark mode: original midnight + cream + lime palette restored inside the
:root[data-theme="dark"] override block. No regression there.
Append a sibling note to the .entity-table contract (t-paliad-099)
explaining that pointer-event overlays for whole-card click break
text selection. Steer future hands at the row-handler pattern from
t-098/099/102/103 instead.
The t-102 ::before overlay (`inset: 0` on `.entity-event-link`) made the whole
card clickable but also captured pointer events on the title and description
text — users couldn't select-to-copy. Same trap noted in brunel's t-102
debrief: when text-selection matters, switch to a row-level click handler that
skips inner <a>/<button>, matching the .entity-table pattern from t-098/099.
CSS (global.css):
- drop `.entity-event-link::before` overlay
- drop `position: relative` from `.entity-event` and `position: static` from
`.entity-event-link` (no longer anchoring an absolute pseudo-element)
- keep cursor: pointer + hover-lift on `.entity-event:has(.entity-event-link)`
so the affordance still telegraphs "clickable"
- card hover-lift now keys off the card itself, not the link's :hover, so the
lift triggers from anywhere on the card (matching the new click surface)
- mirror `.dashboard-activity-item`: cursor + lime-tint hover row-highlight
TS:
- projects-detail.ts:renderEvents: after innerHTML, attach a row-level click
handler that reads `.entity-event-link.href` and skips clicks on inner
<a>/<button>. Cards without a link have no `.entity-event-link` and stay
non-clickable (cursor stays default via the `:has()` selector).
- dashboard.ts:renderActivity: same handler reading `.dashboard-activity-project.href`.
Acceptance:
- Title and description text on /projects/{id} → Verlauf cards and on
/dashboard activity rows is selectable again.
- Click anywhere on a card still navigates (no regression from t-102).
- Title link still navigates; Cmd-/Ctrl-click opens in new tab; keyboard
tabbing still hits the inner link.
- `cd frontend && bun run build` clean; `go build/vet/test ./...` clean.
Extends the t-paliad-097 metadata pattern from checklist_* events to the
remaining audit families. Project Verlauf and Dashboard activity feed now
deep-link each event to its originating entity:
- deadline_{created,updated,completed,reopened} → /deadlines/{id}
- appointment_{created,updated} → /appointments/{id}
- note_created → /appointments/{id} | /deadlines/{id} | /projects/{id}
(most-specific parent — notes have no standalone page)
Backend (Go):
- deadline_service.go / appointment_service.go: switch single-entity
mutation events from insertProjectEvent to insertProjectEventWithMeta
carrying {"deadline_id"|"appointment_id": uuid}.
- note_service.go:insertWithAudit: derive metadata from noteParent so
the audit row records {note_id, deadline_id|appointment_id|project_id}.
Frontend (TS):
- projects-detail.ts: extract eventDetailHref(); wrapEventTitleLink
delegates to it. Comment block lists every wired event family.
- dashboard.ts:activityHref: same routing rules as the project Verlauf.
- global.css: .entity-event becomes position:relative; the
.entity-event-link::before pseudo expands the link's hit area to the
full card so a click anywhere on the row navigates (matches what m
expected from "die Karte ist verlinkt"). Hover lifts border + shadow.
Excluded by design (mirrors checklist_deleted exclusion):
- *_deleted events — entity is gone.
- deadlines_imported — bulk event with no single deadline_id; would
need an aggregate target the product doesn't have today.
Pre-metadata rows stay non-clickable (no backfill — same precedent as
t-paliad-097).
m's call: most of yesterday's backfill entries weren't worth surfacing.
Removed: Checklisten von überall öffnen, Admin-Verwaltung der Event-Typen,
Mehr Verfahrensarten im Fristenrechner, Dunkler Modus, Rollen-Filter im
Team-Verzeichnis, Checkboxen in Formularen ausgerichtet.
Kept: Event-Typen für Fristen (2026-04-30, Feature) and UPC-Fristen
genauer berechnet (2026-04-30, Fix) — both have direct user impact on
the deadline workflow.
Eight new entries cover the user-facing work landed since the 2026-04-20
Settings entry: dark mode, /team Role filter, event types + admin
moderation panel, UPC RoP fixes, Tier 2 Fristenrechner ports, checklist
"Vorhandene Instanzen" tab, and the checkbox row-alignment fix.
Folded as siblings (per task hint): t-paliad-082 light-mode contrast
into the dark-mode entry; t-paliad-098 row-click into the t-paliad-097
checklist entry — both are small fixes on the same surface as their
sibling feature.
Internal/refactor merges in the window were skipped (t-080/091/092/093/
095/099 plus doc/dead-code/audit/smoke merges).
Tests: go test ./internal/changelog/... green (date-desc invariant
still holds). go build ./... + go vet ./... + bun run build clean.
Anchor the convention surfaced by t-paliad-098/099 so the next
hand on .entity-table sees it before adding a new table:
- frontend/src/styles/global.css: contract comment block above the
default cursor:pointer rule explaining the navigate-or-readonly choice
- .claude/CLAUDE.md: new "Frontend conventions" section pointing at
the CSS and the row-handler pattern in client/checklists.ts +
client/projects-detail.ts
No code changes; pure docs.
Audit follow-up to t-paliad-098. The global `.entity-table tbody tr`
rule set `cursor: pointer` and a hover background on every row, but six
tables across the admin and settings surfaces don't navigate on row
click — actions live in inline buttons (admin-team, admin-event-types,
admin-partner-units) or the rows are pure read-only summaries (admin
audit log, CalDAV sync log). The cursor lied and the hover invite was
empty.
- Add `.entity-table--readonly` modifier in global.css that resets
cursor and neutralises the hover background, including a dark-theme
override since the existing `:root[data-theme="dark"] .entity-table
tbody tr:hover` rule outranks the base modifier on specificity.
- Apply the modifier to the six table instances that don't navigate.
The eight tables that DO navigate (projects, deadlines, appointments,
checklists templates+instances, project-detail's deadlines/appointments
/checklists) already have row click handlers and keep the default
clickable affordance.
The .entity-table tbody tr CSS rule sets cursor: pointer on every row,
but the three checklist-instance tables only wired navigation to the
name cell's <a>. Clicks on Vorlage / Fortschritt / Angelegt cells looked
clickable yet did nothing.
Added row-level click handlers (skipping inner <a>/button so the
project-link cell and delete button still work) in:
- checklists.ts (Vorhandene Instanzen tab)
- projects-detail.ts (Checklisten tab on project detail)
- checklists-detail.ts (instance list on template detail)
Search-result anchors (handlers/search.go) already worked since they
are real <a> elements, no row handler needed.
Two related checklist UX gaps:
1. Checklist events in a project's Verlauf tab were unclickable — and
nothing in the project_events row carried the originating instance ID.
Add an `insertProjectEventWithMeta` helper, write
{"checklist_instance_id": <uuid>} as project_events.metadata for
checklist_created / _renamed / _linked / _unlinked / _reset (skipped
for _deleted — instance is gone). Surface metadata on
/api/projects/{id}/events and on dashboard recent_activity. The
Verlauf renderer wraps the title in <a href="/checklists/instances/{id}">
when metadata.checklist_instance_id is present, and the dashboard's
activity feed deep-links the project ref to the instance directly for
checklist_* events. Existing rows (metadata `{}`) stay non-clickable —
no migration backfill needed.
2. /checklists previously demanded a template pick before any existing
instance was reachable. Add a tab nav (Vorlagen / Vorhandene Instanzen)
using the existing entity-tab pattern. New endpoint
GET /api/checklist-instances and ChecklistInstanceService.ListAllVisible
return every visible instance across templates + projects, joined with
project ref/title and sorted by created_at DESC. Rows show template,
instance name (linked), project link (or "Persönlich"), progress bar,
and created date. URL state (?tab=instances) keeps the active tab
shareable. EN + DE i18n covered for tab labels and column headers.
Also adds event.title.checklist_* localizations for the Verlauf header
that translateEvent looks up.
`.form-field input` set `width: 100%` and `padding: 0.55rem 0.75rem` on
all inputs — that includes checkboxes and radios. The visual checkbox
visibly stretched to fill the form column on /settings (Benachrichtigungen
tab) and inside the Fristenrechner "Fristen übernehmen" save modal,
pushing the label text out of place.
Add a targeted override that restores natural sizing for type=checkbox
and type=radio inside `.form-field`. Also bump `.caldav-toggle-label`
specificity (selector → `label.caldav-toggle-label`) so its
`inline-flex; align-items: center; gap: 0.5rem` actually wins over the
generic `.form-field label { display: block }` rule — without that the
checkbox + label kiss with no gap.
Surfaces verified via Playwright on paliad.de:
- /settings?tab=benachrichtigungen — Frist-Erinnerungen master + 3 sub-toggles
- /tools/fristenrechner — "Fristen übernehmen" save modal rows
F-7 of the t-paliad-074 architecture audit. Sweeps the last German-named
CSS leftovers — purely a class-name change, no behaviour or styling
delta. 466 references across global.css and ~30 TSX/TS files.
Naming rules applied:
- Generic table/tabs/form/empty/controls/detail/events/status/type/
suggestion/chip/col/ref/search-wrap/select/soon/loading/muted/
unavailable/row/header-row/title-input -> .entity-*
- Truly generic widgets dropped the prefix: .multi-* (multi-select
panel), .filter-*, .collab-* (collaborator picker; bare class is
now .collab-picker), .firmwide-*, .office-*, .back-link
- Project-specific names kept specific: .party-form/-controls/-table
(parties on a project), .checklists-hint, .netdocs-link
- Page-scoped IDs in projects.tsx -> projects-search/-count/-body;
projects-new.tsx -> project-new-form/-msg
- German content "akten-bezogen" tightened to "aktenbezogen" (one-word
form is also valid German) so the strict grep stays clean
F-6 from t-paliad-074 architecture audit. The Gitea repo was renamed
m/patholo → mAi/paliad → m/paliad, but go.mod still declared
`mgit.msbls.de/m/patholo` and every internal import echoed the
pre-rebrand name.
Sweep:
- go.mod: module path → mgit.msbls.de/m/paliad
- All *.go files: imports rewritten via sed
- README.md, docs/design-kanzlai-integration.md: mAi/paliad → m/paliad
- Frontend issue-reference comments (mAi/paliad#N → m/paliad#N) in
i18n.ts, theme.ts, sidebar.ts, app.ts, Sidebar.tsx, PWAHead.tsx,
global.css
Verified: go build/vet/test ./... clean, bun run build clean,
no remaining mgit.msbls.de/m/patholo or mAi/paliad references
outside docs that intentionally describe the rename history.
Per audit recommendations §6.2 (rec 5-9). Ports 14 RoP rules from
youpc's deadline calc into paliad.deadline_rules so they're surfaceable
in the timeline (course-of-proceedings) Fristenrechner mode in addition
to the existing trigger-event mode.
Adds 4 new proceeding types and extends 2 existing trees:
- UPC_DAMAGES (new) — Schadensbemessung (R.137.2, R.139 reply, R.139 rejoin)
- UPC_DISCOVERY (new) — Bucheinsicht (R.142.2, R.142.3 reply, R.142.3 rejoin)
- UPC_COST_APPEAL (new) — Berufung Kostenentscheidung (R.221.1)
- UPC_APP_ORDERS (new) — 15-day order-flavor (R.220.2, R.220.3, R.237 b, R.238.2)
- UPC_INF extended — R.151 chained off inf.decision
- UPC_APP extended — decision-flavor cross-appeal pair (R.237 a, R.238.1)
Total: 14 RoP rules across 5 families. New types appear in the
proceeding-type picker via deadlines.upc_damages / upc_discovery /
upc_cost_appeal / upc_app_orders i18n keys (DE + EN).
Design notes in the migration explain why some rules live in their own
proceeding type (when the legal anchor differs from UPC_INF/UPC_APP's
trigger date) vs being chained off existing rules.
Q6 of t-paliad-088 left firm-wide event_type creation open to any user; this
ships the moderation surface admins use to dedupe and clean up the resulting
drift.
Service layer (internal/services/event_type_service.go):
- ListAllForAdmin(filter) — firm-wide rows with usage_count and
author_display_name, optionally including archived (single query, scalar
subquery + LEFT JOIN paliad.users). Sorted live-first, then category +
label_de.
- ListPrivatePendingPromotion — every private non-archived row across all
users, sorted by usage_count DESC.
- ArchiveBulk(ids) — UPDATE archived_at=now() WHERE is_firm_wide AND NULL.
- Promote(id) — flip is_firm_wide=true; surfaces ErrEventTypeSlugTaken on
collision so the admin can merge instead.
- Restore(id) — flip archived_at back to NULL; same slug-collision surface.
- MergeIDs(winner, losers) — tx-scoped INSERT … SELECT … ON CONFLICT
redirect of deadline_event_types from losers → winner, then DELETE on the
loser junction rows, then archive the losers. Refuses if the winner is
archived or private. Junction PK does the dedup.
- requireAdmin gate runs at every method (defence-in-depth on top of the
handler-level RequireAdminFunc).
Handlers (internal/handlers/admin_event_types.go):
- GET /api/admin/event-types[?include_archived=1]
- GET /api/admin/event-types/private
- POST /api/admin/event-types/archive { ids:[…] }
- POST /api/admin/event-types/merge { winner_id, loser_ids:[…] }
- POST /api/admin/event-types/{id}/promote
- POST /api/admin/event-types/{id}/restore
- GET /admin/event-types page shell.
All wrapped behind auth.RequireAdminFunc at registration time.
Frontend:
- New /admin/event-types SPA (admin-event-types.tsx + client/admin-event-types.ts):
search, "Archivierte anzeigen" toggle, per-row archive/restore, bulk
archive, merge modal (winner picker defaults to highest-usage row),
separate table for private types pending promotion.
- Sidebar entry under Verwaltung; admin landing card.
- ~50 i18n keys DE+EN under admin.event_types.* + nav.admin.event_types.
- CSS for archived badge, merge option list, bulk-actions bar.
Out of scope (deferred): public "merge request" workflow for non-admins.