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 ./...`.
paliad
Paliad — all-in-one patent practice platform for HLC (formerly Hogan Lovells). Knowledge tools and Aktenverwaltung behind one sidebar.
- Aktenverwaltung: Akten (matters), Fristen (deadlines), Termine (appointments) with CalDAV sync, Parteien, Dashboard. Office-scoped visibility with explicit collaborators.
- Knowledge tools: Prozesskostenrechner (DE / UPC / EPA), Fristenrechner, Gebührentabellen, Patentglossar, Gerichtsverzeichnis, Checklisten, Link Hub, Downloads.
Domain: paliad.de (legacy: patholo.de, patholo.msbls.de).
Repo: m/paliad on mgit.msbls.de.
Stack
- Frontend: Bun + custom JSX/TSX renderer (no React), per-page client TS bundles, HTML-first forms
- Backend: Go (
net/http),sqlxfor DB access - Migrations:
golang-migrate/migrate/v4with SQL files embedded viaembed.FS; applied at server startup before the HTTP listener binds - Database: youpc Supabase Postgres,
paliadschema. Office-scoped RLS (paliad.can_see_akte(akte_id)) — seedocs/design-kanzlai-integration.md§2 - Auth: Supabase password (cookie session,
@hoganlovells.com/@hlc.*email gate) - CalDAV: hand-rolled iCal + minimal WebDAV client in
internal/services/caldav_*.go; AES-GCM at rest for stored passwords - Hosting: Dokploy compose
Zx147ycurfYagKRl_Zzyoon mlake
Database migrations
Migrations live in internal/db/migrations/ as NNN_description.up.sql + .down.sql pairs. They are embedded into the Go binary via embed.FS and applied automatically at server startup (before the HTTP listener binds) when DATABASE_URL is set.
The migration tracker is paliad.paliad_schema_migrations (not the default public.schema_migrations). This avoids a collision with other apps on the shared youpc Supabase instance — see the memory episode "paliad migration bootstrap collision with shared Postgres" for the incident that drove the change.
Current migrations (as of April 2026):
001_paliad_schema schema + extensions
002_users paliad.users (office, role, practice_group)
003_reference_tables proceeding_types, deadline_rules, holidays
004_akten paliad.akten with visibility columns
005_akten_children parteien, fristen, termine, dokumente, akten_events, notizen
006_visibility paliad.can_see_akte() function
007_rls_policies RLS on every paliad table
008_seed_proceeding_types
009_seed_deadline_rules 32 UPC + 4 ZPO rules
010_seed_holidays DE federal + UPC judicial vacations
011_feedback_tables link_suggestions, checklisten_feedback, gerichte_feedback
012_fristenrechner_rules DB-backed rule set for /tools/fristenrechner
013_user_caldav_config per-user CalDAV (encrypted) + sync log
014_checklist_instances persisted checklist instances linkable to Akten
Add a new migration:
internal/db/migrations/015_<description>.up.sql
internal/db/migrations/015_<description>.down.sql
The down file is required and must reverse the up cleanly (verified by adding a one-off down test before merge).
To run migrations against a local Postgres:
docker run -d --name paliad-pg -e POSTGRES_PASSWORD=test -p 5432:5432 postgres:16-alpine
# bootstrap a mock auth schema (auth.users + auth.uid()) — required because
# the migrations reference Supabase-provided objects:
psql postgres://postgres:test@localhost:5432/postgres -f internal/db/devtools/mock_supabase_auth.sql
DATABASE_URL='postgres://postgres:test@localhost:5432/postgres?sslmode=disable' \
SUPABASE_URL=stub SUPABASE_ANON_KEY=stub \
go run ./cmd/server
Environment
| Variable | Required | Purpose |
|---|---|---|
PORT |
no (default 8080) | HTTP listen port |
SUPABASE_URL |
yes | Supabase project URL (auth) |
SUPABASE_ANON_KEY |
yes | Supabase anon key (auth) |
DATABASE_URL |
for Aktenverwaltung | Direct Postgres conn for migrations + Akten/Fristen/Termine services. Knowledge-platform endpoints (Kostenrechner, Glossar, Links, Gebührentabellen, Checklisten, Gerichte, Downloads) don't use the pool and work without it. Aktenverwaltung endpoints return 503 if unset. |
CALDAV_ENCRYPTION_KEY |
for CalDAV sync | 32-byte AES-256 key, base64-encoded. Encrypts CalDAV passwords at rest (AES-GCM). Server fails fast on malformed key; if unset, CalDAV is silently disabled (/api/caldav-config returns 501). Generate with openssl rand -base64 32. |
GITEA_TOKEN |
optional | Gitea API token for the private file proxy (Downloads) |
ANTHROPIC_API_KEY |
not used today | Reserved for Phase H (AI Frist-Extraktion). Currently deferred — do not set. |
Development
make build # compile backend + frontend
make test # run Go tests + frontend tests
go build ./... # backend only
go vet ./... # static checks
go test ./... # Go tests
bun run build # frontend only (produces frontend/dist/)
Project layout:
cmd/server/ # main entry point
internal/db/ # sqlx pool + embedded migrations
internal/services/ # AkteService, FristService, TerminService, CalDAV, ...
internal/handlers/ # HTTP handlers (pages + API)
internal/calc/ # Kostenrechner / Fristenrechner logic
frontend/ # Bun + TSX source; static HTML output to frontend/dist/
docs/ # design docs + this roadmap
Deploy
Push to main → Gitea webhook → Dokploy auto-deploy on mlake.
Project status (April 2026)
Phases A–G, I and J of the KanzlAI integration are shipped: schema, services, Akten, Fristen, Termine + CalDAV, Dashboard, Notizen service + UI (commit 5a9f8e5, 2026-04-17), and instanceable Checklisten (migration 014). Phase H (AI Frist extraction) is deferred pending a reversal of the "no Anthropic API" decision; the Dokumente tab on Akten detail is hidden until that lands. KanzlAI infra retirement (Dokploy shutdown, kanzlai schema drop, Gitea archive) is still pending.
See docs/feature-roadmap.md for the full backlog and docs/design-kanzlai-integration.md for the integration design.