claude in the shim's tmux pane was being launched from $HOME, so it
loaded only global MCPs (mai, mai-memory, mgeo) and missed the
project-scoped Supabase MCP at /home/m/dev/paliad/.mcp.json. SKILL.md's
SQL recipes therefore had no DB tool — m saw 'no DB access' on every
real Paliadin turn.
Fix: tmux new-window -c $CLAUDE_CWD when spawning the pane. New env
var PALIADIN_REMOTE_CWD (default /home/m/dev/paliad) lets a host
override the path if the repo lives elsewhere; shim fast-fails with
exit 3 if the directory doesn't exist.
CLAUDE.md updated. Verified by spawning a fresh session via the shim
and inspecting #{pane_current_path}.
10 KiB
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,sqlxfor DB access - Migrations:
golang-migrate/migrate/v4with SQL files embedded viaembed.FS; applied at server startup before the HTTP listener binds. Migration tracker ispaliad.paliad_schema_migrations(avoids collision with other apps on the sharedpublic.schema_migrations). - Database: youpc Supabase Postgres (port 11833),
paliadschema. Team-based RLS viapaliad.can_see_project(project_id)— visibility determined by team membership (direct + inherited up the project tree). Seedocs/design-data-model-v2.md. - Auth: Supabase (youpc instance) — password-based, email-domain gate via
ALLOWED_EMAIL_DOMAINS(defaulthoganlovells.com,hlc.com,hlc.de). The whitelist references real DNS domains and rotates independently fromFIRM_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 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. The persona + response protocol now live in ~/.claude/skills/paliadin/SKILL.md (installed via scripts/install-paliadin-skill) — no in-process system prompt is sent. |
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. |
Note on Paliadin gating (t-paliad-146): there is no
PALIADIN_ENABLEDenv var. Access is gated in code viaservices.PaliadinOwnerEmail(currentlymatthias.siebels@hoganlovells.com). Every other authenticated user gets a 404 on/paliadinand/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 hastmux+ aclaudeCLI 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 (defaultHLC) | Display name of the firm Paliad is being branded for in this deployment. Read once at process start byinternal/branding.Name(Go) and inlined into client bundles byfrontend/build.ts(TypeScript). Powers every user-facing surface — landing hero, page titles, login hint, Downloads page, footer, invitation/reminder email bodies. TheALLOWED_EMAIL_DOMAINSwhitelist 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/loginfor anon visitors); only/,/login,/logout, and/assets/*are public. ThegateOnboardedmiddleware additionally blocks unonboarded users from app pages but does NOT gate the knowledge-platform pages.
Infrastructure
- Gitea:
m/paliadon mgit.msbls.de (renamed frommAi/paliad2026-04-30; previouslymAi/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 frominternal/branding.Name(Go) /frontend/src/branding.ts(TypeScript). Default "HLC", overridable viaFIRM_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 infrontend/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--readonlyto 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.