Commit Graph

62 Commits

Author SHA1 Message Date
m
f952fb85c3 design(t-paliad-151) amend: port 22022 bypass + Phase A.0 results
Phase A.0 revealed Tailscale SSH on mRiver intercepts :22 from tailnet
peers and bypasses OpenSSH's authorized_keys entirely (banner
"SSH-2.0-Tailscale", auth method "none", command= restriction never
fires). The fix is port 22022 via a systemd ssh.socket drop-in:
Tailscale SSH only intercepts :22, so :22022 hits real OpenSSH where
the design's command=/from= shim restriction works as specified.

Updated:
- §3 locked decisions: row 5 added (port 22022, m's call 23:35)
- §4.5 new subsection: Tailscale SSH bypass via socket drop-in
  + records the "Address already in use" first-attempt failure as a
  "don't retry without cleaning sshd_config Port directives first"
  lesson
- §5.2/5.3: ssh-keyscan now uses -p 22022; known_hosts is host:port
  keyed for non-22 ports
- §6.1/6.2/6.3: SSHPort field on RemotePaliadinService config, -p
  flag in callShim, PALIADIN_REMOTE_PORT env (default 22022)
- §7 phasing: A.0 completion checked off step-by-step with concrete
  fingerprints; A.5/A.6/A.7 split out as m-driven
- §8 security: Tailscale-SSH-on-:22 risk explicitly tabled with
  port-22022 mitigation
- §10 deliverables: mRiver host-setup artifacts noted
- §12 new Phase A.0 completion summary with the three secrets m
  needs to register in Dokploy

Phase A.0 verified end-to-end:
- ssh -p 22022 paliad-prod-key m@mriver health → ok
- run-turn UUID base64msg → 3.4 s including a real Claude response
- from="100.99.98.201" correctly rejects connections from mRiver
  itself

mRiver host state in place (not in repo): authorized_keys with
restrictions, /home/m/.local/bin/paliadin-shim, ssh.socket drop-in.
Three secrets staged at ~/.paliad-staging/ on mRiver for m to copy
into Dokploy: paliad-prod-key (PALIADIN_SSH_PRIVATE_KEY),
known_hosts (PALIADIN_KNOWN_HOSTS), and the three plain env vars.

Refs m/paliad#12
2026-05-07 23:37:26 +02:00
m
befa41c00e design(t-paliad-151): Paliadin Tailscale SSH route to mRiver
Inventor design for routing Paliadin from paliad.de's Dokploy container
on mLake to mRiver via Tailscale + SSH, preserving m's Claude Code
subscription instead of paying Anthropic API tokens.

Three sub-designs covering m's four locked decisions (2026-05-07 22:35):
- network_mode: host on paliad (m overrode the sidecar recommendation;
  Phase A explicitly tests traefik compatibility under host mode)
- server-side paliadin-shim with one RPC per turn (run-turn / reset /
  health / bootstrap), authorized_keys command= restriction, from=mlake
- env-var routing trigger (PALIADIN_REMOTE_HOST) + Paliadin interface
  split: LocalPaliadinService keeps the laptop PoC, RemotePaliadinService
  shells out to ssh m@mriver paliadin-shim
- ed25519 keypair via Dokploy secret PALIADIN_SSH_PRIVATE_KEY, written
  to a chmod 600 tmpfile at startup; pinned host key via
  PALIADIN_KNOWN_HOSTS

Verified live before designing: mRiver tmux+claude present, mLake
Tailscale active and sees mRiver, paliad Dockerfile is alpine-minimal,
no authorized_keys on mRiver yet. No assumptions left from CLAUDE.md.

Includes: friendly error code mriver_unreachable extending t-paliad-150,
single-flight rate limit, security review (defence-in-depth via
command=/from= restrictions), three-phase rollout (manual proof →
Dockerfile bake → polish), file-level deliverables for the coder shift.

Inventor stops here — no code shipped. Awaiting m's go/no-go.

Refs m/paliad#12
2026-05-07 22:47:30 +02:00
m
438e73fd13 docs(t-paliad-149): renumber migrations 058→060 (PR 1) and 059→061 (PR 2)
058 = paliadin_poc (t-146), 059 = profession_vs_responsibility (t-148), both shipped on main 2026-05-07. Next available is 060.

Per maria's coder-shift instruction.
2026-05-07 22:15:22 +02:00
m
597d76e21c Merge remote-tracking branch 'origin/main' into mai/godel/inventor-projects-page 2026-05-07 22:14:28 +02:00
m
b9824dd86f docs(t-paliad-149): lock 4 surfaced questions per m's AskUserQuestion answers
m's locks (2026-05-07 22:08):
- Q1 default landing → last-viewed restore (sessionStorage; URL params override; first-visit fallback Tree+Alle+top-level)
- New-Q20 cards default content → rich (~9 facts: title+type+status+clientmatter+parent path+deadline counts+next 3+last 3+team)
- New-Q21 cards customisation → FULL drag-rearrange + named layouts (new paliad.user_card_layouts table, migration 059)
- Q13 search shape → both (in-place page filter on active view + global Cmd-K palette unchanged)

Implementation impact: PR 2 grows from ~700-900 LoC to ~1300-1700 LoC because
m chose (c) full drag-rearrange over (b) localStorage-only. New backend
service CardLayoutService + JSON validator + 5 endpoints; frontend gets
edit-mode chrome + HTML5 drag-and-drop + layout dropdown. Optional internal
PR 2a / PR 2b split if review feels heavy (read-only cards first, then
customisation).

PR 1 (tree + chips + pin + search) is unchanged at ~1100-1400 LoC.
Other 17 recommendations stay READY-FOR-REVIEW per the dogma.
2026-05-07 22:11:18 +02:00
m
397a9b1854 docs(t-paliad-149): inventor design — projects page redesign (tree-first + chips + pinning + cards)
Three view modes (Tree default | Cards | Flat), chip filter row, pinning,
search-as-tree-filter, mobile drill-in. Cards view (m's addition) has
configurable content + per-user prefs in localStorage v1.

Q15 decision (delegated to inventor): bespoke /projects, NOT Custom Views.
Custom Views is event-shaped; projects are scope, not events. Adding
SourceProject + ShapeTree to t-144's substrate would break shape ⊥ source
orthogonality. Reversible if a unifying abstraction emerges.

Two-PR phasing: PR 1 = tree + chips + pin + search (~1100-1400 LoC,
migration 058). PR 2 = Cards view + customisation modal + cards-preview
endpoint (~700-900 LoC, additive on PR 1).

4 surfaced questions for m via AskUserQuestion: default landing + view mode,
cards default content, cards customisation scope, search shape. Other 17
questions answered with recommendations + rationale per the dogma (make it
easy for m).

Awaiting m's go on §12 questions before locking. NO coder shift until lock.
2026-05-07 22:05:44 +02:00
m
efaa7787af Merge remote-tracking branch 'origin/main' into mai/kepler/inventor-profession-vs 2026-05-07 22:00:26 +02:00
m
d24f73358c design(t-paliad-146): re-scope to PoC track — m-only + monitoring
m's reframing 2026-05-07 20:56: Paliadin is "mostly for myself now
but can be expanded — monitoring use." Two-stage shape replaces the
single-PR production-v1:

- Phase 0 (PoC): tmux-Claude pattern lifted from goldi/mVoice
  (mVoice/server.py:250-380). Claude Code window in a long-lived
  tmux session, prompts via tmux send-keys -l, response via
  /tmp/paliadin/{turn_id}.txt tail-f → SSE relay. Single user (m),
  m's laptop only (PALIADIN_ENABLED=false on prod). ~600-900 LoC,
  ~1 day. Migration 057 (PoC variant) stores full prompt + response
  for monitoring — no redaction at this scope.
- Phase 1 (production v1): the original §2-§6 Anthropic API design,
  GATED on PoC success per §0.5.7 expansion criteria (≥3 turns/wd,
  ≥50% tool-use rate, 4 weeks).

§0.5 (new) inserted as the load-bearing PoC spec. §7 leads with the
two-stage frame. §8.5 questions split into PoC-relevant (Q-PoC-1..6)
and production-v1-deferred. youpc case-law lookup promoted to
Q-PoC-6: m himself does case-law research, so include it from day
one (cross-schema SELECT into data.judgments is technically trivial
since paliad and youpc share the same Postgres).

What we drop for PoC: Anthropic API client, BYO-AI, rate limit,
token caps, multi-user RLS edge cases, /admin cost dashboard,
compliance disclosure, most i18n keys.

What we keep: system prompt voice, citation discipline (best-effort),
visibility gate (Claude is required to use paliad.can_see_project()
in queries), /paliadin surface, SSE shape, audit table.

The two-stage shape protects against the t-145 pattern: ship cheap,
observe, decide. No 4500-LoC investment based on m's gut feel about
adoption.
2026-05-07 20:59:46 +02:00
m
dc7c807725 design(t-paliad-146): Paliadin — in-app AI buddy
Inventor design pass for the Paliadin: a Claude-backed conversational
assistant grounded in the user's own paliad data + paliad's static
reference (courts, glossary, deadline rules, Fristenrechner concept
tree). Long-lived in-process Go service that calls Anthropic's
Messages API directly with tool use; every tool is a thin shim over
an existing service (Dashboard / Project / Deadline / Appointment /
Court / Glossary / DeadlineRule). RLS / visibility inherited from
those services — Paliadin literally cannot see what the caller cannot.

Five coordinated sub-designs answer the issue's 20 open questions:
  A. LLM architecture + tool-use + prompts (§2)
  B. Data access + RLS + PII (§3)
  C. UX (§4)
  D. Token budget + cost + audit (§5)
  E. Phasing (§7)

Phase 1 v1: /paliadin full page + sidebar entry, SSE stream of
Anthropic, 7 read-only tools, session-only history, 30/hour user cap
+ 1000/hour global cap, audit row per turn (metadata only — no
transcript), 4k input + 2k output token caps, no avatar/mascot, no
proactive onboarding. Migration 057 introduces paliadin_turns +
paliadin_rate_limit. Single PR, ~3500-4500 LoC.

mlex / /lex-* reuse: shape (system-prompt voice, tool-catalog idea,
citation style) — NOT code. mLex is a workspace, not a Go/TS repo;
the /lex-* skills drive Claude against youpc's MCP and cannot be
embedded in a paliad service.

Premise verifications surfaced one CLAUDE.md doc-bug (the
ANTHROPIC_API_KEY "Reserved for Phase H — do not set" row needs to
flip in the implementation PR — Paliadin un-defers it).

12 open questions for m in §8.5 — Anthropic key choice (personal vs
HLC enterprise), default model (Sonnet vs Haiku), surface
(/paliadin page vs drawer), mascot phase, 2-PA sanity check before
locking scope, etc. Same adoption-risk concern that just parked
t-paliad-145 — Paliadin's edge over open-Claude-in-another-tab is
data grounding, which only works if v1 makes it visible (citation
chips + tool-call evidence + tagline).

STOP after design. Awaiting m go/no-go before coder shift.
2026-05-07 20:45:31 +02:00
m
1eb43ceb6b design(t-paliad-148): split project_teams.role into firm-level profession + project-level responsibility
Inventor design doc (kepler) for issue m/paliad#6. Splits the conflated
project_teams.role column into two axes:

- paliad.users.profession (firm-wide, drives t-138 approval ladder)
- paliad.project_teams.responsibility (per-project, lead/member/observer/external)

Approval ladder evaluated as tuple: profession_level if responsibility
opens the gate (lead/member), else 0. Policy grammar from t-138 stays
single-valued.

Verified live state: project_teams=3 rows (all 'lead'), partner_unit_members=20
rows (all default 'attorney'). Backfill is essentially trivial; risk is the
SQL rewiring (4 sites in approval_service.go, 2 in derivation_service.go,
2 in reminder_service.go) — all mechanical.

12 open questions from issue body answered with recommendations + rationale +
alternatives. Awaits m's go before any coder shift.

DESIGN READY FOR REVIEW.
2026-05-07 20:45:07 +02:00
m
dd4f563212 design(t-paliad-145): local chat for teams
Inventor design for in-app messaging surface inside paliad. Three
coordinated sub-designs (surface/visibility, real-time/content/
persistence, integration with existing surfaces) answering all 21
issue-body questions plus 11 inventor follow-ups.

Recommendations:
- Per-project chat + DMs in v1; per-deadline/termin/partner-unit defer
- Per-thread visibility = can_see_project (existing predicate, derivation-aware)
- SSE for real-time (single new long-lived endpoint)
- New: paliad.chat_threads, chat_messages, chat_reads, chat_thread_participants, chat_mentions
- New project_teams.chat_access flag, default OFF for local_counsel/expert
- Approval auto-post into project chat (one auto-event class only)
- Markdown subset, @mentions, entity-refs (#frist-, #projekt-, #termin-, #approval-)
- 5-min author edit window, soft-delete by author or admin
- In-app sidebar badge in v1; PWA push + email digest deferred Phase 2
- Both top-level /chat and per-project Chat tab
- NOT a 5th source in Custom Views (chat is conversation, not events)

Trade-off flagged: adoption risk against existing Slack/WhatsApp.
Recommend m sanity-check with PA colleagues before locking v1 scope.

Migration 057. Awaiting m go/no-go before coder shift.
2026-05-07 16:03:05 +02:00
m
956ff10e4d design(t-paliad-144): m signed off + Q4 correction (3 shapes, not 4)
m's lock-in 2026-05-07: agree with all recommendations on Q1-Q18 and §10
Q19-Q27, with one correction on Q4: "activity" is a content selection
(sources + filters), not a render shape. Folded into `list` shape with
density: "compact" + actor/time columns. Shape ⊥ source — any source can
render in any shape.

Render shapes for v1: list / cards / calendar (3, was 4).

PR split decision (delegated to inventor): A1 backend substrate + API
(no UI change, ~1800 LoC, smoke via curl) → main → A2 frontend Custom
Views UI (~1600 LoC, additive on A1) → main.

Status flipped DRAFT → LOCKED. Inventor → coder transition initiated.
2026-05-07 12:36:05 +02:00
m
5c263102e3 design(t-paliad-144): data display model — additive Custom Views + render-shape switcher
Inventor pass on m's data-display rethink (m/paliad#5). Three coordinated
sub-designs in one doc, scoped to m's locked direction (additive, subsume
unified inbox, sidebar Meine Sichten group, in-page render-shape switcher,
paliad-only).

Recommended substrate: 4-source ViewService (deadline + appointment +
project_event + approval_request) returning discriminated ViewRow.
Recommended filter grammar: structured JSON spec with server-side validator.
Recommended render shapes for v1: list / cards / calendar / activity (defer
kanban, connections-graph, distinct-timeline). Recommended persistence:
new paliad.user_views table (migration 056), RLS-bounded to caller, code-
resident system defaults (no is_system flag).

Phasing: Phase A ships substrate + Custom Views standalone (~3400 LoC,
no system page changes); Phase B refactors /agenda/events/inbox/dashboard
internals onto the substrate later. Coexists transparently with t-139
(hierarchy aggregation in flight on noether's other branch) and t-138
(approvals shipped).

27 questions answered (18 from issue body §1–§5 + 9 inventor follow-ups
in §10 for m's call before coder shift).
2026-05-06 17:25:44 +02:00
m
2d06cdf20e Merge: t-paliad-139 Phase 1 — /projects/{id} aggregation bug fix (use projectDescendantPredicate on 3 legacy narrow methods + frontend toggle + attribution chip) 2026-05-06 16:29:14 +02:00
m
2247c0707d docs(t-paliad-139): design lock — m signed off on all 19 §6 recommendations
m's go/no-go pass at 2026-05-06 15:58: "I agree with all your recommendations
- go." All 19 questions in §6 lock as the recommended answers verbatim.

§0 status flipped from READY-FOR-REVIEW to LOCKED. New "Locked m decisions
on §6" subsection captures the highlights inline so future readers don't
have to scan the whole table to know what's pinned.

§13 end-of-design line updated to reflect the lock.

Implementation phasing (§7) unchanged:
- Phase 1: bug fix on the 3 narrow service methods (no schema, ~400 LoC,
  ships standalone, closes the user-visible /projects/{id} "Keine Fristen"
  bug).
- Phase 2: migration 055 (partner_unit_members.unit_role,
  project_partner_units, extended can_see_project()) + DerivationService +
  frontend Team-tab subsections + /admin/partner-units unit_role tagging
  + project /settings/team Partner Units section. Independent of t-138.
- Phase 3: approval extension — canApprove + inbox SQL widening for
  derived_peer decision_kind. Gates on cronus's t-138 (currently on
  mai/cronus/inventor-dual-control @ b3401ec) landing on main.

Inventor parked. Awaiting head's coder-shift assignment.
2026-05-06 15:59:37 +02:00
m
6c41550945 docs(t-paliad-139): inventor design — hierarchy aggregation + effective team + PA derivation
Three coordinated sub-designs in one doc, scoped to m's locked constraints
(2026-05-06):

1. Surface-by-surface aggregation policy. Bug surface fix:
   /projects/{client_id} renders "Keine Fristen" because
   DeadlineService.ListForProject + AppointmentService.ListForProject +
   ProjectService.ListProjectEvents all WHERE project_id=$1 exact-match
   instead of walking paliad.projects.path descendants. The shipped t-124
   contract (projectDescendantPredicate, deadline_service.go:133 etc.)
   already aggregates correctly on the union endpoints — three legacy
   narrow paths just bypass it. Per-surface decision table for events /
   deadlines / termine / Verlauf / project tree counts / dashboard /
   CalDAV / email / search.

2. Effective-team semantics. Three structural gaps in the issue's
   premise (verified against schema):
   - No project↔unit junction (partner_unit involvement on a project).
   - No PA/lawyer distinction in partner_unit_members (no role column).
   - No lawyer↔PA pairing anywhere — Q11's "where is it stored" → nowhere.
   Proposes:
   - paliad.partner_unit_members.unit_role (lead|attorney|senior_pa|pa|paralegal),
     unit-scoped not firm-wide so 3-axis principle holds.
   - paliad.project_partner_units junction with derive_unit_roles[]
     (default {pa, senior_pa}) + derive_grants_authority bool.
   - Compute-on-read derivation via extended can_see_project() — no
     materialised state, no drift.
   - Display-effective vs visibility-effective team are different sets;
     rename ListEffectiveMembers to ListVisibilityEffectiveMembers + add
     ListSubtreeMembers.

3. Approval policy × hierarchy × derivation. Coordinates with t-138
   (cronus, mai/cronus/inventor-dual-control @ 7d1ddb9):
   - Q10: keep cronus's no-auto-inheritance, harden UX with a "Eltern-
     Politik (zur Information)" panel showing parent rules without
     applying.
   - Q12: derived members visibility-only by default; per-(project, unit)
     opt-in flag derive_grants_authority. When opted in, decision_kind
     extends with derived_peer for honest audit chronology.
   - canApprove + inbox SQL extension shape spec'd; coordinates with
     cronus's t-138 §3.4 / §7.4.

Locked m decisions surfaced in §0:
- Behaviour is surface-specific.
- Effective Team of a Client = direct ∪ descendants ∪ partner-unit-derived.
- PA derivation = unit-on-project trigger.
- Derivation honesty: annotated everywhere.
- paliad-only scope.

19 design questions with proposed answers in §6 for m to lock. Migration
055 specced (§5). Implementation phased into 3 PRs (§7) — Phase 1 bug fix
ships standalone if m wants quick win.

Inventor parked. Awaiting m go/no-go before coder shift.
2026-05-06 15:38:41 +02:00
m
7d1ddb9b84 docs(t-paliad-138): inventor design — dual-control approvals (4-eye)
Locked design for 4-Augen-Prüfung on Fristen + Termine. m-confirmed
decisions on all 11 open questions:

- Qualification gate reuses paliad.project_teams.role per-project
  (no new firm-wide axis). Adds new value `senior_pa` to the enum.
- Strict ladder: lead > of_counsel > associate > senior_pa > pa.
  Default required_role = associate. Per-project override allows pa-
  approves-pa or senior_pa-tier escalation.
- Per-(project, entity_type, lifecycle_event) policy grammar — up to
  8 settable rows per project in paliad.approval_policies.
- Edit-trigger allowlist = date-bearing fields only (Frist due_date /
  original_due_date / warning_date; Termin start_at / end_at).
- Write-then-approve: row mutates immediately, approval_status flips
  between approved/pending/legacy. Delete is the one stage-then-write
  exception (hard-delete on approve, restore on reject).
- Refuse + global_admin override on single-qualified-approver deadlock.
- Pending state visualised everywhere — list views, agenda, dashboard
  traffic-light, project detail, CalDAV-synced calendars (`[PENDING] `
  title prefix), email reminders.
- Bell + /inbox page with two tabs (zur Genehmigung / meine Anfragen).
- Operational paliad.approval_requests + audit lifecycle written to
  existing paliad.project_events (4 new event_types per entity).
- RLS = same can_see_project predicate; service layer enforces the
  approve/reject action gate. CHECK constraint blocks self-approval.
- Mark-legacy backfill: approval_status='legacy' on existing rows;
  next mutation flows through the gate.

Implementation phasing: single migration 054 + 8-commit PR plan
covering schema, service, wiring, policy authoring page, inbox,
pending pills, CalDAV/email integration, Verlauf rendering.

Inventor parked. Awaiting m go/no-go before any coder shift.
2026-05-06 14:58:01 +02:00
m
bf06499d9c docs(t-paliad-122): inventor design — courts entity + per-country holidays
Archives m's locked design call (2026-05-05 18:51) plus live-codebase
verification: paliad.holidays.country exists per-country; paliad.courts
does not (must create); proceeding_types.jurisdiction is regime not
country (do not remove); 41 hand-curated courts already in
internal/handlers/courts.go ready to seed; HolidayService.loadYear is
country-blind today (latent bug); germanFederalHolidays merge is
hardcoded (must become country-conditional). Task stays ON-HOLD until a
non-DE forum or EPO closure-day calendar comes into scope.
2026-05-05 23:51:47 +02:00
m
30ac337a78 docs(t-paliad-136): Fristenrechner v4 inventor design
v4 addresses three concerns from m on 2026-05-05 in priority order:

1. Card-click → compute deadline → add-to-project (v3 cards were dead-ends).
2. Filter narrowing bug — slug → concept_id allow-list dropped per-leaf
   proceeding_type_code, so picking "UPC infringement opposing party"
   leaked DE/EPA/DPMA pills. Confirmed via DB query: 25+ leaves overbroad.
3. RoP-rigorous tree audit: 6 confirmed seed errors (Hinweisbeschluss
   DE_INF mismap, notice-of-defence-intention UPC_INF mismap, three
   cost-appeal notice-of-appeal mismaps, request-for-discretionary-review
   needs UPC_APP_ORDERS narrowing), plus reply-to-cross-appeal coverage
   gap and bescheid-mit-frist orphan.

Plan splits into three independent phases (A: filter fix, no schema; B:
card-click flow + new calculate-rule endpoint; C: taxonomy migration 052
without RAISE EXCEPTION coverage gates per last night's outage lesson).

Inventor → coder gate held: no production code in this commit.
2026-05-05 12:11:36 +02:00
m
f40b652d01 Reapply "Merge: t-paliad-133 — Fristenrechner v3 (Pathway A/B fork + B1 decision tree + B2 forum filter + retire legacy tabs)"
This reverts commit 5bd17de732.
2026-05-05 11:18:38 +02:00
m
5bd17de732 Revert "Merge: t-paliad-133 — Fristenrechner v3 (Pathway A/B fork + B1 decision tree + B2 forum filter + retire legacy tabs)"
This reverts commit f7d72ff1d3, reversing
changes made to 1ea983f0c7.
2026-05-05 11:17:58 +02:00
m
7e363ac01d design(t-paliad-133): lock v3 design with m's answers (10:33)
m approved all 12 open questions in one batch. Locked spec:

1. Legacy tabs RETIRED in Phase E.
2. Decision-tree depth UNLIMITED (was: 4 max). Property of
   event_categories data, not hard-coded.
3. Clickable breadcrumb for navigation.
4. Partial-path bookmarks (?b1=...).
5. Multi-select forum filter, default 1 selected.
6. Path-matching cards at each step. Renamed "Pfad lockern" →
   "Schritt zurück".
7. Emojis only, no separate colour treatment.
8. Forum buckets simplified to 10: UPC CFI + UPC CoA + DE LG/OLG/
   BGH/BPatG + EPA Erteilung/Einspruchsabt./Beschwerdek. + DPMA.
   m collapsed UPC LD/CD into UPC CFI (rules identical).
9. B1↔B2 share filter state.
10. Single branch / sequential commits / one final merge.
11. Party perspective default Claimant/Proactive; localStorage
    remembers last-used. URL ?my_side= + ?appeal_filed_by=.
12. Bilateral rules tagged via new is_bilateral column on
    deadline_rules; mirroring only when flagged.

Maria's two scope additions folded in:
- Court-system granularity for forum filter (clarification).
- Party-perspective selector absorbing t-paliad-132.

Implementation now starting on this branch.
2026-05-05 10:37:44 +02:00
m
2ed476dc64 design(t-paliad-133): Fristenrechner v3 — Pathway A vs Pathway B fork
m's 2026-05-05 brief restructures the page surface that v2 (t-paliad-131)
shipped. The current Fristenrechner stacks three blurred entrypoints —
Phase D search bar, Verfahrensablauf tile grid, "Was kommt nach…" tab.
v3 forks the page so each mental model has its own entry:

- Pathway A — Verfahrensablauf informieren (Browse): existing wizard.
- Pathway B — Frist eintragen aufgrund Ereignis (Event → Deadline),
  subdivided into:
  - B1 Entscheidungsbaum: data-driven button cascade (CMS-Eingang →
    Vom Gericht → Hinweisbeschluss → cards), max 4 deep, back +
    breadcrumb + bookmark URLs.
  - B2 Filter / Suche: Phase D concept-card search PLUS new
    Gericht/System multi-select chip filter (Q8 reversal). All filters
    AND-narrow.

Adds two new tables (Phase A — purely additive):

- paliad.event_categories — recursive taxonomy tree, with step
  questions on non-leaf nodes.
- paliad.event_category_concepts — leaf → concept junction with
  optional proceeding_type_code narrowing.

Existing data layer (deadline_concepts, deadline_rules, trigger_events,
deadline_search matview) untouched. Phase D search handler gains
?event_category_slug= and ?forum= query params; forum-bucket map lives
in Go (UPC / DE LG / DE OLG / DE BGH / DE BPatG / EPA / DPMA).

Phasing: A (data) → B (landing fork) → C (B1 tree) → D (B2 forum
filter) → E (retire legacy tabs, gate-gated). Each phase independently
shippable.

Open questions for m at §10: retire legacy tabs, decision-tree depth,
back/breadcrumb, partial-path bookmarks, multi vs single-select forum,
all-vs-path-matching cards per step, austere icons, 7 forum buckets,
B1↔B2 state-sharing, PR phasing.

Inventor parked. Next: m's go/no-go before coder shift.

Cross-references docs/plans/unified-fristenrechner.md (v2, shipped) for
concept-layer / search-backend / coverage details v3 inherits unchanged.
2026-05-05 10:21:20 +02:00
m
20eaa9bba4 design(t-paliad-131): v2 — flip slug rule (EN for shared) + drop flag_param
m's revisions (23:36):

- Q1 corrected: EN slug for shared concepts too (klageerwiderung →
  statement-of-defence, replik → reply-to-defence, berufungsfrist →
  notice-of-appeal, einspruchsfrist → opposition, wiedereinsetzung →
  re-establishment-of-rights). DE slug only for German-law-only
  concepts (nichtzulassungsbeschwerde, versaeumnisurteil-einspruch,
  hinweisbeschluss-stellungnahme).

- Q4 simplified: drop the customizable-extension flag_param mechanism.
  Replace with a generalised "user can override any computed date,
  downstream re-anchors off it" capability. CalcOptions gains
  AnchorOverrides map[string]string; tree-walk consults it before the
  computed-date map. UI gives each row a click-to-edit date affordance
  (also unlocks court-set decision dates being entered post-hoc, which
  the existing IsCourtSet placeholder UX has been hinting at). PatG §82
  seed stays at 2 months static; user-set extensions handled by inline
  date override, not by a flag_param mechanism.

  Cleaner. No new DB column. Generalises beyond extensions to any case
  where the user knows the real date better than the calculator's
  projection.
2026-05-04 23:38:23 +02:00
m
94ebc1d043 design(t-paliad-131): v2 — m's answers to the 8 v2 open questions locked
- Q1 concept slug naming: mixed convention. EN slug for UPC/EPC-native
  concepts (application-to-amend, request-for-discretionary-review).
  DE slug for German-only concepts (nichtzulassungsbeschwerde,
  versaeumnisurteil-einspruch). DE slug for SHARED concepts that exist
  in both DE and UPC/EPC (klageerwiderung, replik, berufungsfrist,
  einspruchsfrist, wiedereinsetzung) because m works primarily in
  German and the slug is internal/maintenance-facing only.
- Q2 EU.EPÜ confirmed for EPÜ namespace.
- Q3 PatG §111(1) 1mo→3mo confirmed for Phase B3.
- Q4 PatG §82(1): shape (b) — 1mo base + with_extension flag with
  CUSTOMIZABLE extension duration (default 1mo). New flag_param
  mechanism on flag-conditioned rules: CalcOptions.Flags becomes
  map[string]int; rules with flag_param_code add caller's param to
  duration. UI shows number input next to checkbox. Generalises to
  PatG §75 etc. Phase A5 picks up the calculator extension; Phase B3
  hooks PatG §82.
- Q5 Full Appeal Chain: multiple date inputs per stage, no inter-stage
  gap guessing. Stage N's downstream deadlines render as IsCourtSet
  placeholders until user enters Stage N-1's terminal decision date.
- Q6/Q7/Q8 confirmed as drafted.

§5.2.2 PatG §82 row updated to reflect flag-based shape. §4.4 concept
slug examples expanded with the mixed-convention rule rendered
explicitly. §7 Phase A5 added for the flag_param calculator change.
2026-05-04 23:33:33 +02:00
m
79f09006fc design(t-paliad-131): v2 — incorporate m's go-direction (Unifier shape, concept cards, no tab subsumption)
Significant restructure after m's 10 answers (relayed via head 23:10):

- Augment, not replace — search bar at top + existing tile grid stays as
  browse fallback. Both existing tabs stay live. Phase E (subsumption)
  dropped.
- Unifier shape: new paliad.deadline_concepts layer above existing
  deadline_rules; deadline_rules gains concept_id FK + structured
  legal_source. condition_flag scalar→array (Q3) for AND-of-flags
  semantics on UPC_REV (with_amend ∥ with_cci).
- Search hits as ONE card per concept with proceeding pills inside (NOT
  a flat list of one-per-proceeding hits). Card body: pills [UPC R.23.1
  3mo] [LG §276.1 6w] [BPatG §82.1 1mo] [EPA R.79.1 4mo] etc.
- Structured legal_source codes: UPC.RoP.23.1, DE.ZPO.276.1,
  EU.EPÜ.108, DE.PatG.111.1 — parseable, filterable, indexed.
- "Vollständige Instanzenkette" checkbox synthesises LG→OLG→BGH (or
  BPatG→BGH) timeline as one tree at render-time; data stays per-
  instance.
- Forum filter dropped (Q8). Filters now: Verfahrensart / Partei /
  Rechtsquelle.
- Court-set placeholders ("Verhandlung", "Entscheidung",
  "Zwischenverfügung") surface as searchable triggers (Q10).
- Columns-view sequence preservation (Q9) flagged but punted to a
  separate follow-up task — t-paliad-129 column renderer must respect
  sequence_order even on undated court-set events.

8 remaining open questions for m (concept slug convention, EPÜ
namespace, PatG §82(1) modeling, Full Appeal Chain anchor handoff,
quick-pick chip seed, etc.).
2026-05-04 23:21:28 +02:00
m
355e718516 design(t-paliad-131): unified Fristenrechner — search-by-anything + complete coverage
Inventor design doc at docs/plans/unified-fristenrechner.md.

Covers:
- Single search bar UX over both deadline_rules (proceeding-tree) and
  trigger_events (event-driven) backends, federated via a materialised
  view with pg_trgm indexes.
- Faceted filters: forum, proceeding type, party, legal source.
- UPC counterclaim cross-flows missing today (R.29(a)/(d)/(e), R.30,
  R.32, R.43.3, R.49(2), R.51, R.52, R.55, R.56) — verbatim citations
  pulled from data.laws_contents (UPCRoP).
- German PatG/ZPO gap audit: missing OLG + BGH-Revision + BGH-NZB cycles,
  ZPO §339 Versäumnis, §521 Berufungserwiderung, PatG §111 (likely 1mo→3mo
  fix), DPMA Einspruch + Beschwerde. EPA gaps: R.116, R.79(2/3), R.106
  Überprüfung, Wiedereinsetzung × 3.
- Phased migration plan A–E, each independently shippable.
- 10 open questions for m's go/no-go before coder shift.

No code changes; awaiting m's review.
2026-05-04 23:11:16 +02:00
m
25efce0c76 design(t-paliad-109): unify Fristen + Termine as filtered Events views
Design doc only — no code touched. Recommends keeping /deadlines + /appointments
URLs but rendering one EventsPage component (smallest-diff Option A1), backed
by a new EventService that delegates to existing Deadline/AppointmentService
(Option B1). Two-rail bucket summary on Beides (5 deadline + 3 appointment),
detail pages stay separate, /agenda timeline left alone. §F lists 17 questions
gating m's greenlight, including a premise correction: the brief described
/agenda as the appointment list — actually it's a pre-existing cross-type
timeline; the appointment list is /appointments.
2026-05-04 13:14:52 +02:00
m
460736ad1e refactor(t-paliad-092): rename Go module path patholo → paliad
F-6 from t-paliad-074 architecture audit. The Gitea repo was renamed
m/patholo → mAi/paliad → m/paliad, but go.mod still declared
`mgit.msbls.de/m/patholo` and every internal import echoed the
pre-rebrand name.

Sweep:
- go.mod: module path → mgit.msbls.de/m/paliad
- All *.go files: imports rewritten via sed
- README.md, docs/design-kanzlai-integration.md: mAi/paliad → m/paliad
- Frontend issue-reference comments (mAi/paliad#N → m/paliad#N) in
  i18n.ts, theme.ts, sidebar.ts, app.ts, Sidebar.tsx, PWAHead.tsx,
  global.css

Verified: go build/vet/test ./... clean, bun run build clean,
no remaining mgit.msbls.de/m/patholo or mAi/paliad references
outside docs that intentionally describe the rename history.
2026-04-30 16:46:31 +02:00
m
75867b2a3e design(t-paliad-088): resolve open questions per m's calls
m greenlit all 7 open questions on 2026-04-30 12:23. Notable changes
from the initial draft:

- Submissions are explicitly the primary Event-Type use case, not a
  secondary discriminator. m: "those are the event types I mean,
  mainly". Deferring a separate paliad.submissions table stands.
- /deadlines + /agenda Typ filter is MULTI-SELECT (UNION across
  selected types, AND-intersected with Status/Projekt). New
  EventTypeMultiSelect component spec'd in §4: trigger button styled
  like the existing <select>s, popover with search + grouped checkbox
  list. Status/Projekt stay single-select.
- Firm-wide Event-Type creation OPEN to any authenticated user. RLS
  insert policy simplified to created_by=self. Admins moderate via
  archive. Mitigation: duplicate-warning in the add modal. Follow-up
  t-paliad-089 flagged for admin moderation panel.
- Broader-scope seeds confirmed (UPC + EPO + DPMA + DE + contract).
- §12 rewritten as a resolution table.
2026-04-30 12:26:53 +02:00
m
413a40c808 design(t-paliad-088): Event Types for deadlines + submissions
Standalone paliad.event_types table with nullable FK on paliad.deadlines,
seeded from a curated subset of paliad.trigger_events (UPC submissions +
decisions) plus hand-written EPO/DPMA/DE-national/contract entries.
Picker on /deadlines/new + edit modal with grouped options + inline
custom-add modal (private types for any user, firm-wide gated to
global_admin). Filter <select> on /deadlines (matching existing
Status/Projekt pattern, not pills) and pill-row on /agenda. Submissions
are NOT a separate entity — category='submission' on event_types carries
the discrimination until a real Schriftsatz-Verwaltung is built.

Awaiting m's go/no-go on §12 before any implementation.
2026-04-30 12:03:40 +02:00
m
aab82d4aca docs(t-paliad-084): Fristenrechner completeness audit vs youpc deadline calc
Read-only research deliverable. Compares paliad's 9-proceeding-type
Fristenrechner ruleset (52 public rules in deadline_rules) against
youpc's 70-deadline event-driven calc (data.deadlines + data.events).

Top findings (§1 executive summary):
- youpc covers 64 distinct UPC RoP rule codes; paliad covers ~5
- The two tools answer different questions (timeline-by-procedure vs
  search-by-trigger-event) — biggest gap is structural, not data
- Paliad's holiday system is materially better; youpc's defaults are empty

Critical bugs surfaced (§4):
- Public UPC_INF Fristenrechner ignores CCR-conditional rejoinder
  duration (always uses 029.c/1mo, should be 029.d/2mo when CCR filed).
  KanzlAI internal INF type already wires this; public type doesn't.
- UPC_APP grounds chained off notice instead of decision date,
  giving wrong dates when notice is filed early
- EP_GRANT publish chained off filing instead of priority date
- Rule_code format inconsistent across migrations (RoP 23 vs RoP.023)

Recommendations ranked across 5 tiers (§6) for m to review.
Open product decisions in §7. No code changes.
2026-04-30 10:36:30 +02:00
m
3da11bd798 chore(t-paliad-081): doc + dead-code batch (F-5/F-10/F-11/F-15/F-16/F-17/F-18)
Bundle of small audit findings, all doc-only or dead-code:

- F-5: refresh stale escalation-contact comment in models.User —
  Settings UI dropdown shipped 2026-04-29 (t-paliad-066).
- F-10: add "OBSOLETED by migration 018" note to migrations 004/005/006
  so readers stop hunting for the live shape in obsolete files.
- F-11: document the data-loss semantics of dropping
  paliad.partner_unit_events on the 027 down — audit rows are
  append-only telemetry, accepted loss on rollback.
- F-15: drop the patholo_session / patholo_refresh cookie fallback
  added during the 2026-04-16 rebrand. Active users have long since
  been re-authed through the upgrade path; inactive users hit the
  normal /login flow.
- F-16: refresh stale /api/departments comment in team_pages.go to
  /api/partner-units (renamed in t-paliad-070).
- F-17: move internal/db/migrations/_dev/mock_supabase_auth.sql to
  internal/db/devtools/ so a future loosening of the //go:embed
  pattern can't accidentally ship the dev-only fixture.
- F-18: update docs/project-status.md "Audit polish-2" entry — the
  batch shipped via t-paliad-067 / 068 / 073, follow-ups are now
  tracked under the 2026-04-30 re-audit + t-paliad-074.

go build / vet / test clean.
2026-04-30 03:42:25 +02:00
m
61766161b7 docs(t-paliad-074): architecture improvement audit 2026-04-30
Read-only audit after the 9-merge push of t-paliad-066..073. Surfaces
18 findings across 7 lenses (service boundaries, naming, frontend↔
backend contract, migrations, tests, dead code, doc drift) plus three
architecture observations carried forward from the 2026-04-18 audit.

Top 3 punch list:
- F-1 (🔴 active): AdminDeleteUser SQL writes to dropped tables
  paliad.department_members / paliad.departments. Live production bug,
  blocks admin user-delete. user_service.go:768,773. Missed by
  t-paliad-070 rename sweep (last touched 2026-04-27, predates rename).
- F-13 (🔴 active): 7 live-DB integration tests skip silently when
  TEST_DATABASE_URL unset, no CI exists. Same pattern that masked the
  t-paliad-069 reminder bug for ~24h and that hid F-1 above.
- F-2 (🔴 active): visibility predicate inlined in 10 hot-path SQL
  sites despite central helper in visibility.go (dashboard/agenda/
  reminder/team/deadline service). Inlined sites silently skip the
  global_admin shortcut.

No code changes — head sequences dispatch.
2026-04-30 02:53:50 +02:00
m
832104af9e Merge remote-tracking branch 'origin/main' into mai/cronus/partner-units-rename
# Conflicts:
#	frontend/build.ts
#	frontend/src/admin.tsx
#	frontend/src/client/i18n.ts
#	internal/handlers/handlers.go
2026-04-29 22:17:32 +02:00
m
633ce5a9fe design(t-paliad-070): incorporate m's answers — full partner_unit rename
m's 21:44 answers expanded the rename scope and resolved all 5 open Qs:
- Naming: partner_unit everywhere (not 'department')
- API + URL rename too: paliad.departments → paliad.partner_units,
  /api/partner-units, /admin/partner-units
- Settings admin section: removed
- Audit emit: in this PR (paliad.partner_unit_events table)
- users.dezernat: dropped entirely (not renamed)

Migration 026 now does: best-effort second seed of department_members from
dezernat free-text → DROP COLUMN → rename departments + department_members
tables to partner_units + partner_unit_members → rename junction column to
partner_unit_id → rename constraints/indexes/policies → create
partner_unit_events audit table with RLS.

Single tx, exception-trapped renames for idempotency on freshly-provisioned
DBs.

Onboarding form: free-text input replaced with a partner-unit <select> that
inserts a membership row in the user-create tx. Settings profile loses the
free-text field.

PR strategy: still single PR, ~2200 lines net (heavier than v1 due to
structured-side rename + audit plumbing).
2026-04-29 21:50:27 +02:00
m
c4122bc265 docs(admin): t-paliad-072 — m greenlighted all 5 open Qs
DB-backed (Q1), subjects customisable with SYSTEMAUSFALL comment in seed
(Q2), base.html editable (Q3-A), 20-version retention (Q4), note field
kept (Q5). Coder shift unblocked from the inventor side.
2026-04-29 21:46:35 +02:00
m
9e216a4c44 docs(admin): design — admin email-templates editor (t-paliad-072)
DB-backed templates with embedded fallback, per-language split, full
edit/preview/version/restore loop. Subject moves from Go-built strings to
template-rendered. Five open questions for m parked at §8 — most loaded:
should base.html be editable or read-only.
2026-04-29 21:46:35 +02:00
m
1a89b0c490 design(t-paliad-070): partner units rename + admin departments page
Inventor design doc for the Dezernate→Partner Units rename and the new
/admin/departments management surface that replaces the placeholder card.

Key proposals:
- Single PR, single migration (026: users.dezernat → users.department).
- New /admin/departments page mirrors /admin/team aesthetic; lifts the CRUD
  out of /settings?tab=dezernat.
- User-facing label "Partner unit" / "Partner units" (same in DE+EN per m).
- Defer audit event emission to t-paliad-071 to keep this PR focused.
- Phase 2 follow-up: drop the free-text users.department duplicate once
  onboarding can pick from the structured registry.

Five open questions for m in §12 before coder shift starts.
2026-04-29 19:03:14 +02:00
m
8fe05fe696 Merge main into mai/cronus/audit-polish-2-triage (resolve i18n.ts collision with brunel's t-paliad-066 escalation keys) 2026-04-29 15:05:50 +02:00
m
bff2ec5107 feat(t-paliad-066): escalation contact dropdown in Settings → Notifications
Exposes paliad.users.escalation_contact_id (added in migration 025) via
the Benachrichtigungen tab so users can route DRINGEND/overdue
escalation to a specific colleague instead of the global_admins
fallback.

Service:
- UpdateProfileInput.EscalationContactID *string (empty = clear, matches
  Dezernat tri-state pattern). Server-side validation rejects self-
  pointer (also enforced by CHECK in migration 025) and unknown UUIDs.

Reminder read path:
- digestRow now carries owner.escalation_contact_id and the audience
  predicate adds the override. visibleForCategory's "global admin"
  branch suppresses when an override is set, so escalation does not
  fan out to the whole admin team. Test table extended with override
  cases (escalation contact sees overdue / DRINGEND, admin suppressed).

UI / client:
- New "Eskalations-Kontakt" section under Benachrichtigungen with a
  select populated from /api/users (excluding self, sorted by name).
  First option is the default-fallback marker; selecting it clears.
- savePrefs PATCHes escalation_contact_id alongside the existing
  reminder fields.

i18n: einstellungen.prefs.escalation.{heading,hint,default_option}
in DE + EN.

docs/project-status.md: flips the open follow-up to "shipped".
2026-04-29 13:59:30 +02:00
m
80fdab0963 docs(t-paliad-067): polish audit triage 2 — classify F-01..F-50, propose 3 PRs
Re-verified every BATCH finding from docs/audit-polish-2026-04-27.md
against the post-PR-B/D/E + palette + firm-name codebase.

- 18 OBSOLETE (already shipped via t-paliad-060/061/062/063/064/065).
- 26 KEEP — bundled into PR-1 (i18n leak sweep + activity log), PR-2
  (visual residue + small per-page polish), PR-3 (tab/chip/notes-hint
  consistency).
- 1 RESCOPED (F-20 colour fixed by palette sweep, structural rule
  consolidation still pending).
- 7 DEFER — design-call or redesign-class items (F-23, F-25, F-32, F-38,
  F-40, F-48, F-49).

Top-5 user-visible items ranked: F-07 dashboard activity narrative, F-15
red archive button, F-04 raw i18n key on /deadlines/new, F-12 AKTE column
header (rename residue), F-13 appointments AKTE cell collision.

DESIGN-READY GATE — head reviews before any coder shift.
2026-04-29 13:58:51 +02:00
m
ee1af9d9cf docs: move project status & history out of CLAUDE.md
CLAUDE.md should be AI guidance only. Phase status, shipped milestones,
open follow-ups, and the patHoLo→Paliad rebrand history are project
state — they belong in docs/, not in agent instructions.

Created docs/project-status.md with the full block. CLAUDE.md now points
to it.
2026-04-29 13:54:59 +02:00
m
93fdf10537 docs(t-paliad-064): reminder system redesign — design doc
Design for zero-overdue SLO, per-user bundled digests (one email per slot
per local-day), DRINGEND evening escalation, and global-admin escalation
on overdues. Includes the actual TZ root cause (alpine container has no
tzdata; LoadLocation silently falls back to UTC) and the embed-tzdata fix.

Awaiting m's go/no-go before implementation.
2026-04-28 12:55:33 +02:00
m
f8982a6628 docs(t-paliad-059): polish audit — 50 findings + top 10 ranked
Survey-only pass across the authenticated paliad surface as test admin
on Playwright at 1280×900 + 375 mobile spot-checks + DE/EN toggle.

Top 10 (best value-per-effort):
1. Strip "Hogan Lovells"/"HL" from public surface (landing, downloads)
2. Pick lime as the single primary green; retire forest-green
3. "Projekt archivieren" red → neutral (reversible, not destructive)
4. /admin/team search input has overlapping placeholder text (visible bug)
5. fristen.field.project.choose raw i18n key on /deadlines/new
6. Activity log leaks project_type_changed + "Type case → litigation"
7. lang="de" on date and time inputs (mm/dd/yyyy + 09:00 AM in DE UI)
8. "Akte" → "Projekt" residue on /deadlines + /appointments
9. Office values lowercased no-umlaut on /projects/{id}/team
10. Project tabs use href="#" — middle-click broken

Plus 40 other findings ranked by severity (broken/friction/polish) and
effort (≤30min/1-2h/half-day+). Suggested 5-PR batching.

41 screenshots in tests/screenshots-polish-2026-04-27/ covering every
sidebar entry + project detail tabs + DE/EN + mobile.

No code changes. Implementation tasks dispatched separately by head.
2026-04-27 18:46:05 +02:00
m
b34500ad31 feat(t-paliad-051): split paliad.users.role into job_title + global_role
Conflation: paliad.users.role was simultaneously job title (display only)
and global permission ('role=admin' checks across Go/SQL/JS). m wanted
to set his real job title ('Counsel Knowledge Lawyer') without losing
admin access — the t-paliad-050 admin-team UI even rejected role='admin'
on edit, so any UI-driven update silently demoted m.

Per m's three-axis principle ("firm roles are not project roles are not
tool roles"), this lands TWO orthogonal columns:

* paliad.users.job_title — free text, NULL allowed, display only.
  NEVER gates anything in code or SQL.
* paliad.users.global_role — CHECK ('standard'|'global_admin'),
  default 'standard'. The only thing that gates ops.

Migration 023:
* Drops NOT NULL + 'associate' default off the legacy role column
* Promotes role='admin' rows to global_role='global_admin'; clears
  their role text; sets m's job_title='Counsel Knowledge Lawyer'
* Renames role -> job_title with CHECK (job_title IS NULL OR <> '')
* Replaces can_see_project body with global_role='global_admin'
* CASCADE-rebuilds every RLS policy under canonical English names —
  with the historic u.role IN ('partner','admin') gates simplified
  to u.global_role='global_admin' only (job_title NEVER gates)

Code surface:
* internal/models/models.go: User.Role -> User.JobTitle (*string) +
  User.GlobalRole (string)
* internal/services/user_service.go: bootstrap (first row promoted to
  global_admin via pg_advisory_xact_lock(7346298141), unchanged constant);
  UpdateProfile drops role, accepts job_title only; AdminUpdateUser adds
  global_role with last-admin demotion guard (ErrLastGlobalAdmin);
  IsAdmin reads global_role
* Other services (dashboard/agenda/appointment/project/deadline/
  department/party/note/checklist_instance): pass user.GlobalRole into
  visibility predicates; partner-or-admin gates simplified to
  global_admin only
* Handlers: drop now-impossible ErrAdminBootstrapOnly cases;
  admin_users handles ErrLastGlobalAdmin -> 409
* department_service: SQL u.role -> u.job_title, DepartmentMember.Role
  -> JobTitle (*string)

Frontend:
* /api/me + Me interfaces ship {job_title, global_role}
* Onboarding form: 'Berufsbezeichnung / Job title' (job_title)
* Settings + admin-team forms: same renames + i18n updates
* Admin-team: new 'Berechtigung / Permission' column with
  'Standard'|'Global Admin' badge + dropdown editor; last-admin
  demotion guard at the UI layer
* Sidebar admin-section reveal: me.global_role==='global_admin'
* deadlines/deadlines-detail/projects-detail/notes: partner-as-permission
  gates dropped, only global_admin grants those operations

Tests:
* user_service_test: bootstrap promotes first user to global_admin,
  subsequent default to standard; AdminUpdateUser refuses to demote
  the last global_admin; IsAdmin reads global_role

Migration applied to ydb 2026-04-27. Live state verified:
* m: job_title='Counsel Knowledge Lawyer', global_role='global_admin'
* tester: job_title=NULL, global_role='global_admin'
* 29 stub colleagues: job_title='associate', global_role='standard'
2026-04-27 14:59:03 +02:00
m
aec150f1cd design(t-paliad-051): split paliad.users.role into job_title + global_role
Conflation today: paliad.users.role is simultaneously job title (display only),
global permission (`role='admin'` checks across Go/SQL/JS), and not-quite-but-
sort-of project_teams.role (already separated). m wants to record his real job
title ("Counsel Knowledge Lawyer") without losing admin access — the existing
admin-team UI even rejects role='admin' on edit, so any UI-driven update
silently demotes him.

Design proposes:
- Rename paliad.users.role -> paliad.users.job_title (free text, NULL allowed)
- Add paliad.users.global_role (CHECK IN ('standard','global_admin'),
  default 'standard')
- Single migration 023 does the rename, populates global_role from the old
  role, fixes m to job_title='Counsel Knowledge Lawyer', updates
  can_see_project, rebuilds RLS policies
- Inventory of every role='admin' call site across services/handlers/
  migrations/frontend bucketed by what migrates vs. what stays
- Keeps the existing 'partner' gate as job_title-driven (already broken in
  prod — "Partner" capital-P vs lowercase 'partner' check; documented as
  out-of-scope follow-up)
- Bootstrap rule (first user becomes admin) keeps the same advisory lock,
  flips global_role instead of role
- API surface: /api/me returns both fields; admin-team UI gets a Permission
  column with a global_role dropdown + last-admin demotion guard

Awaiting m greenlight before implementation phase.
2026-04-27 14:31:15 +02:00
m
c226a8b14d docs(palette): design Cmd/Ctrl+K command palette (t-paliad-044)
Choose Option B (full palette: actions + entities) over Option A
(keybind-only) because:

- pwa-baseline.md canon for multi-entity sites (paliad has 8 entity types).
- 80% of infrastructure already exists in search.ts (sectioned results,
  keyboard nav, i18n, debounce, abortable fetch).
- Patent-lawyer audience benefits from keyboard-first creation flows.
- A-then-B would mean revisiting in 2 weeks anyway.

Scope guardrails: no fuzzy lib, no MRU persistence, no extension API,
no per-action shortcut keys (Cmd+K only). 20-action catalog (12 navigate,
3 create, 4 toggle/action). Mobile gets palette via drawer (no new
BottomNav slot). Desktop preventDefault on Ctrl+K to suppress URL-bar.

Doc covers: trigger surface, UX shape (empty + filtered), action catalog,
component architecture (extend search.ts, new palette-actions.ts data
file), render flow, i18n keys, mobile considerations, acceptance,
implementation plan, risks. Implementer choice deferred to m.

Awaiting m's go/no-go before coder shift.
2026-04-26 15:02:31 +02:00
m
263a4605e3 docs(design): add PWA mobile BottomNav design (t-paliad-041)
Design only — no code changes. Five-slot bottom bar for phones (<768px),
center slot opens slide-up Quick-Add sheet (Frist / Termin / Projekt),
right slot reuses the existing mobile sidebar drawer. Tablets and
desktop unchanged. Awaiting m's review before implementation.
2026-04-26 01:59:31 +02:00
m
fabe32aa56 design: data model v2 — Mandanten + nestable Projekte + Teams (t-paliad-023)
Comprehensive design doc for the replacement of flat paliad.akten with:
  - paliad.mandanten (Clients as first-class table)
  - paliad.projekte (single self-referential typed tree, ltree materialised
    path, 5 project types: mandat/litigation/patent/verfahren/projekt)
  - paliad.teams + paliad.team_mitglieder (Dezernate + project teams in one
    table with kind-shape CHECK)
  - paliad.projekt_mitglieder (hot-path junction replacing akten.collaborators)

Polymorphic FK strategy: single project_id FK on fristen/termine/dokumente/
parteien/akten_events/checklist_instances. Notizen keeps its 4-way polymorphic
shape (akte_id renamed to project_id).

Visibility model: tree-connected — seeing any node grants access to the whole
tree (ancestors + descendants). Office-scope stays at project level; Mandant-
level firm_wide_visible / collaborators override.

Migration plan: 6 phases, non-destructive. UUIDs preserved between akten and
projekte rows so child tables only need column renames, no data moves.

Opinionated: German naming throughout (mandanten, projekte, teams,
team_mitglieder, projekt_mitglieder); /akten URLs alias to /projekte
indefinitely; akten_events table name kept for continuity.

Deliverable: docs/design-data-model-v2.md (920 lines, 14 sections).
2026-04-20 14:17:32 +02:00