From 1150bcbe6c82cef3a63a5eb7ed3da5548c1c30bd Mon Sep 17 00:00:00 2001 From: m Date: Thu, 16 Apr 2026 13:11:14 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20add=20KanzlAI=20=E2=86=92=20Paliad=20in?= =?UTF-8?q?tegration=20design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strategic pivot: Paliad becomes all-in-one platform for HLC patent practice (knowledge base + matter management). Design doc covers all 8 items from t-paliad-001: - Integration strategy: full merge into Paliad's stack (single Go binary, single Supabase schema, single Dokploy deployment) - Auth: drop multi-tenancy entirely (HLC is one tenant) - Schema: paliad.* schema absorbs kanzlai.*; matters renames cases - Feature prioritization: port deadlines/matters/appointments/CalDAV/ dashboard/AI/notes; drop billing/RVG/multi-tenant infrastructure - UI: single sidebar with 5 grouped sections; lime branding stays - Deployment: kanzlai.msbls.de → 301 to paliad.de, retire KanzlAI app - Roadmap update: rewrite "What patholo Is NOT" section, drop §2.3 UPC Rechtsprechung (link to youpc.org), add Phase 0 matter mgmt - Migration plan: 10 phases, ~52h coder time, ~2-3 weeks - Risks: KanzlAI audit issues must be fixed during port (not carried forward); React → Bun TSX rewrite needs HTML-first discipline --- docs/design-kanzlai-integration.md | 547 +++++++++++++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100644 docs/design-kanzlai-integration.md diff --git a/docs/design-kanzlai-integration.md b/docs/design-kanzlai-integration.md new file mode 100644 index 0000000..d1d300b --- /dev/null +++ b/docs/design-kanzlai-integration.md @@ -0,0 +1,547 @@ +# KanzlAI → Paliad Integration + +**Author:** cronus (inventor) +**Date:** 2026-04-16 +**Task:** t-paliad-001 +**Status:** Design draft for review + +--- + +## Executive Summary + +**Recommendation: Full merge into a single Paliad codebase, single Go binary, single Supabase schema, single Dokploy deployment.** Port KanzlAI's domain logic (deadline rules, calculator, holiday service, matter/deadline/appointment/notes models, CalDAV sync, AI extraction) into Paliad's existing Go backend. Rewrite KanzlAI's React+Next.js frontend in Paliad's Bun + TSX stack. Retire `kanzlai.msbls.de` to a 301 redirect once parity is reached. + +**Why this path.** Two backends behind one domain doubles operational surface for zero user benefit. The two products share an audience (HLC patent practice), a stack philosophy (Go + Supabase), and an auth model (Supabase password). The only real cost is rewriting the React frontend — which is a smaller burden than it sounds, because KanzlAI's UI is mostly forms, lists, calendar grids, and tabbed details, all of which fit Paliad's server-rendered TSX + per-page client JS pattern. The merger from HL → HLC is the right moment to consolidate: one platform, one URL, one mental model. Knowledge tools and matter management belong together — a lawyer who looks up a UPC fee on Paliad should also see their next UPC deadline. + +--- + +## 1. Integration strategy + +### Decision: Full merge into Paliad's stack + +Four options were considered: + +| Option | Backend | Frontend | UI | Verdict | +|---|---|---|---|---| +| **A. Run both apps behind unified domain** | 2 binaries (Go + Go) | 2 stacks (Bun TSX + Next) | 2 sidebars, reverse-proxy split | Doubles ops; jarring UX seam between `/` and `/app/*` | +| **B. Port frontend only, keep backends separate** | 2 binaries | 1 stack (Bun TSX) | 1 sidebar, 2 API clients | Eliminates UI seam but keeps deploy/schema/auth split | +| **C. Full merge — port KanzlAI into Paliad** | 1 binary | 1 stack (Bun TSX) | 1 sidebar | Higher upfront cost, lowest ongoing cost ✅ | +| **D. Incremental — start with deadlines, then add others** | Phased | Phased | Phased | This is the *implementation* of C, not an alternative | + +**Pick C, execute as D.** Option C is the destination; the phased migration plan in §8 *is* the incremental path. + +### Why not keep KanzlAI as-is and link to it? + +- KanzlAI has unfixed critical security issues (`AUDIT.md` §1.1 tenant isolation bypass, §1.3 plaintext CalDAV creds, §1.4 no CORS, §1.6 race condition in HolidayService). Shipping it under the HLC name without fixing those is malpractice. Fixing them requires a full pass anyway — better to do the fixes inside Paliad than touch a deprecated stack. +- React + Next.js + Tailwind v4 + react-query is too much machinery for Paliad's profile (small content + tools site). Maintaining two frontends is a tax we don't want. +- KanzlAI has zero production users today (per audit). Now is the cheapest possible moment to consolidate. + +### Trade-off accepted + +- **~6 weeks of focused implementation** (estimated 52h coder time, see §8) to retire ~16,500 lines of KanzlAI code and rebuild ~4,000 lines on Paliad's stack. +- **Lose React ecosystem** (react-query, react-day-picker, etc.). Paliad's stack handles these patterns with HTML-first forms + small per-page TS bundles. Acceptable. + +--- + +## 2. Auth & multi-tenancy + +### Decision: HLC is one tenant. Drop multi-tenancy entirely. + +KanzlAI was built multi-tenant in case it became a SaaS product. That bet is now dead — Paliad serves one firm. Multi-tenancy adds: +- A whole `tenants` + `user_tenants` table layer +- Tenant resolution middleware (with the tenant isolation bypass bug) +- `tenant_id` on every row + every query +- RLS policies that key off tenant membership + +For one firm with one matter pool, this is pure overhead. **Strip it.** + +### Concrete model + +- **Auth gate**: keep the email domain whitelist on registration. Accept `@hoganlovells.com` AND `@hlc.com` (any TLD HLC ends up using). Whitelist is a config slice, not a hardcoded constant. +- **Single shared visibility**: every authenticated user sees every matter/deadline/appointment by default. No office-scoped or practice-group-scoped filtering at the schema level. +- **`paliad.users` table**: extends `auth.users`, adds `office`, `practice_group`, `role` (`partner` | `associate` | `pa` | `admin`). Used for display, attribution, and future ACL — not for access control on day one. +- **RLS posture**: enable RLS on all tables, but with permissive policies (`USING (auth.role() = 'authenticated')`). RLS-on with permissive policies is cheap insurance against future tenant requirements and against forgotten WHERE clauses. +- **Audit trail**: every mutation writes a `case_event` (or equivalent) with `created_by = auth.uid()`. So we have *attribution* even though we don't have *isolation*. + +### When the model needs to change + +If HLC ever decides "Munich shouldn't see Düsseldorf matters" (unlikely — HLC is a single firm with cross-office teams), we add an `acl` JSONB column or a `matter_visibility` table and tighten the RLS policy. Don't build that now. + +### Post-merger email transition + +- Phase 1 (now → merger): whitelist `[@hoganlovells.com, @hlc.com, @hlc.de]`. Any new HLC subdomain TLD added to the slice in env config. +- Phase 2 (post-merger): when HLC mandates a new email, existing `@hoganlovells.com` Supabase identities continue to work. New users register under `@hlc.*`. No data migration needed because Supabase keys on UUID, not email. +- Phase 3 (cleanup, +1 year): optional script to update display emails in `paliad.users` if HLC actually retires the old domain. + +--- + +## 3. Database / schema + +### Decision: Single `paliad` schema in the youpc Supabase instance. Migrate `kanzlai` tables in. + +KanzlAI's schema lives in `kanzlai.*` on the youpc Supabase instance. Paliad currently has 3 feedback tables (`link_suggestions`, `checklisten_feedback`, `gerichte_feedback`) that look like they're in the public schema (no schema prefix in `docs/migrations/00*.sql`). + +**Plan:** +1. Create `paliad` schema in the same youpc Supabase instance (no new project, no DB migration). +2. Move Paliad's 3 existing feedback tables into `paliad.*` (CREATE in new schema, COPY data, drop old). +3. Port KanzlAI's tables into `paliad.*`, dropping multi-tenancy fields: + - `cases` → `paliad.matters` (rename: "matter" is universal across DE/EN; "Akte" stays the German display label) + - `parties`, `deadlines`, `appointments`, `documents`, `case_events`, `notes` → `paliad.*`, drop `tenant_id`, keep `created_by` (FK to `auth.users.id`) + - Reference tables: `proceeding_types`, `deadline_rules`, `holidays` → `paliad.*` (these are pure reference data, no tenancy concept needed) +4. RLS enabled on all paliad tables with permissive `authenticated` policies (per §2). +5. Retire `kanzlai.*` schema once cutover is verified (`DROP SCHEMA kanzlai CASCADE` in Phase J). + +### Reuse vs. port + +- **Reuse the data**: KanzlAI's `deadline_rules` table has 32 UPC + 4 ZPO rules (more than Paliad's hardcoded `internal/calc/deadline_rules.go`). Port the data, drop Paliad's hardcoded rules. +- **Reuse the holiday data**: KanzlAI's `holidays` table has DE federal + UPC judicial vacations 2026. Port and extend. +- **Don't reuse**: KanzlAI's `tenants`, `user_tenants`, `billing_rates`, `invoices`, `time_entries` (the latter three are aspirational — handler stubs exist but no real implementation). Drop entirely. + +### Migration tooling + +Paliad currently has SQL files in `docs/migrations/` with no runner. Pick a tool now while the schema is small: + +- **Choice: `golang-migrate/migrate`** as a sidecar in the Docker entrypoint. Migrations live in `migrations/` (top-level, not `docs/migrations/` — that path is wrong; move them). Numbered `00N_description.up.sql` + `00N_description.down.sql`. Applied at server startup before the HTTP listener binds. +- Why golang-migrate over Atlas/Goose: simplest, single binary, well-known, plays nicely with Supabase Postgres. + +### Schema collisions + +None. Paliad's existing 3 feedback tables don't overlap with anything in KanzlAI. The risk vector was `cases` vs. `matters` naming — resolved by picking `matters` for the merged schema. + +--- + +## 4. Feature prioritization + +KanzlAI shipped a lot in one session; not all of it was production-quality. Cherry-pick. + +### Ports (P0 — first cutover) + +- **Deadline rules + holidays + calculator** (KanzlAI: `holidays.go`, `deadline_calculator.go`, `deadline_rule_service.go`). Replaces Paliad's `internal/calc/deadline_rules.go` hardcoded data. +- **Matters (Mandate) CRUD + parties** (KanzlAI: `case_service.go`, `parties.go`). Foundation for everything below. +- **Deadline management UI** (KanzlAI: `DeadlineList`, `DeadlineCalendarView`, traffic light cards). Extends current Fristenrechner with persistence. + +### Ports (P1 — fast follow) + +- **Appointments + CalDAV sync** (KanzlAI: `appointment_service.go`, `caldav_service.go`). CalDAV is a real differentiator. **Mandatory fix during port:** encrypt CalDAV credentials at rest (audit §1.3). +- **Dashboard** (KanzlAI: `dashboard_service.go`, dashboard widgets). Replaces Paliad's logged-in landing. + +### Ports (P2 — once foundation is stable) + +- **AI deadline extraction** (KanzlAI: `ai_service.go`, document upload). Dependency on Anthropic key in Dokploy. +- **Notes** (polymorphic notes table, KanzlAI Phase A backend). Cross-cutting; useful but not blocking. + +### Drop entirely + +- **Billing / RVG (`billing_rates.go`, `invoices.go`, `time_entries.go`)** — KanzlAI has handler stubs, no real impl. The audit (§8.2) calls out beA + RVG as table-stakes for *competing with RA-MICRO*. We're not competing with RA-MICRO. HLC has its own firm-wide billing. Don't pretend. +- **`reports.go`, `notifications.go`, `templates.go`, `tenant_handler.go`, `case_assignments.go`** — all aspirational stubs in KanzlAI. Drop. +- **Multi-tenancy infrastructure** — see §2. +- **Reports/audit endpoints** beyond the basic `case_events` audit log. + +### Knowledge platform features that survive + +Paliad's existing tools (Kostenrechner, Glossar, Gebührentabellen, Checklisten, Gerichte, Links, Downloads) all stay as-is. They sit alongside the new matter management features in the same sidebar. + +### Knowledge platform features that change + +- **Fristenrechner** evolves: same calculator UI, but now backed by the same DB as deadline management. Adds a "Save to matter" button that creates persistent deadlines. +- **Roadmap §2.3 UPC Rechtsprechungsübersicht** — dropped entirely (per task brief). Replaced by a curated link in the Link Hub pointing to youpc.org. + +--- + +## 5. Roadmap update + +### Changes to `docs/feature-roadmap.md` + +**Rewrite the "What patholo Is NOT" section (lines 527–534).** New text: + +```markdown +### What Paliad Is + +Paliad is the all-in-one platform for HLC patent practice: + +- **Knowledge platform** — curated content, practical tools, quick reference (Glossar, Gebührentabellen, Checklisten, Gerichtsverzeichnis, Leitfäden, Links). +- **Matter management** — Mandate (cases), Fristen (deadlines), Termine (appointments), parties, documents, notes, audit trail. Personal calendar sync via CalDAV. AI-assisted deadline extraction from uploaded court documents. + +What Paliad is *not*: + +- Not a billing tool — HLC has firm-wide billing infrastructure. +- Not a beA gateway — out of scope; lawyers use existing beA software. +- Not a document management system — SharePoint/netDocuments stay in their lane. +- Not a CMS — content lives in git, not a database with a CMS UI. +``` + +**Drop §2.3 UPC Rechtsprechungsübersicht entirely.** Replace with one Link Hub entry under "Recherche" pointing to `youpc.org/upc/rechtsprechung` (or wherever the curated overview lives). Save 14h of work; defer to youpc.org which already has the data. + +**Update §4.2 Fristenkalender.** Currently P3, "8h, High impact". Promote to P0 — it's the entry point to the matter-management features, not an optional add-on. Replace with: "Fristenkalender → see §8 Phase E (Deadline management UI), Phase F (CalDAV sync)." + +**Add new sections** (after current §1.5 or as a new top-level "Phase 0: Matter Management Foundation"): + +```markdown +### 0.1 Mandate (Matter Management) +### 0.2 Fristen (Persistent Deadline Management) +### 0.3 Termine + CalDAV Sync +### 0.4 Dashboard (Logged-in Landing) +### 0.5 AI-assisted Deadline Extraction +### 0.6 Notes +``` + +Each section should reference the per-phase task in §8 of *this* design doc rather than duplicating estimates. + +**Update prioritized backlog table** (lines 463–483): insert the 6 Phase 0 features at the top with P0 priority. Demote 1.5 (Kostenrechner enhancements) to P1 — matter management beats Kostenrechner polish. + +**Update Architecture Notes "Data Strategy" (line 489).** Currently says "Most Phase 1 and 2 features use static JSON data." Add: "Phase 0 features (matter management) use Supabase tables with RLS. The split is: reference data + curated content = git/JSON; user-generated data = DB." + +--- + +## 6. UI integration + +### Decision: One sidebar, six grouped sections + +Current Paliad sidebar is flat. KanzlAI has its own React sidebar with cases-first navigation. Merge into a single grouped sidebar: + +``` +PALIAD (logo, lime green) + +— ÜBERSICHT — + Dashboard + +— ARBEIT — + Mandate + Fristen + Termine + +— WERKZEUGE — + Kostenrechner + Fristenrechner ← stays as a quick-calc tool, distinct from /fristen + Gebührentabellen + +— WISSEN — + Glossar + Checklisten + Gerichtsverzeichnis + Leitfäden (future, per current roadmap §2.1) + +— RESSOURCEN — + Downloads + Nützliche Links + +— FOOTER — + DE / EN + user@example.com → Logout +``` + +Rationale: +- **"Übersicht"** (Dashboard) is the new home for logged-in users. The current `/` marketing-ish landing becomes the unauthenticated welcome screen. +- **"Arbeit"** = the user's active workload. This is what KanzlAI provided; it's now the top of the new value stack. +- **"Werkzeuge"** = stateless calculators / lookups. Including Fristenrechner here as a quick scratch tool, separate from `/fristen` which is the persistent deadline list. (Yes, two deadline-related routes — they serve different jobs.) +- **"Wissen"** = curated content. +- **"Ressourcen"** = files + external links. + +### Navigation in detail + +- `/` — unauthenticated landing OR redirects authenticated users to `/dashboard`. +- `/dashboard` — Dashboard (KanzlAI port). +- `/mandate`, `/mandate/neu`, `/mandate/[id]`, `/mandate/[id]/{verlauf|fristen|termine|dokumente|parteien|notizen}` — matter list + detail with sub-pages. +- `/fristen`, `/fristen/neu`, `/fristen/[id]`, `/fristen/kalender` — persistent deadlines. +- `/termine`, `/termine/neu`, `/termine/[id]`, `/termine/kalender` — appointments. +- `/tools/fristenrechner` — stays at current path (don't break links). Adds "Save to matter" button. +- All other knowledge routes unchanged. + +### Visual language + +- Lime accent (`#c6f41c`) stays — it's the brand. Used for active nav, primary CTAs, traffic-light "go", new-since-last-visit badges. +- Traffic light colors for deadlines: red `#ef4444` (overdue), amber `#f59e0b` (this week), green `#22c55e` (OK). KanzlAI's choices, keep them. +- Type colors for appointments: hearing=blue, meeting=violet, consultation=emerald, deadline_hearing=amber. KanzlAI's choices, keep them. +- Sidebar: existing Paliad styling, with section headers (small caps, low contrast) added. + +### Frontend rewrite scope + +Pages to build in Bun TSX: +- `dashboard.tsx` (1 page) +- `mandate.tsx`, `mandate-detail.tsx`, `mandate-neu.tsx` (3 pages, plus sub-routes via Go mux) +- `fristen.tsx`, `fristen-detail.tsx`, `fristen-neu.tsx`, `fristen-kalender.tsx` (4 pages) +- `termine.tsx`, `termine-detail.tsx`, `termine-neu.tsx`, `termine-kalender.tsx` (4 pages) +- `einstellungen-caldav.tsx` (1 page) + +Per-page client-side TS bundles in `src/client/` for interactive bits (calendar nav, mark complete, modal forms). HTML-first: forms POST and reload by default; JS enhances where it improves UX (calendar pagination, optimistic mark-complete). + +No react-query, no Tailwind v4. Use existing `global.css` patterns. + +--- + +## 7. Deployment + +### Decision: Single Dokploy compose, retire `kanzlai.msbls.de` + +- **Production target**: existing Paliad compose `Zx147ycurfYagKRl_Zzyo`. No new infra. +- **Domains on the compose**: + - `paliad.de` — primary + - `patholo.de` — legacy redirect (already configured) + - `patholo.msbls.de` — internal preview (already configured) + - **Add: `kanzlai.msbls.de` → 301 redirect to `paliad.de`** (Phase J) +- **KanzlAI Dokploy app** — stop and delete after Phase J cutover. Free up the resources. +- **KanzlAI Gitea repo** (`m/KanzlAI-mGMT`) — archive (set read-only) after cutover. Keep for git history reference. + +### Build and deploy + +- Existing flow stays: push to `main` on `mAi/paliad` → Gitea webhook → Dokploy auto-deploy. +- Dockerfile changes: add migration step to entrypoint (run `migrate up` against `DATABASE_URL` before starting the HTTP server). +- New env vars in Dokploy: + - `DATABASE_URL` (youpc Supabase Postgres conn string) + - `SUPABASE_SERVICE_KEY` (for storage access during AI extraction) + - `ANTHROPIC_API_KEY` (for AI extraction; AI features stay disabled if unset) + - `CALDAV_ENCRYPTION_KEY` (32-byte AES key for credential-at-rest encryption) +- Existing env vars unchanged: `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `GITEA_TOKEN`, `PORT`. + +### Cutover sequence + +Single deployment (no parallel run). Because KanzlAI has zero production users, the cutover is just: + +1. Apply migrations (Phase A). +2. Ship phases B–H to `paliad.de`. +3. Verify functionality. +4. Flip `kanzlai.msbls.de` to 301-redirect to `paliad.de` (Phase J). +5. Stop KanzlAI Dokploy app. + +If KanzlAI ever gets users before cutover, switch to a parallel-run-with-data-migration model. Right now, no users = no data migration. + +--- + +## 8. Migration plan (phased task breakdown) + +Each phase is sized for one focused coder session with clear acceptance criteria. Effort estimates assume Sonnet implementation with Opus design review where flagged. + +### Phase A — Database foundation & migration tooling (~6h) + +**Scope:** +- Create `paliad` schema in youpc Supabase instance. +- Add `golang-migrate` to the Docker entrypoint. +- Move existing Paliad migrations (`docs/migrations/00{1,2,3}.sql`) → `migrations/00{1,2,3}_*.up.sql` + `.down.sql`. Re-namespace tables into `paliad.*`. +- Port KanzlAI tables → `paliad.*` migrations: `users`, `proceeding_types`, `deadline_rules`, `holidays`, `matters` (renamed from `cases`), `parties`, `deadlines`, `appointments`, `documents`, `case_events`, `notes`. Drop `tenant_id` everywhere; add `created_by` FK to `auth.users.id`. +- RLS: `ENABLE ROW LEVEL SECURITY` on all paliad tables with permissive `authenticated` policy. Document the rationale in a migration comment. +- Indexes per audit §3.3: `(status, due_date)` on deadlines, `(start_at)` on appointments, `(matter_id, created_at)` on case_events, `(status)` on matters. +- Seed: 6 proceeding types, 32 UPC + 4 ZPO rules, 68 holidays (port directly from KanzlAI seed). + +**Acceptance:** +- `\dt paliad.*` on the Supabase Postgres shows ≥11 tables. +- RLS enabled on all (verified via `pg_class.relrowsecurity`). +- Migrations run cleanly up + down. +- Seed data present. +- Server starts after `migrate up` runs in entrypoint. + +**Owner suggestion:** coder. Opus review the RLS policy + index choices before merge. + +--- + +### Phase B — Backend foundation (sqlx, services, no UI yet) (~8h) + +**Scope:** +- Add `internal/db/` package (sqlx connection pool with `search_path = paliad,public` qualified at table level — don't rely on session-level setting per audit §2.8). +- Port `internal/services/holidays.go` from KanzlAI (with `sync.RWMutex` fix per audit §1.6). +- Port `internal/services/deadline_calculator.go` (UUID-based; works from DB rules). +- Port `internal/services/deadline_rule_service.go` (read from DB). +- Add `internal/services/matter_service.go` (formerly `case_service.go`), simplified — no tenant filtering, all queries scoped only by `auth.uid()` for `created_by` attribution. +- Port `internal/services/party_service.go`. +- Tests: deadline calculator round-trip, holiday adjustment, matter CRUD basic happy-path. + +**Acceptance:** +- `GET /api/deadline-rules` returns rules from DB. +- `POST /api/deadlines/calculate` works with DB rules (proves Phase A schema ↔ Phase B services wiring). +- `GET/POST /api/matters` works (no UI yet — verified via curl). +- All ported tests pass. +- No regressions in existing Paliad endpoints. + +**Owner suggestion:** coder. + +--- + +### Phase C — Migrate existing Fristenrechner to DB-backed rules (~3h) + +**Scope:** +- Delete `internal/calc/deadline_rules.go` hardcoded data. +- Update `internal/handlers/fristenrechner.go` to call DB-backed services from Phase B. +- Verify existing `/tools/fristenrechner` UI is byte-identical (no UX regression). +- Update `internal/calc/deadlines.go`: delete logic that's now in `services/deadline_calculator.go`; thin shim if needed for the existing JSON shape. + +**Acceptance:** +- `/tools/fristenrechner` page identical UX, same dropdowns, same calc results. +- No hardcoded rules in `internal/calc/`. +- Existing fristenrechner tests pass against new code path. + +**Owner suggestion:** coder. + +--- + +### Phase D — Matters (Mandate) CRUD UI (~6h) + +**Scope:** +- Backend: handlers for `GET/POST/PATCH/DELETE /api/matters`, `GET/POST /api/matters/{id}/parties`. +- New TSX pages: `mandate.tsx` (list with filters), `mandate-neu.tsx` (form), `mandate-detail.tsx` (header + tabs). +- Sub-routes via Go mux: `/mandate/{id}/verlauf`, `/mandate/{id}/parteien`, etc. (other tabs come in later phases — show "Coming soon" placeholder). +- Sidebar: add "Arbeit" group with Mandate as the only entry initially. +- Per-page client TS for inline edit, delete confirm. + +**Acceptance:** +- Lawyer logs in, clicks "Mandate", creates a new matter, adds parties, sees it in the list, opens detail page. +- Audit trail: each mutation creates a `case_event` row. +- Empty states are friendly (German Umlaute correct per audit §2.10). + +**Owner suggestion:** coder. + +--- + +### Phase E — Persistent deadline management UI (~6h) + +**Scope:** +- Backend: handlers for `GET /api/deadlines` (all), `GET/POST /api/matters/{id}/deadlines`, `PATCH /api/deadlines/{id}`, `PATCH /api/deadlines/{id}/complete`, `DELETE /api/deadlines/{id}`. +- New TSX pages: `fristen.tsx` (list with status filter — overdue/this_week/all), `fristen-neu.tsx`, `fristen-detail.tsx`, `fristen-kalender.tsx` (month grid). +- Traffic light cards on `fristen.tsx` + on `mandate-detail.tsx` (deadlines tab — replace placeholder from Phase D). +- "Save to matter" button on existing Fristenrechner: select target matter from dropdown, persists calculated deadlines as draft. + +**Acceptance:** +- Lawyer creates matter, adds deadlines manually OR via Fristenrechner "Save to matter". +- List filterable by status. Calendar view navigable by month. +- Mark-complete works, persists, updates traffic light counts. + +**Owner suggestion:** coder. + +--- + +### Phase F — Appointments + CalDAV sync (~8h) + +**Scope:** +- Backend: `appointment_service.go`, handlers for CRUD. +- Backend: `caldav_service.go` ported from KanzlAI **with credential encryption fix** (audit §1.3): use `CALDAV_ENCRYPTION_KEY` env var with AES-GCM at rest. Never return password in API responses. +- New TSX pages: `termine.tsx`, `termine-neu.tsx`, `termine-detail.tsx`, `termine-kalender.tsx`, `einstellungen-caldav.tsx`. +- Per-user CalDAV config (one entry per `auth.uid()`, stored in new `paliad.user_caldav_config` table). +- Background sync goroutine per user with active config (pulled at server startup, refreshed when config changes). + +**Acceptance:** +- User configures CalDAV in settings, password is encrypted in DB. +- User creates an appointment, it appears in their external calendar within 60s. +- External calendar event edited → reflected in Paliad on next sync cycle. +- Deleting CalDAV config purges credentials. + +**Owner suggestion:** coder, Opus design review of the encryption scheme + sync conflict resolution. + +--- + +### Phase G — Dashboard (~4h) + +**Scope:** +- Backend: `dashboard_service.go` ported and simplified (no tenant). Returns `deadline_summary`, `matter_summary`, `upcoming_deadlines` (7d), `upcoming_appointments` (7d), `recent_activity`. +- New TSX page: `dashboard.tsx` — server-rendered initial paint (no client-side fetch + skeleton, per audit §2.3 critique). +- Update `/` route: redirect authenticated users to `/dashboard`; serve current marketing-ish landing only for unauthenticated visitors (or remove entirely if HLC sees no value in the unauthenticated landing). +- Update sidebar "Übersicht → Dashboard" entry. + +**Acceptance:** +- Authenticated user lands on `/dashboard` with zero client-side waterfall. +- Traffic lights link to `/fristen?status=...`. +- Upcoming items link to detail pages. +- Recent activity shows last 10 case events with author + timestamp. + +**Owner suggestion:** coder. + +--- + +### Phase H — AI deadline extraction (~4h) + +**Scope:** +- Backend: `ai_service.go` ported from KanzlAI. Anthropic SDK call with PDF document blocks + tool-forced structured output. +- Backend: document upload → Supabase Storage, then AI extraction triggered. +- Frontend: document upload component on `mandate-detail.tsx` "Dokumente" tab. +- Extraction result: review screen with confidence scores + source quotes; user confirms which deadlines to persist. +- Feature flag: AI tab/buttons hidden if `ANTHROPIC_API_KEY` is unset. + +**Acceptance:** +- Upload a UPC Statement of Defence PDF → AI extracts deadlines with rule references → user confirms → persisted as draft deadlines on the matter. +- No AI calls on uploads to other matters' documents (stays user-triggered). + +**Owner suggestion:** coder. + +--- + +### Phase I — Notes (polymorphic) (~4h) + +**Scope:** +- Backend: `notes.go` table migration (polymorphic FK with CHECK constraint per KanzlAI Phase A pattern), `note_service.go`, handlers. +- Frontend: shared `NotesList` TSX component, embedded on matter / deadline / appointment / case-event detail pages. + +**Acceptance:** +- User adds note to a matter → visible on matter detail. +- User adds note to a deadline → visible on deadline detail. +- Edit/delete works. + +**Owner suggestion:** coder. + +--- + +### Phase J — Roadmap rewrite + KanzlAI retirement (~3h) + +**Scope:** +- Rewrite `docs/feature-roadmap.md` per §5 of this doc. +- Add `kanzlai.msbls.de` domain to Paliad Dokploy compose with 301 redirect rule (Traefik label or middleware). +- Stop and delete KanzlAI Dokploy app. +- Archive `m/KanzlAI-mGMT` Gitea repo (set to read-only / archived). +- Update `mai.projects`: optionally merge `kanzlai` project into `paliad` (decide: keep history separate or unify). +- Memory: write episode in `paliad` group documenting the consolidation, supersede KanzlAI episodes. +- Drop `kanzlai` schema from Supabase: `DROP SCHEMA kanzlai CASCADE` after final verification that no Paliad code references it. + +**Acceptance:** +- `kanzlai.msbls.de` redirects to `paliad.de` with 301. +- KanzlAI Dokploy app gone. +- KanzlAI repo archived. +- Roadmap doc reflects the new strategy. +- `kanzlai` schema dropped. + +**Owner suggestion:** head + cronus together (mostly ops, a bit of writing). + +--- + +### Total effort + +**~52 hours of focused implementation across 10 phases**, plus this design review (~3h) = **~55h total**. At 1–2 phases per day with one coder, this completes in **2–3 working weeks**. + +--- + +## 9. Risks & unknowns + +### Risks + +1. **KanzlAI's audit issues must not be ported as-is.** The audit identified 7 critical and 13 important issues. Each port phase is responsible for fixing the issues relevant to its code: Phase A fixes RLS policies (audit §2.13), Phase B fixes HolidayService race + search_path (§1.6, §2.8), Phase F fixes CalDAV plaintext (§1.3), every backend phase implements proper error handling (§1.5) + length limits + pagination (§2.1, §2.2). Track these in a checklist per phase. + +2. **React → Bun TSX rewrite friction.** KanzlAI's frontend uses react-query, react-day-picker, modal libs, complex client state. Paliad's stack uses server-rendered TSX + small per-page client TS. Some patterns translate awkwardly. Mitigation: HTML-first design, JS only where it materially improves UX. If a feature *requires* a heavy interactive component (e.g., date picker), prefer native `` over importing a library. + +3. **Single-tenant assumption locks us in.** If HLC ever wants per-office or per-practice-group matter visibility, schema needs ACL columns or a separate visibility table. Mitigation: every table has `created_by`; can add `visible_to JSONB` later without breaking changes. Don't build it now. + +4. **Email domain transition risk.** Whitelist `[@hoganlovells.com, @hlc.com, @hlc.de]`. Risk: HLC picks a TLD we didn't anticipate (`@hlc.law`?). Mitigation: keep the whitelist in env config, not hardcoded. Trivial to update. + +5. **CalDAV encryption key rotation.** If `CALDAV_ENCRYPTION_KEY` is ever lost or rotated, all stored credentials become unreadable. Mitigation: document the recovery posture (users re-enter CalDAV password from their existing software), commit to never rotating without a re-encrypt migration. + +6. **KanzlAI-derived German Umlaut typos** in audit §2.10. Don't carry them forward. Style check during each frontend port phase. + +7. **No load testing on KanzlAI's calc-heavy paths.** AI extraction in particular can be slow + expensive. Phase H should add per-user rate limits before going live. + +### Unknowns + +- **Does HLC actually want firm-wide matter visibility, or office-scoped?** Need product input from m. The §2 decision (firm-wide) is the simplest assumption; if wrong, Phase D needs an ACL design before shipping. +- **AI extraction quality on real HLC documents.** KanzlAI's test fixtures were synthetic. Real UPC Statements of Defence are messier. Phase H should include a manual test pass on 3–5 real (anonymized) HLC documents before declaring done. +- **CalDAV server compatibility.** KanzlAI tested against `dav.msbls.de` and Apple iCloud. HLC lawyers may use Outlook + Exchange, where CalDAV support is limited or off by default. Phase F should verify against the actual calendar systems HLC lawyers use. +- **HLC IT approval for storing matter data on a Supabase instance hosted in Germany.** Out of scope for this design but blocking for going live with real client data. Flag to m. +- **Naming: "Mandate" vs "Akten" vs "Matters"** in the German UI. Currently picking "Mandate" — confirm with a HLC partner before frontend strings are baked in. + +--- + +## 10. Open question for the head + +Once this design is approved, who implements? + +- **Option 1: I (cronus) implement Phases A–J as coder.** I have the deepest context from this design work and the source repos. Single context, no handoff loss. Likely faster overall. +- **Option 2: Hand off to a separate coder worker (e.g., pike, knuth, ritchie) per phase.** Better parallelism if multiple phases can run in parallel. But Phases A → B → C → D have hard dependencies; only F, G, H, I can run in any order after E. +- **Option 3: Hybrid — I implement Phases A–C (foundation), hand off D–I to coder workers in parallel where possible, take J myself for the wrap-up.** + +**My recommendation: Option 3.** Foundation needs design context (cronus is the carrier). Phases D–I are mechanical ports of well-defined services; coder workers can run them with the design doc + KanzlAI source as the brief. Phase J is the easy victory lap. + +--- + +*End of design.*