Files
paliad/docs/design-kanzlai-integration.md
m 1150bcbe6c docs: add KanzlAI → Paliad integration design
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
2026-04-16 13:11:14 +02:00

31 KiB
Raw Blame History

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.

  • 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:
    • casespaliad.matters (rename: "matter" is universal across DE/EN; "Akte" stays the German display label)
    • parties, deadlines, appointments, documents, case_events, notespaliad.*, drop tenant_id, keep created_by (FK to auth.users.id)
    • Reference tables: proceeding_types, deadline_rules, holidayspaliad.* (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 527534). New text:

### 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"):

### 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 463483): 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 BH 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 12 phases per day with one coder, this completes in 23 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 <input type="date"> 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 35 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 AJ 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 AC (foundation), hand off DI 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 DI 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.