Files
paliad/.claude/CLAUDE.md
m 97a412498d feat(t-paliad-155): real Claude SKILL.md + per-user tmux session
Move Paliadin's persona + response protocol from a tmux-keystroke-injected
system prompt into a real Claude skill at ~/.claude/skills/paliadin/SKILL.md
(repo source: scripts/skills/paliadin/SKILL.md, install script:
scripts/install-paliadin-skill). Claude's skill router auto-matches the
[PALIADIN:<uuid>] envelope on every turn, so the protocol contract
survives /clear, fresh sessions, and pane restarts — root-cause fix for
the post-/clear stuck-spinner that triggered this task.

Per-user tmux session keying: each Paliad user gets a session named
<prefix>-<userid8> (first 8 hex chars of UUID). One persistent session
per user, conversation history accumulates per visit, ResetSession kills
the session entirely. Health-check cache becomes per-session.

Service-side simplifications:
- paliadin_prompt.go (paliadinSystemPrompt) deleted; trailer parser stays
  in paliadin.go.
- paliadin_remote.go: ensureBootstrapped removed; healthGate takes a
  session arg + caches per-key; ResetSession derives session from UserID
  and shells out to 'reset <session>'.
- paliadin.go (LocalPaliadinService): per-user pane cache, ensurePane
  takes UserID, no more in-process system-prompt send.
- Paliadin interface: ResetSession now takes UserID.

Shim refactor (scripts/paliadin-shim):
- All verbs accept the tmux session as their first positional arg.
- 'bootstrap' verb removed (skill replaces it).
- 'reset' kills the named session via tmux kill-session.
- Session name validated against [A-Za-z0-9_.-]{1,64}.

Env var rename: PALIADIN_TMUX_SESSION -> PALIADIN_SESSION_PREFIX (semantic
shift from literal session name to per-user prefix); CLAUDE.md updated.

Tests cover per-session health caching, session-name derivation,
ResetSession kill-session shape, and health-cache eviction on reset.
2026-05-08 12:42:57 +02:00

9.9 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, 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 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_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_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.