# paliad

Paliad — the patent paladin. All-in-one patent practice platform for HLC (formerly Hogan Lovells) colleagues. Knowledge platform + Aktenverwaltung in one sidebar, one URL, one login.

**Brand:** Paliad (firm-agnostic — survives firm renames)
**Primary domain:** paliad.de
**Legacy domains:** patholo.de, patholo.msbls.de (active during transition)
**Memory group_id:** `paliad`
**mai project_id:** `paliad`

## Purpose

- **Project management** — hierarchical projects (Client → Litigation → Patent → Case), deadlines, appointments, parties, notes, audit trail. Team-based visibility with inheritance down the project tree. Personal CalDAV sync.
- **Interactive knowledge tools** — Prozesskostenrechner, Fristenrechner, Gebührentabellen, Checklisten, Gerichtsverzeichnis, Patentglossar, Link Hub, Downloads.
- **Dashboard** — logged-in landing with deadline traffic lights, upcoming appointments, recent activity, all scoped to visible projects.
- Share guides, templates, and documents with the patent team.
- Document best practices and style guides (HL Patents Style).
- Long-term: document upload, collaboration annotations, Outlook/Exchange sync.

## Audience

- HLC patent lawyers and PAs (Munich, Düsseldorf, Amsterdam, London, Paris, Milan, Hamburg)
- Primary languages: German + English (DE/EN toggle on every page)
- Must be accessible, clean, professional

## Tech Stack

- **Frontend:** Bun + TSX (custom JSX renderer, no React), per-page client TS bundles, HTML-first forms
- **Backend:** Go API, `net/http`, `sqlx` for DB access
- **Migrations:** `golang-migrate/migrate/v4` with SQL files embedded via `embed.FS`; applied at server startup before the HTTP listener binds. Migration tracker is `paliad.paliad_schema_migrations` (avoids collision with other apps on the shared `public.schema_migrations`).
- **Database:** youpc Supabase Postgres (port 11833), `paliad` schema. Team-based RLS via `paliad.can_see_project(project_id)` — visibility determined by team membership (direct + inherited up the project tree). See `docs/design-data-model-v2.md`.
- **Auth:** Supabase (youpc instance) — password-based, email-domain gate via `ALLOWED_EMAIL_DOMAINS` (default `hoganlovells.com,hlc.com,hlc.de`). The whitelist references real DNS domains and rotates independently from `FIRM_NAME` (display name).
- **Hosting:** Dokploy compose on mlake (72.62.52.189), compose ID `Zx147ycurfYagKRl_Zzyo`
- **Domains on Dokploy:** paliad.de (primary, Let's Encrypt), patholo.de (legacy), patholo.msbls.de (internal)
- **Deploy:** push to main → Gitea webhook → Dokploy auto-deploy

## Environment variables

| Variable | Required | Purpose |
|---|---|---|
| `PORT` | no (default 8080) | HTTP listen port |
| `SUPABASE_URL` | yes | Supabase project URL (auth) |
| `SUPABASE_ANON_KEY` | yes | Supabase anon key (auth) |
| `DATABASE_URL` | for Aktenverwaltung | Direct Postgres conn for migrations + Akten/Fristen/Termine services. Knowledge-platform features (Kostenrechner, Glossar, Links, Gebührentabellen, Checklisten, Gerichte, Downloads) work without it — those endpoints return data from static JSON and never touch the pool. Aktenverwaltung endpoints return 503 if unset. |
| `CALDAV_ENCRYPTION_KEY` | for CalDAV sync | 32-byte AES-256 key, base64-encoded. Encrypts CalDAV passwords at rest (AES-GCM). Server fails fast on malformed key; CalDAV is silently disabled if unset (Termine still work locally; `/api/caldav-config` returns 501). |
| `GITEA_TOKEN` | optional | Gitea API token for the private file proxy (Downloads) |
| `PALIAD_BASE_URL` | optional | Public origin used in email links. Defaults to `https://paliad.de`; override for staging/preview. |
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_USERNAME` / `SMTP_PASSWORD` / `SMTP_FROM` / `SMTP_FROM_NAME` / `SMTP_USE_TLS` | for email | SMTP credentials for Paliad's transactional mail (reminders, invitations). Port 465 uses implicit TLS. `MailService` silently no-ops when any required var is missing — the server still boots for knowledge-platform-only deployments. |
| `ANTHROPIC_API_KEY` | not used in PoC | Reserved for the eventual production-v1 Paliadin (the Anthropic Messages API path, see `docs/design-paliadin-2026-05-07.md` §2). The Phase 0 PoC (t-paliad-146) does NOT use this — it shells out to a local `claude` CLI via tmux instead, which uses m's existing Claude Code subscription. Set this env var only after the PoC validates and we cut over to the API-backed path. The earlier "Phase H Frist-Extraktion" reservation is dead — that feature is deferred separately (memory `b6a11b55…`). |
| `PALIADIN_SESSION_PREFIX` | optional (default `paliad-paliadin`) | Prefix for the per-user tmux session names the legacy Paliadin service uses (t-paliad-155). Each Paliad user gets their own session named `<prefix>-<userid8>` (first 8 hex chars of the user's UUID); conversation history accumulates per visit, `ResetSession` kills the session entirely. **Skill source-of-truth moved to `m/mAi` under `skills/aichat/paliadin/` (m's 2026-05-13 decision, t-paliad-194).** The aichat backend owns installation on mRiver via its own deploy doc (`m/mAi/docs/reference/aichat-deploy.md`). Legacy `LocalPaliadinService` (PoC) and `RemotePaliadinService` (shim) still rely on `~/.claude/skills/paliadin/SKILL.md` being present on the target host — install it manually from the aichat repo until those paths are retired. |
| `PALIADIN_REMOTE_CWD` | shim env (default `/home/m/dev/paliad`) | Working directory `paliadin-shim` uses when spawning the long-lived `claude` pane on mRiver. Must be the paliad repo root so claude picks up `.mcp.json` (project-scoped Supabase MCP); without it, the SKILL.md SQL recipes have no DB tool. Set on mRiver only — paliad's Go side never reads this. |
| `PALIADIN_RESPONSE_DIR` | optional (default `/tmp/paliadin`) | Directory where Claude writes its per-turn response files. The Go service polls this directory for `{turn_id}.txt` files. (Legacy `LocalPaliadinService` path only — aichat owns its own response dir at `/tmp/aichat/paliadin/`.) |
| `PALIADIN_BACKEND` | optional (default `legacy`) | Selects which Paliadin backend boots (t-paliad-194 / m/paliad#38 Phase B). `legacy` keeps the existing tree (`PALIADIN_REMOTE_HOST` → SSH shim, else local tmux, else disabled). `aichat` opts into the centralized `m/mAi#207` backend on mRiver — `RemotePaliadinService`/`LocalPaliadinService` are bypassed and `AichatPaliadinService` issues HTTP calls instead. Parallel paths during the migration window; flip back is one env-var change. |
| `AICHAT_URL` | required when `PALIADIN_BACKEND=aichat` | Aichat service root (typically `http://100.99.98.203:8765` over Tailscale; see `m/mAi/docs/reference/aichat-deploy.md`). No trailing slash needed. |
| `AICHAT_TOKEN` | required when `PALIADIN_BACKEND=aichat` | Raw bearer token registered for paliad's app_id in aichat's `tokens.yaml`. Distributed via Dokploy secret per Q11 (age-encrypted at rest). |
| `AICHAT_PERSONA` | optional (default `paliadin`) | Persona id to target. Override only when running a non-default deploy (e.g. staging persona). |

> *Note on Paliadin gating (t-paliad-146):* there is **no** `PALIADIN_ENABLED` env var. Access is gated in code via `services.PaliadinOwnerEmail` (currently `matthias.siebels@hoganlovells.com`). Every other authenticated user gets a 404 on `/paliadin` and `/admin/paliadin`. This means the routes register on every paliad deploy (including paliad.de prod), but only m can reach them — and even then, prod only works if the host has `tmux` + a `claude` CLI in PATH (which the Dokploy container does not). PoC remains a laptop-only feature; the gate is in the code, not the deploy.
| `FIRM_NAME` | optional (default `HLC`) | Display name of the firm Paliad is being branded for in this deployment. Read once at process start by `internal/branding.Name` (Go) and inlined into client bundles by `frontend/build.ts` (TypeScript). Powers every user-facing surface — landing hero, page titles, login hint, Downloads page, footer, invitation/reminder email bodies. The `ALLOWED_EMAIL_DOMAINS` whitelist is a separate concern (real DNS domains, not display name) and rotates independently. |

> *Note on `DATABASE_URL`:* "Work without DB" ≠ "ungated". All knowledge-platform routes (Kostenrechner, Glossar, Links, Gebührentabellen, Checklisten, Gerichte, Downloads) are still behind the auth gate (302 to `/login` for anon visitors); only `/`, `/login`, `/logout`, and `/assets/*` are public. The `gateOnboarded` middleware additionally blocks unonboarded users from app pages but does NOT gate the knowledge-platform pages.

## Infrastructure

- **Gitea:** `m/paliad` on mgit.msbls.de (renamed from `mAi/paliad` 2026-04-30; previously `mAi/patholo` — auto-redirects)
- **DNS:** paliad.de → 72.62.52.189 (via Hostinger)
- **Branding:** lime green accent (`#c6f41c`), sidebar layout, DE/EN i18n. Firm-agnostic: every user-facing firm reference is rendered from `internal/branding.Name` (Go) / `frontend/src/branding.ts` (TypeScript). Default "HLC", overridable via `FIRM_NAME`. See t-paliad-065.

## Project status & history

Phase status, shipped milestones, open follow-ups, and the patHoLo→Paliad rebrand history live in `docs/project-status.md`. Read that before assuming a feature is or isn't built.

## Worker Preferences

- Use **Opus** for design/architecture decisions
- Use **Sonnet** for implementation
- Prefer **gitster** role for issues

## Language convention

**System language is English.** All code, table names, Go types, service names, URL paths, API endpoints, file names — English. Examples: `projects` not `projekte`, `deadlines` not `fristen`, `appointments` not `termine`, `ProjectService` not `ProjektService`, `/projects` not `/projekte`.

**Frontend default language is German.** User-facing i18n strings are bilingual (DE primary, EN secondary). UI labels, error messages, page titles — all translated via `i18n.ts`. The product speaks German to its users but the codebase speaks English to developers.

**Product tool names stay German** as brand names: Fristenrechner, Kostenrechner, Gebührentabellen (these are proper nouns in the product context, kept in URLs as `/tools/fristenrechner` etc.).

## Frontend conventions

**`.entity-table` row-click contract.** The default `.entity-table tbody tr` rule sets `cursor: pointer` + a hover highlight on every row. If you add an `.entity-table` to a page, the row affordance must match reality:

- **Rows that navigate** — wire a row-level click handler that does `window.location.href = "..."` and skips clicks on inner `<a>` / `<button>` (so nested links and action buttons still work). Pattern lives in `frontend/src/client/checklists.ts`, `client/projects-detail.ts`, `client/deadlines.ts`.
- **Rows that don't navigate** (read-only summary tables, admin tables where all actions are inline buttons) — add `entity-table--readonly` to the `<table>` className. That modifier neutralises the cursor and hover.

A row that looks clickable but isn't is a UX lie and confuses users (cf. t-paliad-098/099). The CSS rule and modifier are anchored in `frontend/src/styles/global.css` near `.entity-table tbody tr`.

**Whole-card / whole-row click → use a JS row handler, not a `::before` overlay.** Don't make a card fully clickable by spanning a `::before { inset: 0 }` (or any pointer-event overlay) over it — the overlay swallows pointer events on the text and breaks selection / copy (cf. t-paliad-102 → t-paliad-103). Instead, attach a row-level click handler that calls `window.location.href = ...` and skips clicks on inner `<a>` / `<button>` (the same pattern as the `.entity-table` rule above). Examples on `.entity-event` (Verlauf) and `.dashboard-activity-item` in `frontend/src/client/projects-detail.ts` + `client/dashboard.ts`. Text stays selectable, click still navigates, keyboard / Cmd-click semantics intact.
