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
31 KiB
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_tenantstable layer - Tenant resolution middleware (with the tenant isolation bypass bug)
tenant_idon 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.comAND@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.userstable: extendsauth.users, addsoffice,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) withcreated_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.comSupabase 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.usersif 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:
- Create
paliadschema in the same youpc Supabase instance (no new project, no DB migration). - Move Paliad's 3 existing feedback tables into
paliad.*(CREATE in new schema, COPY data, drop old). - 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.*, droptenant_id, keepcreated_by(FK toauth.users.id)- Reference tables:
proceeding_types,deadline_rules,holidays→paliad.*(these are pure reference data, no tenancy concept needed)
- RLS enabled on all paliad tables with permissive
authenticatedpolicies (per §2). - Retire
kanzlai.*schema once cutover is verified (DROP SCHEMA kanzlai CASCADEin Phase J).
Reuse vs. port
- Reuse the data: KanzlAI's
deadline_rulestable has 32 UPC + 4 ZPO rules (more than Paliad's hardcodedinternal/calc/deadline_rules.go). Port the data, drop Paliad's hardcoded rules. - Reuse the holiday data: KanzlAI's
holidaystable 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/migrateas a sidecar in the Docker entrypoint. Migrations live inmigrations/(top-level, notdocs/migrations/— that path is wrong; move them). Numbered00N_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'sinternal/calc/deadline_rules.gohardcoded 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_eventsaudit 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:
### 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 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
/fristenwhich 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— primarypatholo.de— legacy redirect (already configured)patholo.msbls.de— internal preview (already configured)- Add:
kanzlai.msbls.de→ 301 redirect topaliad.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
mainonmAi/paliad→ Gitea webhook → Dokploy auto-deploy. - Dockerfile changes: add migration step to entrypoint (run
migrate upagainstDATABASE_URLbefore 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:
- Apply migrations (Phase A).
- Ship phases B–H to
paliad.de. - Verify functionality.
- Flip
kanzlai.msbls.deto 301-redirect topaliad.de(Phase J). - 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
paliadschema in youpc Supabase instance. - Add
golang-migrateto 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 intopaliad.*. - Port KanzlAI tables →
paliad.*migrations:users,proceeding_types,deadline_rules,holidays,matters(renamed fromcases),parties,deadlines,appointments,documents,case_events,notes. Droptenant_ideverywhere; addcreated_byFK toauth.users.id. - RLS:
ENABLE ROW LEVEL SECURITYon all paliad tables with permissiveauthenticatedpolicy. 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 upruns 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 withsearch_path = paliad,publicqualified at table level — don't rely on session-level setting per audit §2.8). - Port
internal/services/holidays.gofrom KanzlAI (withsync.RWMutexfix 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(formerlycase_service.go), simplified — no tenant filtering, all queries scoped only byauth.uid()forcreated_byattribution. - Port
internal/services/party_service.go. - Tests: deadline calculator round-trip, holiday adjustment, matter CRUD basic happy-path.
Acceptance:
GET /api/deadline-rulesreturns rules from DB.POST /api/deadlines/calculateworks with DB rules (proves Phase A schema ↔ Phase B services wiring).GET/POST /api/mattersworks (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.gohardcoded data. - Update
internal/handlers/fristenrechner.goto call DB-backed services from Phase B. - Verify existing
/tools/fristenrechnerUI is byte-identical (no UX regression). - Update
internal/calc/deadlines.go: delete logic that's now inservices/deadline_calculator.go; thin shim if needed for the existing JSON shape.
Acceptance:
/tools/fristenrechnerpage 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_eventrow. - 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+ onmandate-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.goported from KanzlAI with credential encryption fix (audit §1.3): useCALDAV_ENCRYPTION_KEYenv 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 newpaliad.user_caldav_configtable). - 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.goported and simplified (no tenant). Returnsdeadline_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
/dashboardwith 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.goported 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_KEYis 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.gotable migration (polymorphic FK with CHECK constraint per KanzlAI Phase A pattern),note_service.go, handlers. - Frontend: shared
NotesListTSX 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.mdper §5 of this doc. - Add
kanzlai.msbls.dedomain to Paliad Dokploy compose with 301 redirect rule (Traefik label or middleware). - Stop and delete KanzlAI Dokploy app.
- Archive
m/KanzlAI-mGMTGitea repo (set to read-only / archived). - Update
mai.projects: optionally mergekanzlaiproject intopaliad(decide: keep history separate or unify). - Memory: write episode in
paliadgroup documenting the consolidation, supersede KanzlAI episodes. - Drop
kanzlaischema from Supabase:DROP SCHEMA kanzlai CASCADEafter final verification that no Paliad code references it.
Acceptance:
kanzlai.msbls.deredirects topaliad.dewith 301.- KanzlAI Dokploy app gone.
- KanzlAI repo archived.
- Roadmap doc reflects the new strategy.
kanzlaischema 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
-
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.
-
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. -
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 addvisible_to JSONBlater without breaking changes. Don't build it now. -
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. -
CalDAV encryption key rotation. If
CALDAV_ENCRYPTION_KEYis 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. -
KanzlAI-derived German Umlaut typos in audit §2.10. Don't carry them forward. Style check during each frontend port phase.
-
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.deand 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.