Commit Graph

359 Commits

Author SHA1 Message Date
m
56522adffe feat(t-paliad-115): canonicalise list URL on /events; redirect old paths
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 ./...`.
2026-05-04 14:40:53 +02:00
m
1dad1c7371 feat(t-paliad-115): events view ⊥ filter — view selector + unified calendar view
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 ./...`.
2026-05-04 14:34:44 +02:00
m
7463831932 Merge: t-paliad-109 — design doc for Fristen/Termine unification 2026-05-04 14:23:01 +02:00
m
165f0c1717 Merge: t-paliad-110 fix — hide Überfällig on Beides view 2026-05-04 13:57:47 +02:00
m
82421b3c86 fix(t-paliad-110): hide Überfällig card on Beides view
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.
2026-05-04 13:57:40 +02:00
m
b2dfc87f57 Merge: t-paliad-110 PR-4 — Dashboard Termine rail + Fristen rail refactor 2026-05-04 13:52:56 +02:00
m
57237a55a3 feat(t-paliad-110): refactor Dashboard rails — drop Erledigt card, add Später + Termine rail
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).
2026-05-04 13:52:49 +02:00
m
88af8d3487 Merge: t-paliad-110 PR-3 — mount EventsPage on /deadlines + /appointments 2026-05-04 13:48:58 +02:00
m
50ac065c7d feat(t-paliad-110): mount unified EventsPage on /deadlines + /appointments
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.
2026-05-04 13:48:53 +02:00
m
285e97203a Merge: t-paliad-110 PR-2 — shared EventsPage component 2026-05-04 13:46:38 +02:00
m
fe9c1b7de2 feat(t-paliad-110): add shared EventsPage component + bucket-aware backend tweaks
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).
2026-05-04 13:46:33 +02:00
m
49eb3c97e1 Merge: t-paliad-110 PR-1 — backend EventService + /api/events 2026-05-04 13:37:36 +02:00
m
2102dfd07d feat(t-paliad-110): add EventService + /api/events + /api/events/summary
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.
2026-05-04 13:37:20 +02:00
m
25efce0c76 design(t-paliad-109): unify Fristen + Termine as filtered Events views
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.
2026-05-04 13:14:52 +02:00
m
1def9e86b9 Merge: t-paliad-108 — bucket card colors (Heute dark-orange, Diese Woche yellow) 2026-05-04 13:02:21 +02:00
m
db09f6e7d5 feat(t-paliad-108): bucket card palette — Heute orange, Diese Woche yellow
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.
2026-05-04 13:02:12 +02:00
m
99af714d65 Merge: t-paliad-107 — event-type picker browse-all modal 2026-05-04 12:06:26 +02:00
m
de03f3ddcb feat(t-paliad-107): add Alle anzeigen browse-all modal to event-type picker
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).
2026-05-04 12:06:00 +02:00
m
d19e35bfaf Merge: t-paliad-106 — Deadline summary 5-bucket harmonization 2026-05-04 12:04:20 +02:00
m
37a925d3b2 feat(t-paliad-106): harmonize deadline summary — 5 disjoint buckets across Dashboard + Fristen
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).
2026-05-04 12:03:56 +02:00
m
16eb73bf44 Merge: t-paliad-105 — Überfällig hide-when-zero / alarm-when-nonzero 2026-05-04 11:52:45 +02:00
m
2bbbe562d7 feat(t-paliad-105): hide Überfällig card at zero, alarm at >0
Ü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"]`).
2026-05-04 11:52:35 +02:00
m
102d0168e9 Merge: t-paliad-104 — harmonize light-theme sidebar with cream body 2026-05-04 11:51:46 +02:00
m
e6369bc4c2 feat(t-paliad-104): harmonize light-theme sidebar with cream body
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.
2026-05-04 11:51:40 +02:00
m
1a815979f8 docs(t-paliad-103): warn against ::before block-link overlays
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.
2026-05-04 10:54:09 +02:00
m
8a9b9c6611 Merge: t-paliad-103 — replace Verlauf ::before with row-click for text-selectable cards 2026-05-04 10:34:54 +02:00
m
73d108d878 fix(t-paliad-103): replace Verlauf ::before block-link with row-click handler
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.
2026-05-04 10:34:45 +02:00
m
0081749f69 Merge: t-paliad-102 — link Verlauf entries to deadlines/appointments/notes 2026-05-03 18:39:16 +02:00
m
95a6df5b49 feat(t-paliad-102): link Verlauf entries to deadlines/appointments/notes
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).
2026-05-03 18:39:06 +02:00
m
f51bce3342 Merge: t-paliad-100 trim — keep only Event-Typen + UPC-Fristen entries 2026-05-03 13:09:41 +02:00
m
268695d83f revert(t-paliad-100): trim changelog backfill — keep only Event-Typen + UPC-Fristen
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.
2026-05-03 13:09:37 +02:00
m
e436abd631 Merge: t-paliad-100 — backfill changelog from git history (2026-04-21 → 2026-05-01) 2026-05-03 13:01:26 +02:00
m
a020c1e4c8 feat(t-paliad-100): backfill changelog 2026-04-21 → 2026-05-01
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.
2026-05-03 13:01:17 +02:00
m
2d46a86c50 docs(t-paliad-099): document .entity-table row-click contract
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.
2026-05-02 11:47:57 +02:00
m
95cedebea7 Merge: t-paliad-099 — entity-table cursor matches behavior across all consumers 2026-05-02 11:17:11 +02:00
m
19e4bfacfb fix(t-paliad-099): cursor only invites click where rows actually navigate
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.
2026-05-02 11:17:00 +02:00
m
8f7f53b4c8 Merge: t-paliad-098 — checklist row click now navigates from any cell 2026-05-01 10:55:36 +02:00
m
215d4a465b fix(t-paliad-098): row-level click navigates to checklist instance
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.
2026-05-01 10:55:30 +02:00
m
a5a05b1a66 Merge: t-paliad-097 — open checklists from anywhere + Vorhandene Instanzen tab 2026-05-01 09:48:35 +02:00
m
df321acb63 feat(t-paliad-097): clickable checklist references + Vorhandene Instanzen tab
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.
2026-05-01 09:48:25 +02:00
m
a05002299d Merge: t-paliad-096 — fix checkbox row-alignment across forms/lists/modals 2026-04-30 21:45:23 +02:00
m
fd6517d53a fix(t-paliad-096): checkbox row-alignment in form-field contexts
`.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
2026-04-30 21:45:09 +02:00
m
d14c8111eb Merge: t-paliad-093 — CSS class rename .akten-* → English equivalents (F-7 of t-074) 2026-04-30 16:52:25 +02:00
m
c6872f94b0 refactor(t-paliad-093): rename .akten-* CSS classes to English equivalents
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
2026-04-30 16:52:10 +02:00
m
47af52d7ea Merge: t-paliad-092 — rename Go module path mgit.msbls.de/m/patholo → mgit.msbls.de/m/paliad 2026-04-30 16:47:30 +02:00
m
460736ad1e refactor(t-paliad-092): rename Go module path patholo → paliad
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.
2026-04-30 16:46:31 +02:00
m
bbd46f658b Merge: t-paliad-089 — Admin Event-Type moderation panel (bulk archive, merge, promote, restore) 2026-04-30 16:43:09 +02:00
m
4ddab75493 Merge: t-paliad-094 — Tier 2 Fristenrechner rule ports (14 rules across 5 families) 2026-04-30 16:41:56 +02:00
m
90b2f935c2 feat(t-paliad-094): Tier 2 Fristenrechner rule ports — damages, cost-appeals, cross-appeal, lay-open, leave/discretion
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.
2026-04-30 16:41:38 +02:00
m
fca7143244 feat(t-paliad-089): admin Event-Type moderation panel
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.
2026-04-30 16:37:48 +02:00