docs(paliadin): t-paliad-161 inventor design — inline modal + agent-suggested write path
Two intertwined Paliadin upgrades, scoped together because the chat surface is where the write path is triggered and the write path is what makes the chat non-trivial: 1. Inline slide-out modal reachable from every authenticated paliad page, with structured page-context payload (route_name + primary_entity + selection text) and per-route starter prompts. 2. Agent-suggested write path that drafts deadlines/appointments/notes into the existing pending_create lifecycle (t-paliad-160) with new provenance columns on approval_requests (requester_kind + agent_turn_id); approved-from-agent rows render alongside 👀 with a sparkle ✨. Hard call: keep the existing tmux relay for v1; recommend (but do not commit) the Anthropic API cutover as a prerequisite for opening beyond owner-only. Single Paliadin persona — no scope-bouncer pre-design. Inventor parked. DESIGN READY FOR REVIEW. Awaiting m's go/no-go before any coder shift. Refs: m/paliad#20, t-paliad-146, t-paliad-160, t-paliad-138.
This commit is contained in:
943
docs/design-paliadin-inline-2026-05-08.md
Normal file
943
docs/design-paliadin-inline-2026-05-08.md
Normal file
@@ -0,0 +1,943 @@
|
||||
# Inline Paliadin chat modal + agent-suggested-with-approval write path
|
||||
|
||||
**Inventor:** dirac · **Task:** t-paliad-161 · **Issue:** m/paliad#20
|
||||
**Date:** 2026-05-08 · **Branch:** `mai/dirac/inventor-inline-paliadin`
|
||||
**Status:** READY FOR REVIEW — awaiting m's go/no-go before any coder shift.
|
||||
|
||||
---
|
||||
|
||||
## 0 · TL;DR
|
||||
|
||||
Two intertwined upgrades, scoped together because the chat surface is where
|
||||
the write path is triggered and the write path is what makes the chat
|
||||
non-trivial:
|
||||
|
||||
1. **Inline modal**: a slide-out chat widget reachable from every
|
||||
authenticated paliad page, replacing the standalone `/paliadin` route's
|
||||
primacy (the page survives as the dedicated full-screen surface). The
|
||||
widget is **context-aware** — it knows which route the user is on, the
|
||||
primary entity in view, and any selected text — and uses that to
|
||||
pre-populate page-specific starter prompts.
|
||||
|
||||
2. **Agent-suggested write path**: Paliadin gains a *suggestion verb* that
|
||||
drafts a deadline / appointment / note / project edit straight into the
|
||||
existing `pending_create` lifecycle from t-paliad-160. The user reviews
|
||||
via the same eye-pill 👀 surface (`/inbox`, list/agenda views) and
|
||||
approves or rejects. Approved-from-suggestion rows pick up a sparkle ✨
|
||||
provenance glyph that lives **next to** 👀, not in place of it.
|
||||
|
||||
**Hard call**: the inline modal should **keep the existing tmux-relay
|
||||
backend** for v1. Cutover to the Anthropic Messages API is a separate
|
||||
substantial piece of work (auth, prompt-caching, tool framework, budget
|
||||
management); coupling it to the inline-modal ship would extend the design
|
||||
window past where m needs the modal to land. The design *recommends* the
|
||||
API cutover as a prerequisite for opening Paliadin beyond owner-only — but
|
||||
the inline modal at owner-only scope works fine on the existing relay.
|
||||
|
||||
**Key locked positions** (all reversible by m before coder shift):
|
||||
|
||||
| # | Decision | Position |
|
||||
|---|---|---|
|
||||
| 1 | Modal trigger | Floating button bottom-right + `Cmd/Ctrl-K` shortcut |
|
||||
| 2 | Surface shape | Right slide-out drawer, 420px desktop, full-screen on mobile |
|
||||
| 3 | Visibility | Every authenticated page **except** `/paliadin`, `/login`, `/onboarding` |
|
||||
| 4 | Gate | Same `PaliadinOwnerEmail` gate as today (no scope expansion in this task) |
|
||||
| 5 | Backend transport | Tmux relay (existing). Anthropic-API cutover deferred. |
|
||||
| 6 | Multi-turn coherence | Tmux session reuse already handles it; no client-side history hydrate beyond what's there |
|
||||
| 7 | Context payload | `route_name` + `primary_entity_type` + `primary_entity_id` + `user_selection_text` (optional) + page metadata |
|
||||
| 8 | Starter-prompt library | Per-route `paliadinStarters` registry, ships with 8 routes + a generic fallback |
|
||||
| 9 | Agent-suggested attribution | New columns on `paliad.approval_requests` (`requester_kind`, `agent_turn_id`); **not** on entity rows |
|
||||
| 10 | Visual language | ✨ glyph alongside 👀 on pending rows; persistent ✨ on approved-from-agent rows in audit log |
|
||||
| 11 | Persona separation | Single Paliadin SKILL.md unchanged. No pre-design for split personas. |
|
||||
| 12 | Concurrency | One in-flight turn per user enforced server-side (existing `turnMu`); request-side cancel via context |
|
||||
|
||||
---
|
||||
|
||||
## 1 · Premises verified live
|
||||
|
||||
Read the live system before designing on top — every claim below was
|
||||
checked against the running paliad.de + DB on 2026-05-08, not against
|
||||
CLAUDE.md or memory.
|
||||
|
||||
- **paliad.de**: live; root 200, `/paliadin` 302 (login redirect for
|
||||
anon). Production runs `RemotePaliadinService` against mRiver (CLAUDE.md
|
||||
flags `tmux + claude` as missing in the Dokploy container — confirmed
|
||||
the prod path actually goes through `paliadin-shim` over SSH).
|
||||
- **Migration tracker**: `paliad.paliad_schema_migrations.version=69`. Next
|
||||
free migration is **070**.
|
||||
- **`paliad.approval_requests`** existing columns: `id, project_id,
|
||||
entity_type, entity_id, lifecycle_event, pre_image, payload,
|
||||
requested_by, requested_at, required_role, status, decided_by,
|
||||
decided_at, decision_kind, decision_note, created_at, updated_at`. **No
|
||||
`agent_*` columns yet** — migration 070 adds them.
|
||||
- **`paliad.paliadin_turns`**: already has a `page_origin TEXT` column
|
||||
populated from `req.PageOrigin` on every turn. Today the frontend only
|
||||
ever sets `window.location.pathname` on the standalone page; the inline
|
||||
widget will widen this from a single string into a structured payload.
|
||||
- **`paliad.deadlines` + `paliad.appointments`**: already carry
|
||||
`approval_status text NOT NULL DEFAULT 'approved'` + `pending_request_id
|
||||
uuid` from migration 054. The 👀 eye-pill renders on pending rows in
|
||||
`events.ts:521` and `agenda.ts:289` via `.approval-pill--icon`.
|
||||
- **Sidebar** (`frontend/src/components/Sidebar.tsx:123`): already has a
|
||||
`/paliadin` entry hidden by default, revealed by `client/sidebar.ts`
|
||||
after `/api/me` confirms the caller is the Paliadin owner. The same
|
||||
reveal hook drives the inline modal's visibility.
|
||||
- **`PaliadinOwnerEmail`** (`internal/services/paliadin.go:51`):
|
||||
`matthias.siebels@hoganlovells.com`. Hard-coded gate. **No scope
|
||||
expansion in this task.**
|
||||
- **youpc.org reference files** all readable at
|
||||
`/home/m/dev/web/youpc.org/`: `frontend/templates/ai/sidebar-widget.html`,
|
||||
`frontend/js/utils/ai-chat-client.js`, `frontend/js/components/ai/sidebar.js`,
|
||||
`youpc-go/internal/services/youpc_ai_relay.go`, `scripts/youpc-ai-shim`.
|
||||
Klaus's brief in #20 maps to these directly.
|
||||
|
||||
**One CLAUDE.md correction**: the project's `CLAUDE.md` currently calls
|
||||
`ANTHROPIC_API_KEY` "reserved-but-unused for the eventual production-v1
|
||||
Paliadin". That language stays correct — this design *recommends but does
|
||||
not commit* the API cutover. No CLAUDE.md edit in the implementation PR.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Why the inline modal matters
|
||||
|
||||
m's framing (#20 §1) is "Paliadin should be reachable from anywhere". The
|
||||
real differentiation argument is sharper: the *value of the assistant
|
||||
collapses to "open a chat tab" if you can't get to it without leaving the
|
||||
page you're already working on.* For a patent-practice tool, the most
|
||||
common questions are page-anchored:
|
||||
|
||||
- On `/projects/<id>` → "Was steht für diese Akte diese Woche an?"
|
||||
- On `/deadlines/<id>` → "Erkläre mir die Klageerwiderungsfrist nach UPC RoP 23.1."
|
||||
- On `/agenda` with selection → "Schreibe einen Nachtrag zu diesem
|
||||
Termin: …"
|
||||
|
||||
The standalone `/paliadin` page solves none of these because asking the
|
||||
question requires the user to (a) leave the page, (b) re-explain context
|
||||
the page already had, (c) navigate back. The inline modal solves (a) by
|
||||
construction; (b) is solved by the **context payload** (§4); (c) is moot.
|
||||
|
||||
The widget is therefore the **default surface** going forward; the
|
||||
`/paliadin` standalone page survives as the dedicated full-screen mode
|
||||
(useful for long sessions where the slide-out is too narrow). Both speak
|
||||
the same backend.
|
||||
|
||||
---
|
||||
|
||||
## 3 · Modal — shape, trigger, injection
|
||||
|
||||
### 3.1 Visual shape (recommendation)
|
||||
|
||||
**Right-edge slide-out drawer** — same pattern as youpc.org's
|
||||
`ai-sidebar-widget.html` because it solves the right problems:
|
||||
|
||||
- Doesn't crowd the page content (drawer slides in *over* a translucent
|
||||
scrim, page underneath stays visible at ~70% opacity so the user can
|
||||
reference what they were looking at).
|
||||
- Mobile-responsive for free: at `<640px` the drawer goes full-screen and
|
||||
the floating button hides while open.
|
||||
- Doesn't fight with paliad's existing left sidebar (`Sidebar.tsx`) — the
|
||||
drawer claims the right edge, the sidebar keeps the left.
|
||||
|
||||
**Considered and rejected:**
|
||||
|
||||
- *Always-visible secondary sidebar* (left or right rail). Wastes ~280px
|
||||
of horizontal real-estate on every page; collides with the sidebar on
|
||||
mobile.
|
||||
- *Popover anchored to the floating button*. Too small for multi-turn
|
||||
conversations; mobile would need a separate full-screen mode anyway.
|
||||
- *Fullscreen takeover overlay*. Defeats the purpose — if it covers the
|
||||
page you can't reference what you were looking at.
|
||||
|
||||
### 3.2 Trigger
|
||||
|
||||
Two entry points:
|
||||
|
||||
1. **Floating action button** at bottom-right (`position: fixed; bottom:
|
||||
20px; right: 20px;`). Lime accent (`var(--color-accent)`), ✨ glyph.
|
||||
Same auth-reveal hook as the sidebar `/paliadin` link — `display:none`
|
||||
until `client/sidebar.ts` confirms `/api/me.email ===
|
||||
PaliadinOwnerEmail`.
|
||||
|
||||
2. **Keyboard shortcut**: `Cmd-K` (macOS) / `Ctrl-K` (other). Standard
|
||||
command-palette muscle memory. Doesn't collide with browser shortcuts.
|
||||
Paliad has no other Cmd-K binding today (verified via grep on
|
||||
`keydown` handlers).
|
||||
|
||||
The shortcut also dismisses the drawer when it's open. `Esc` dismisses
|
||||
unconditionally.
|
||||
|
||||
### 3.3 Drawer content
|
||||
|
||||
Layout (top to bottom):
|
||||
|
||||
```
|
||||
┌──────────────────────────────┬─┐
|
||||
│ ✨ Paliadin ↻ ↗ ✕│ │ Header: name, reset-session, open-fullscreen, close
|
||||
├──────────────────────────────┼─┤
|
||||
│ [Auf dieser Seite] │
|
||||
│ Akte: Acme v. Müller │ │ Context chip — collapsible, shows what Paliadin
|
||||
│ 19 Fristen · 4 Termine │ │ knows about the current page (read from payload)
|
||||
├──────────────────────────────┼─┤
|
||||
│ [empty-state starter prompts] │
|
||||
│ • "Was steht hier an?" │
|
||||
│ • "Erkläre die offene…" │
|
||||
│ • "Lege eine Frist an" │
|
||||
├──────────────────────────────┼─┤
|
||||
│ <messages> │ │ Scrollable, user-right / paliadin-left
|
||||
│ > User bubble │
|
||||
│ < Paliadin bubble + ✨ chip │ │ ✨ chip = "I drafted this — it's awaiting your approval"
|
||||
├──────────────────────────────┼─┤
|
||||
│ [textarea + send + abort] │
|
||||
└──────────────────────────────┴─┘
|
||||
```
|
||||
|
||||
The `↗` button is the escape hatch to the standalone `/paliadin` for
|
||||
users who want a full-screen session with full message history visible.
|
||||
|
||||
### 3.4 Injection mechanism
|
||||
|
||||
**One file edits the universe**: `frontend/src/components/PaliadinWidget.tsx`
|
||||
emits an inline `<div id="paliadin-widget" style="display:none">…</div>`
|
||||
that page-template files include alongside `<PWAHead />` and `<Sidebar />`.
|
||||
|
||||
The mechanical edit pass: every authenticated TSX page (~30 files) gets a
|
||||
`<PaliadinWidget />` near `</body>`. This mirrors the existing
|
||||
`<PWAHead />` mechanical pass from t-paliad-042 and is the cleanest way to
|
||||
guarantee the widget reaches every page without HTMX or runtime injection.
|
||||
|
||||
**Alternative considered**: server-side template fragment injected by Go's
|
||||
HTML response writer (cleaner: no per-page edit). Rejected because paliad
|
||||
uses bun-built static HTML files, not templated server responses — there's
|
||||
no place to inject server-side. The mechanical pass is fine; the
|
||||
boilerplate it adds is one component.
|
||||
|
||||
**Visibility predicate** (in `client/paliadin-widget.ts`):
|
||||
|
||||
- **Hide** on `/paliadin` (the standalone page IS Paliadin, the widget
|
||||
would be redundant).
|
||||
- **Hide** on `/login`, `/onboarding` (no auth context).
|
||||
- **Hide** until `/api/me` resolves to `email === PaliadinOwnerEmail`.
|
||||
Same fail-closed pattern as the sidebar link.
|
||||
- **Show** on every other authenticated page.
|
||||
|
||||
### 3.5 What about the BottomNav (mobile)?
|
||||
|
||||
`BottomNav.tsx` has 5 slots (Dashboard / Projects / Add / Agenda / Menu)
|
||||
— full. Adding a Paliadin slot would require evicting one. **Don't.**
|
||||
The floating button is fine on mobile (it sits in the bottom-right corner
|
||||
*above* the bottom nav, with `z-index` arbitration). At full-screen-drawer
|
||||
size on mobile, the floating button hides while the drawer is open.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Context payload — what flows from frontend to backend
|
||||
|
||||
### 4.1 Schema
|
||||
|
||||
The current `TurnRequest.PageOrigin` is a single string (the URL path).
|
||||
The inline modal needs more. Define a structured payload:
|
||||
|
||||
```ts
|
||||
interface PaliadinContext {
|
||||
// Stable route key — independent of URL params. e.g. "projects.detail"
|
||||
// not "/projects/61e3.../tab=team". The frontend computes this from
|
||||
// `window.location.pathname` via a route-table lookup.
|
||||
route_name: string;
|
||||
|
||||
// Path including query string (cosmetic; for audit + display only).
|
||||
page_origin: string;
|
||||
|
||||
// The "primary entity" of the current page, if any. Examples:
|
||||
// /projects/<id> → ("project", "<id>")
|
||||
// /deadlines/<id> → ("deadline", "<id>")
|
||||
// /appointments/<id> → ("appointment", "<id>")
|
||||
// /events?type=deadline → null
|
||||
// /tools/fristenrechner → null
|
||||
primary_entity_type?: "project" | "deadline" | "appointment";
|
||||
primary_entity_id?: string; // uuid
|
||||
|
||||
// User's text selection at the moment they opened the widget (or sent
|
||||
// the turn). Capped at 1000 chars. Empty string = no selection.
|
||||
// Source: window.getSelection().toString() at send-time.
|
||||
user_selection_text?: string;
|
||||
|
||||
// UI state hints. Optional, useful for the model to disambiguate:
|
||||
view_mode?: "list" | "cards" | "calendar" | "tree"; // /events, /projects
|
||||
filter_summary?: string; // e.g. "status=overdue, project=Acme"
|
||||
}
|
||||
```
|
||||
|
||||
**What each field enables:**
|
||||
|
||||
- `route_name`: maps cleanly to a starter-prompt registry (§5) without
|
||||
URL-parsing fragility.
|
||||
- `primary_entity_*`: the SKILL.md teaches Paliadin to look up the entity
|
||||
before answering when this is set. Saves a back-and-forth ("which
|
||||
project?") in the very common case where the user is *already on* the
|
||||
project page.
|
||||
- `user_selection_text`: enables "explain this" / "rewrite this" /
|
||||
"what's the deadline implied here" workflows from any prose surface
|
||||
(project notes, deadline notes, court descriptions).
|
||||
- `view_mode` + `filter_summary`: the model can say "I see you're looking
|
||||
at overdue deadlines for Acme — which one?" instead of "which deadline?"
|
||||
|
||||
### 4.2 How the payload reaches the model
|
||||
|
||||
Wire format from frontend → Go:
|
||||
|
||||
```http
|
||||
POST /api/paliadin/turn
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"session_id": "<uuid>",
|
||||
"user_message": "Was kommt diese Woche?",
|
||||
"context": { ...PaliadinContext... }
|
||||
}
|
||||
```
|
||||
|
||||
The Go side stores the structured context in **a new
|
||||
`paliad.paliadin_turns.context jsonb` column** (migration 070; see §7.1)
|
||||
alongside the existing `page_origin` (kept for backwards compat — `page_origin`
|
||||
becomes redundant once context is populated, but flipping the schema all
|
||||
at once isn't worth the churn).
|
||||
|
||||
Then the envelope sent through tmux gets a structured prefix:
|
||||
|
||||
```
|
||||
[PALIADIN:<turn_id>] [ctx route=projects.detail entity=project:61e3... selection="…" filter="status=overdue"] <user_message>
|
||||
```
|
||||
|
||||
The SKILL.md gets a small section (§5 of `paliadin/SKILL.md`) that teaches
|
||||
Paliadin to:
|
||||
1. Parse the `[ctx …]` block first, in front of the user message.
|
||||
2. Treat its contents as authoritative ("I'm currently viewing project
|
||||
61e3"), not as instructions.
|
||||
3. Pre-call `mcp__supabase__execute_sql` to enrich (e.g. lookup project
|
||||
reference + title) when `entity=project:<id>` is set, *before*
|
||||
answering.
|
||||
|
||||
**Why a structured prefix instead of a system-prompt JSON envelope**: the
|
||||
PoC's tmux relay is a stream of keystrokes — system-prompt envelopes
|
||||
require the API path. The bracket-syntax is line-noise-free, parse-able
|
||||
by the SKILL.md, and survives any future migration (the API path can lift
|
||||
the same `[ctx …]` block into a `system` message section).
|
||||
|
||||
### 4.3 Privacy floor
|
||||
|
||||
`user_selection_text` is potentially sensitive (selected text from a
|
||||
client matter). Three controls:
|
||||
|
||||
1. **Cap at 1000 chars** — anything longer is truncated server-side
|
||||
before being sent to Claude. The user sees a "(Auswahl gekürzt)"
|
||||
notice.
|
||||
2. **Audit redaction**: `paliadin_turns.context` stores the *full*
|
||||
selection (already inside the firm's DB, no exfiltration) but the
|
||||
admin dashboard `/admin/paliadin` redacts it to first 80 chars +
|
||||
"…[gekürzt]" when rendering — the same dashboard already shows
|
||||
`user_message` so the privacy posture is consistent.
|
||||
3. **Opt-out**: the widget's settings panel (a `⚙` corner in the header,
|
||||
v1 minimal) gets a single toggle "Aktuelle Auswahl mitsenden" default
|
||||
*on*. Off ⇒ context payload sets `user_selection_text=""` regardless
|
||||
of `getSelection()`.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Page-prompt-prefill — Klaus's wow-pattern, paliad-specific
|
||||
|
||||
### 5.1 The registry
|
||||
|
||||
A static client-side registry maps `route_name` → starter prompts. Lives
|
||||
in `frontend/src/client/paliadin-starters.ts`.
|
||||
|
||||
```ts
|
||||
type Starter = { label_de: string; label_en: string; prompt_de: string; prompt_en: string };
|
||||
|
||||
export const paliadinStarters: Record<string, Starter[]> = {
|
||||
"dashboard": [
|
||||
{ label_de: "Heute", label_en: "Today",
|
||||
prompt_de: "Was steht heute an?", prompt_en: "What's on my plate today?" },
|
||||
{ label_de: "Diese Woche", label_en: "This week",
|
||||
prompt_de: "Welche Fristen sind diese Woche?", prompt_en: "Which deadlines are this week?" },
|
||||
{ label_de: "Nächste Schritte", label_en: "Next steps",
|
||||
prompt_de: "Was sollte ich als nächstes erledigen?", prompt_en: "What should I tackle next?" },
|
||||
],
|
||||
"projects.detail": [
|
||||
{ label_de: "Status der Akte", label_en: "Project status",
|
||||
prompt_de: "Was ist der aktuelle Status dieser Akte?", prompt_en: "What's the status of this project?" },
|
||||
{ label_de: "Diese Woche", label_en: "This week",
|
||||
prompt_de: "Was steht für diese Akte diese Woche an?", prompt_en: "What's on for this project this week?" },
|
||||
{ label_de: "Frist anlegen", label_en: "Add a deadline",
|
||||
prompt_de: "Lege eine Frist für diese Akte an: ", prompt_en: "Add a deadline for this project: " },
|
||||
],
|
||||
"deadlines.detail": [
|
||||
{ label_de: "Erkläre die Frist", label_en: "Explain this deadline",
|
||||
prompt_de: "Erkläre mir die Frist auf dieser Seite.", prompt_en: "Explain this deadline." },
|
||||
{ label_de: "Rechtsgrundlage", label_en: "Legal basis",
|
||||
prompt_de: "Welche Norm ist hier einschlägig?", prompt_en: "What's the relevant rule?" },
|
||||
],
|
||||
"agenda": [ /* … */ ],
|
||||
"events": [ /* … */ ],
|
||||
"inbox": [ /* … */ ],
|
||||
"tools.fristenrechner": [ /* … */ ],
|
||||
"glossary": [ /* … */ ],
|
||||
// Generic fallback for unmapped routes.
|
||||
"_default": [
|
||||
{ label_de: "Was kann ich für dich tun?", label_en: "What can I help with?",
|
||||
prompt_de: "", prompt_en: "" },
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
The widget's empty state renders the matching starter list. Click → the
|
||||
prompt populates the textarea (or sends immediately if `prompt_de` is
|
||||
empty — letting the user type their own). Picking up "Lege eine Frist an: "
|
||||
seeds the input *partially* so the user finishes the sentence — a
|
||||
deliberate friction-reducer for the common "draft and approve" workflow.
|
||||
|
||||
### 5.2 Why per-route registry, not LLM-generated suggestions?
|
||||
|
||||
Considered: dynamically ask Paliadin to suggest 3 starters based on
|
||||
context. Rejected because:
|
||||
|
||||
1. **Latency**: every drawer-open would burn a full turn before the user
|
||||
even types. The PoC's tmux turn is ~2-5 seconds cold; that's an
|
||||
unusable empty state.
|
||||
2. **Determinism**: m's audience (PA team) needs predictable affordances.
|
||||
"What does this thing know how to do?" answered the same way each
|
||||
visit beats "what does this thing know how to do *today*?"
|
||||
3. **Translatability**: hand-crafted bilingual starters live next to the
|
||||
rest of the i18n. LLM-generated would be one language at a time.
|
||||
|
||||
The registry is small (~10 routes × 3 starters × 2 langs = ~60 strings)
|
||||
and lives next to `i18n.ts` patterns m's team already understands.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Backend transport — tmux relay vs Anthropic API
|
||||
|
||||
### 6.1 Recommendation: keep tmux relay for v1 of the inline modal
|
||||
|
||||
Two reasons:
|
||||
|
||||
1. **Scope discipline**: the inline modal's user-visible payoff is
|
||||
independent of which backend serves it. Cutover to the API is a 4-6
|
||||
commit piece of substantial work (auth headers, prompt-cache
|
||||
management, tool-definition framework, streaming format conversion,
|
||||
budget controls, audit reshape, plus the existing tmux path needs to
|
||||
remain as fallback during rollout). Bundling it with the inline modal
|
||||
doubles the design's blast radius for no inline-modal-side benefit.
|
||||
2. **Owner-only scope**: paliad's user base today is `PaliadinOwnerEmail =
|
||||
m`. One user. The tmux relay's serialised one-turn-at-a-time, ~2-5s
|
||||
cold start, ~1-3s warm response holds up fine for one user clicking
|
||||
through the day.
|
||||
|
||||
### 6.2 What the API cutover *would* fix (recommend as Phase 2)
|
||||
|
||||
When scope expands beyond owner-only — even just to "m + 2 PA colleagues
|
||||
for piloting" — the tmux relay starts to bend:
|
||||
|
||||
- **Concurrency**: serialised turn lock means PA-A waits while PA-B
|
||||
thinks. Per-user tmux sessions help but mRiver still has finite
|
||||
resources.
|
||||
- **Latency**: ~2s cold tmux start is ok for one user; bad for "I just
|
||||
opened the widget, ask a quick question, close" rhythm at scale.
|
||||
- **Cost vs subscription**: m's Claude Code subscription covers his
|
||||
personal turns. Multi-user would either need m's account to absorb the
|
||||
load (dubious) or the firm's enterprise key (the actual prod path).
|
||||
- **Streaming**: tmux streaming today is the youpc.org-style "tail the
|
||||
response file as it grows" stopgap. Real token streaming (TTFB <1s)
|
||||
needs the API.
|
||||
|
||||
The API cutover should therefore be **a prerequisite for opening Paliadin
|
||||
beyond owner-only**. The inline modal's design assumes API-cutover-ready
|
||||
boundaries (the relay interface in §6.4) so when m flips the switch, the
|
||||
inline-modal frontend doesn't change.
|
||||
|
||||
### 6.3 Why not cutover now anyway?
|
||||
|
||||
It's tempting because:
|
||||
|
||||
- The CLAUDE.md note about `ANTHROPIC_API_KEY` reserved-but-unused has
|
||||
been there since 2026-04-16 and would benefit from being un-deferred.
|
||||
- The inline modal is the natural moment to revisit infrastructure.
|
||||
- Klaus's youpc.org has built a relay-interface abstraction
|
||||
(`youpcAIRelay` interface in `youpc_ai_relay.go`) that paliad could
|
||||
borrow for the swap point.
|
||||
|
||||
**Counter-arguments that win:**
|
||||
|
||||
- Today's tmux relay shipped only 2-3 days ago (`paliadin_remote.go`
|
||||
reference t-paliad-151). It's not a legacy substrate to escape — it's
|
||||
fresh code that hasn't earned a rewrite yet.
|
||||
- The compliance question for the API path (HLC-key vs personal-key,
|
||||
audit retention requirements, prompt-logging policy) hasn't been
|
||||
resolved with HLC IT. m flagged this as the **biggest open question** in
|
||||
the t-paliad-146 design and it's still open.
|
||||
- Inline modal can ship entirely on the existing relay; if the API
|
||||
cutover comes later, the modal doesn't have to re-ship.
|
||||
|
||||
**Therefore**: design a small interface seam (§6.4) so v1 doesn't paint us
|
||||
into a tmux-only corner, but don't pay the cutover cost in this PR.
|
||||
|
||||
### 6.4 Relay-interface seam (small, optional, recommended)
|
||||
|
||||
Mirror youpc.org's pattern (`youpc_ai_relay.go`) but smaller — paliad
|
||||
has one role, no streaming variant yet:
|
||||
|
||||
```go
|
||||
// internal/services/paliadin_relay.go (new)
|
||||
type PaliadinRelay interface {
|
||||
RunTurn(ctx context.Context, session string, turnID uuid.UUID,
|
||||
envelope string) ([]byte, error)
|
||||
Reset(ctx context.Context, session string) error
|
||||
HealthGate(ctx context.Context, session string) error
|
||||
}
|
||||
```
|
||||
|
||||
`LocalPaliadinService` and `RemotePaliadinService` keep their current
|
||||
shapes; the audit-row writes (`paliadinDB`) stay shared. `RunTurn` becomes
|
||||
a thin wrapper that builds the envelope (with the new `[ctx …]` block from
|
||||
§4.2) and delegates to the relay. A future `httpAPIRelay` slots in beside
|
||||
the SSH one without touching the audit/turn-row code.
|
||||
|
||||
**Don't extract the interface unless the inline modal's PR organically
|
||||
needs it.** If the modal can ship without restructuring the existing
|
||||
relay, the abstraction-cost is negative.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Agent-suggested write path — schema + flow
|
||||
|
||||
### 7.1 Schema decision: extend `approval_requests`, not entity rows
|
||||
|
||||
The brief listed three candidate locations:
|
||||
|
||||
| Option | Where the marker lives | Verdict |
|
||||
|---|---|---|
|
||||
| A | `boolean agent_suggested` on `paliad.deadlines` / `paliad.appointments` | **Reject**: pollutes domain tables; survives past approval (the entity is no longer "agent-suggested" once it's been live for six months); doesn't carry which agent / which turn |
|
||||
| B | `text suggested_by_agent` on entity rows (multi-agent provenance) | Same problems as A; "agent name" never used because we have one agent |
|
||||
| C | New columns on `paliad.approval_requests` linking back to the suggesting turn | **Recommended** |
|
||||
|
||||
The `approval_request` row IS the audit-chain entry; the entity row is
|
||||
just current state. Provenance information belongs on the audit-chain row
|
||||
where it can persist forever without polluting the entity schema.
|
||||
|
||||
**Migration 070 (proposed):**
|
||||
|
||||
```sql
|
||||
ALTER TABLE paliad.approval_requests
|
||||
-- 'user' = direct user create; 'agent' = drafted by Paliadin from a chat turn.
|
||||
ADD COLUMN requester_kind text NOT NULL DEFAULT 'user'
|
||||
CHECK (requester_kind IN ('user', 'agent')),
|
||||
-- When requester_kind='agent', the chat turn the suggestion came from.
|
||||
-- NULL otherwise. ON DELETE SET NULL — the audit record survives even
|
||||
-- if the turn row is purged (paliadin_turns has no retention policy
|
||||
-- today, but design for it).
|
||||
ADD COLUMN agent_turn_id uuid
|
||||
REFERENCES paliad.paliadin_turns(turn_id) ON DELETE SET NULL,
|
||||
ADD CONSTRAINT approval_requests_agent_xor
|
||||
CHECK (
|
||||
(requester_kind = 'agent' AND agent_turn_id IS NOT NULL)
|
||||
OR (requester_kind = 'user' AND agent_turn_id IS NULL)
|
||||
);
|
||||
|
||||
CREATE INDEX approval_requests_agent_turn_idx
|
||||
ON paliad.approval_requests (agent_turn_id)
|
||||
WHERE agent_turn_id IS NOT NULL;
|
||||
|
||||
-- paliadin_turns also gets the structured context column.
|
||||
ALTER TABLE paliad.paliadin_turns
|
||||
ADD COLUMN context jsonb;
|
||||
```
|
||||
|
||||
`requested_by` continues to be the user uuid — even for agent suggestions
|
||||
the user is the *initiator* (Paliadin acts on their behalf, never
|
||||
autonomously). `requester_kind` distinguishes "the user typed Speichern"
|
||||
from "the user typed `/lege eine Frist an: …` to Paliadin and Paliadin
|
||||
drafted it; the user has not yet approved".
|
||||
|
||||
### 7.2 The flow
|
||||
|
||||
1. **User asks Paliadin**: "Lege eine Frist für diese Akte an: 16.05.
|
||||
Klageerwiderung Acme".
|
||||
|
||||
2. **Paliadin's SKILL.md gets a new section**: "Agent-suggested writes"
|
||||
that teaches it to call a new MCP tool `paliad__suggest_deadline` (and
|
||||
siblings for appointment / project_note / project_attach). The tool's
|
||||
server-side handler:
|
||||
|
||||
- Validates the user has visibility on the project (existing
|
||||
`can_see_project`).
|
||||
- Calls `DeadlineService.Create` *with the new
|
||||
`IsAgentSuggestion=true` flag* and `agent_turn_id=<current turn>`.
|
||||
- Inside the create-tx, after the entity insert, the existing approval
|
||||
hookup runs: `ApprovalService.SubmitCreate(...)`. **Critical
|
||||
change**: when `IsAgentSuggestion` is set, the submit unconditionally
|
||||
creates an approval request *even if no policy applies* — the agent
|
||||
path is approval-gated by construction, not by partner-unit policy.
|
||||
|
||||
3. **Eye-pill 👀 + sparkle ✨** render on the resulting row in `/inbox`,
|
||||
`/deadlines`, `/agenda`. Click → standard approve/reject UI. Approve
|
||||
flips status to `approved`, sets `decision_kind='peer'` (or
|
||||
admin_override if global_admin), the entity becomes live.
|
||||
|
||||
4. **Audit chain on the project's Verlauf**:
|
||||
|
||||
- `deadline_approval_requested` event with
|
||||
`metadata.requester_kind='agent'` + `metadata.agent_turn_id=<uuid>`.
|
||||
Verlauf renderer picks this up and labels the event "Paliadin hat
|
||||
eine Frist vorgeschlagen ✨".
|
||||
- `deadline_approval_approved` with the user as `decided_by` + the
|
||||
existing `decision_kind` ladder. Verlauf renders "Anna hat
|
||||
Paliadin's Vorschlag genehmigt ✨".
|
||||
|
||||
### 7.3 Why agent-suggested unconditionally goes through approval
|
||||
|
||||
Two reasons:
|
||||
|
||||
1. **Trust gradient**: even if a partner has direct create authority on
|
||||
their own projects (no policy = no approval needed today), an agent
|
||||
suggesting on their behalf is qualitatively different. Visible review
|
||||
keeps the user in the loop.
|
||||
2. **Single audit shape**: today the partner-unit policy decides which
|
||||
creates need approval; bypassing that for agent suggestions creates a
|
||||
second code path. Forcing agent suggestions into the approval pipeline
|
||||
means there's exactly one "agent created an entity" audit shape (the
|
||||
approval_request row).
|
||||
|
||||
A user who finds the per-suggestion review tedious can request `/genehmige
|
||||
einfach alles was Paliadin vorschlägt` — but that's a Phase 2 setting
|
||||
("auto-approve agent suggestions on projects where I'm lead"), explicitly
|
||||
out-of-scope for v1 (and m says so in #20: "Multi-turn agent loops …
|
||||
Every creation gets the user's eye.").
|
||||
|
||||
### 7.4 What entities can Paliadin suggest in v1?
|
||||
|
||||
The brief mentions "deadlines, appointments, notes, project-tree edits".
|
||||
Recommend ordering by reversibility + audit complexity:
|
||||
|
||||
| Entity | v1? | Why |
|
||||
|---|---|---|
|
||||
| Deadline create | **Yes** | Highest-value (Klaus would rate this top), well-supported by existing `pending_create` lifecycle |
|
||||
| Appointment create | **Yes** | Same lifecycle substrate; symmetric tool |
|
||||
| Project note (`project_events.note`) | **Yes** | Read-only audit event, no approval gate today — but for agent-authored notes route through approval anyway (consistency) |
|
||||
| Project-tree edit (move, rename) | **No, defer** | Approval lifecycle for project moves doesn't exist; designing it is its own task. |
|
||||
| Deadline / appointment **edit** | **No, defer** | Edits today only need approval when date-fields change (t-paliad-138 §Q4). Agent edits would need their own design pass for "what changes does the user see in the diff?" |
|
||||
| Deadline **complete** | **No, defer** | Same reason — complete already has approval lifecycle, but the agent path is qualitatively different (a deadline being marked done is high-stakes; design it after a v1 lands and we see how often agent-creates need editing) |
|
||||
|
||||
**v1 = create only**. Edits/completes are a Phase 2 expansion.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Visual language — ✨ alongside 👀, not in place of
|
||||
|
||||
### 8.1 Design
|
||||
|
||||
`.approval-pill--agent` is a new modifier that sits **next to** the
|
||||
existing `.approval-pill--icon` (the 👀 glyph), not replacing it.
|
||||
|
||||
| Row state | Pill rendering |
|
||||
|---|---|
|
||||
| `approval_status='pending'` AND `requester_kind='user'` | 👀 |
|
||||
| `approval_status='pending'` AND `requester_kind='agent'` | 👀 ✨ |
|
||||
| `approval_status='approved'` AND `requester_kind='user'` | (no pill) |
|
||||
| `approval_status='approved'` AND `requester_kind='agent'` | ✨ (subtle, in the row's *secondary* badge slot — not a pill) |
|
||||
|
||||
The 👀 + ✨ pairing communicates: "this is awaiting approval *and* came
|
||||
from Paliadin". Hover (`title` attr) on ✨ reads:
|
||||
"Paliadin hat das vorgeschlagen — angeklickt klärt".
|
||||
|
||||
**Why both glyphs, not a fused single glyph?** The two questions ("is
|
||||
this awaiting approval?" / "did a human or Paliadin originate this?") are
|
||||
orthogonal — a future autopilot mode might let some agent suggestions
|
||||
auto-approve, in which case 👀 disappears but ✨ stays. Keeping them
|
||||
separate keeps the visual taxonomy decomposable.
|
||||
|
||||
### 8.2 Where ✨ renders
|
||||
|
||||
Three surfaces:
|
||||
|
||||
1. **Eye-pill row** (`/inbox`, `/deadlines`, `/agenda`, project detail,
|
||||
/events): 👀 ✨ side-by-side when applicable. Same `.approval-pill`
|
||||
shape, separate elements.
|
||||
2. **Audit log** (`/admin/audit-log` + project Verlauf): the row's
|
||||
"approved by" line gets a trailing ✨ when the underlying request had
|
||||
`requester_kind='agent'`. Reads "Anna ✨ Schmidt" → tooltip "Über
|
||||
Paliadin vorgeschlagen, von Anna genehmigt".
|
||||
3. **Approval request inbox card**: the requester's name in the inbox
|
||||
card gets a subtle "✨ Paliadin (für Anna)" badge instead of just
|
||||
"Anna" when `requester_kind='agent'`.
|
||||
|
||||
### 8.3 The "+p" annotation question
|
||||
|
||||
m's #20 said: "we say USER + p or with a star or something". The "+p"
|
||||
text annotation reads in audit logs but doesn't scan in a pill row (✨ is
|
||||
recognisable; "+p" is not without learning). **Recommend**: ✨ as the
|
||||
universal glyph. Reserve a textual fallback for compliance-export
|
||||
contexts where emojis don't render — there the audit string becomes
|
||||
"Anna [agent: Paliadin]" rather than "Anna ✨".
|
||||
|
||||
---
|
||||
|
||||
## 9 · Persona separation
|
||||
|
||||
m's brief asked whether to lean on klaus's "scope-bouncer in SKILL.md"
|
||||
pattern (Hugo refuses legal questions, points at Lexie; Lexie refuses
|
||||
"how do I subscribe?", points at Hugo) for paliad — i.e. pre-design
|
||||
multi-persona infrastructure.
|
||||
|
||||
**Recommendation: don't.** Paliad has one Paliadin (Patentpraxis assistant
|
||||
at HLC's Patent team). The youpc.org split exists because *youpc.org has
|
||||
fundamentally different audiences* — public visitors (Hugo handles "how
|
||||
does this site work?") and premium-beta lawyers (Lexie does case-law
|
||||
research). Their refusal scopes are different because their users are
|
||||
different.
|
||||
|
||||
Paliad's audience is one cohesive group: HLC PA team. They want one
|
||||
assistant that does "everything PA-relevant" — Aktenmanagement, Fristen,
|
||||
Begriffe, Gerichte, UPC-Recht. There's no audience pair that requires
|
||||
distinct refusal scopes.
|
||||
|
||||
**If Phase 2 wants to add a case-law research persona** (e.g. cross-link
|
||||
to youpc.org's Lexie) — *that's a separate skill alongside Paliadin*, not
|
||||
a persona-split inside Paliadin. The infrastructure for that already
|
||||
exists in Claude Code's skill router (multiple skills, each its own
|
||||
description/persona).
|
||||
|
||||
**No SKILL.md changes for persona separation in this design**. The skill
|
||||
gets §4.2's `[ctx …]` parser added, plus §7.2's `paliad__suggest_*` tool
|
||||
guidance, but the persona stays "der Paliad-Patentpraxis-Assistent".
|
||||
|
||||
---
|
||||
|
||||
## 10 · Phasing & implementation surface
|
||||
|
||||
### 10.1 Suggested phasing (single PR is feasible; split optional)
|
||||
|
||||
**Slice A — schema + relay seam** (~1 commit)
|
||||
- Migration 070: `approval_requests.requester_kind` +
|
||||
`agent_turn_id` + xor-check + index; `paliadin_turns.context jsonb`.
|
||||
- Optional `PaliadinRelay` interface extraction (skip if it makes the PR
|
||||
bigger without removing duplication).
|
||||
|
||||
**Slice B — context payload + SKILL.md update** (~1 commit)
|
||||
- Wire structured `PaliadinContext` from frontend → Go → tmux envelope.
|
||||
- SKILL.md `[ctx …]` parsing + behaviour.
|
||||
- `client/paliadin-context.ts` route-table + entity extraction (one file).
|
||||
- `/api/paliadin/turn` accepts the new body shape (backwards-compatible:
|
||||
old `page_origin` still honoured if `context` is absent).
|
||||
|
||||
**Slice C — inline widget** (~1 commit, biggest)
|
||||
- `frontend/src/components/PaliadinWidget.tsx`.
|
||||
- `client/paliadin-widget.ts` (drawer state, sending, history, hide-on-route).
|
||||
- `client/paliadin-starters.ts` registry (8 routes + default).
|
||||
- Mechanical pass: every authenticated TSX adds `<PaliadinWidget />`.
|
||||
- CSS: `.paliadin-widget`, `.paliadin-drawer`, `.paliadin-trigger`,
|
||||
`.paliadin-context-chip`, ~150 lines of `global.css`.
|
||||
- ~30 i18n keys.
|
||||
|
||||
**Slice D — agent-suggested write path** (~1 commit)
|
||||
- `paliad__suggest_deadline` + `paliad__suggest_appointment` MCP tools
|
||||
(or HTTP tool, depending on how the MCP scope already wires —
|
||||
`internal/handlers/paliadin_tools.go` if new file warranted).
|
||||
- `DeadlineService.Create` / `AppointmentService.Create` accept a
|
||||
`IsAgentSuggestion bool` + `AgentTurnID *uuid.UUID` plumbed into
|
||||
`ApprovalService.SubmitCreate` (which gets a sibling
|
||||
`SubmitAgentCreate` that always creates a request even without policy).
|
||||
- SKILL.md adds the §7.2 "Agent-suggested writes" instruction block.
|
||||
|
||||
**Slice E — visual language** (~1 commit)
|
||||
- `.approval-pill--agent` CSS.
|
||||
- `events.ts`, `agenda.ts`, `inbox.ts` render ✨ when
|
||||
`requester_kind='agent'`.
|
||||
- Audit-log + Verlauf renderer extends to surface ✨ on approved-from-agent
|
||||
events.
|
||||
- ~10 i18n keys for the badges + tooltips.
|
||||
|
||||
**Recommended PR shape**: single PR with five commits in this order. Slice
|
||||
A's migration is independent (can deploy without the rest); Slice D needs
|
||||
B + C; Slice E builds on D. If sliced into multiple PRs, A and B-C can
|
||||
ship independently of D-E (modal works as read-only chat without the
|
||||
write path; that's already an upgrade).
|
||||
|
||||
### 10.2 Files of note for the implementer
|
||||
|
||||
**New files:**
|
||||
- `internal/db/migrations/070_paliadin_inline.{up,down}.sql`
|
||||
- `internal/handlers/paliadin_tools.go` (suggest verbs)
|
||||
- `internal/services/paliadin_relay.go` (optional interface)
|
||||
- `frontend/src/components/PaliadinWidget.tsx`
|
||||
- `frontend/src/client/paliadin-widget.ts`
|
||||
- `frontend/src/client/paliadin-starters.ts`
|
||||
- `frontend/src/client/paliadin-context.ts`
|
||||
|
||||
**Edits:**
|
||||
- `internal/services/paliadin.go` (TurnRequest gains structured Context;
|
||||
insertTurnRow stores it)
|
||||
- `internal/services/approval_service.go` (SubmitCreate accepts
|
||||
agent-flag; SubmitAgentCreate variant)
|
||||
- `internal/services/deadline_service.go`,
|
||||
`internal/services/appointment_service.go` (Create accepts
|
||||
IsAgentSuggestion + AgentTurnID; threads to ApprovalService)
|
||||
- `internal/handlers/paliadin.go` (turnRequest body schema)
|
||||
- `frontend/src/client/events.ts`, `agenda.ts`, `inbox.ts` (✨ render)
|
||||
- `frontend/src/styles/global.css` (drawer + ✨ pill CSS)
|
||||
- `frontend/src/client/i18n.ts` (~40 new keys × 2 langs)
|
||||
- `frontend/src/components/Sidebar.tsx` — no edit (the existing sidebar
|
||||
link logic already gates on owner; no new entries)
|
||||
- ~30 page TSX files: mechanical `<PaliadinWidget />` add (~1 line each)
|
||||
- `~/.claude/skills/paliadin/SKILL.md` (via `scripts/install-paliadin-skill`):
|
||||
add §4.2 ctx-parser block + §7.2 suggest-tools block
|
||||
|
||||
**Total estimated surface**: comparable to t-paliad-146 (the original
|
||||
Paliadin design — ~3500-4500 LoC) plus the agent-suggest write path
|
||||
(~1000 LoC). Single PR is feasible if the implementer is pattern-fluent;
|
||||
split is fine.
|
||||
|
||||
---
|
||||
|
||||
## 11 · Open questions for m
|
||||
|
||||
These are the calls m has to make before any coder shift starts.
|
||||
|
||||
### Q1 — Scope gate: still owner-only?
|
||||
The inline modal's design assumes `PaliadinOwnerEmail` stays as the only
|
||||
gate (m only). When does scope expand?
|
||||
- (a) **Stays owner-only for v1** of inline modal — recommended; matches
|
||||
brief. ← **inventor's pick**
|
||||
- (b) Extend to a beta-features whitelist (firm-wide email domain + flag).
|
||||
- (c) Expand to all of `hoganlovells.com` immediately. Requires API
|
||||
cutover (Phase 2 prerequisite).
|
||||
|
||||
### Q2 — Backend: tmux relay or Anthropic API for the inline modal?
|
||||
- (a) **Keep tmux relay** for v1 — recommended; ships fastest. ← **inventor's pick**
|
||||
- (b) Cutover to Anthropic API now — slower ship; better long-term.
|
||||
- (c) Both: ship tmux v1, design the API path as a parallel deferred PR.
|
||||
|
||||
### Q3 — Agent-suggested entities in v1: where to draw the line?
|
||||
- (a) **Create-only**: deadline, appointment, note. Defer edits/completes/project-tree. ← **inventor's pick**
|
||||
- (b) Create + edit (deadline + appointment).
|
||||
- (c) Create + edit + complete + project-tree.
|
||||
|
||||
### Q4 — Visual language for agent provenance?
|
||||
- (a) **✨ glyph alongside 👀** — recommended; orthogonal to lifecycle. ← **inventor's pick**
|
||||
- (b) "+p" text annotation in audit lines only; no glyph in pills.
|
||||
- (c) Replace 👀 with ✨ for agent-pending rows (single glyph, more compact).
|
||||
|
||||
### Q5 — Selection text in context payload — default on or off?
|
||||
- (a) **Default on**, opt-out via widget settings — recommended. ← **inventor's pick**
|
||||
- (b) Default off, opt-in via widget settings.
|
||||
- (c) Always on, no toggle.
|
||||
|
||||
### Q6 — Widget visibility scope: everywhere except `/paliadin`, or finer?
|
||||
- (a) **Everywhere except `/paliadin`, `/login`, `/onboarding`** —
|
||||
recommended; lowest cognitive load. ← **inventor's pick**
|
||||
- (b) Only on data-bearing pages (dashboard, projects, deadlines, agenda,
|
||||
events, inbox); hide on tool pages (fristenrechner etc.).
|
||||
- (c) User-configurable per page.
|
||||
|
||||
### Q7 — Modal vs dialog: drawer + scrim, or non-modal floating panel?
|
||||
- (a) **Modal slide-out drawer with scrim** (focus-traps) — recommended. ← **inventor's pick**
|
||||
- (b) Non-modal floating panel (page stays interactive while widget is open).
|
||||
|
||||
### Q8 — Keyboard shortcut for opening: Cmd-K?
|
||||
- (a) **Cmd-K / Ctrl-K** — recommended. ← **inventor's pick**
|
||||
- (b) Different shortcut (m to specify).
|
||||
- (c) No shortcut, button-only.
|
||||
|
||||
### Q9 — Context payload truncation cap (selection text)?
|
||||
- (a) **1000 chars** — recommended; balances usefulness vs prompt-bloat. ← **inventor's pick**
|
||||
- (b) Higher cap (5000 chars).
|
||||
- (c) Lower cap (300 chars).
|
||||
|
||||
### Q10 — Persona separation pre-design?
|
||||
- (a) **Single Paliadin, no scope-bouncer pattern** — recommended; YAGNI. ← **inventor's pick**
|
||||
- (b) Add scope-bouncer pattern now (Paliadin refuses non-paliad questions, points at... where?).
|
||||
- (c) Pre-design split with a second skill (Phase 2 case-law researcher).
|
||||
|
||||
### Q11 — Auto-approve some agent suggestions?
|
||||
- (a) **No, every agent suggestion needs the user's eye** — recommended; matches m's #20 verbatim. ← **inventor's pick**
|
||||
- (b) Auto-approve agent suggestions on projects where the user is lead.
|
||||
- (c) Auto-approve when the suggestion was a direct response to "Lege … an" (user opted in by phrasing).
|
||||
|
||||
### Q12 — Recommended implementer?
|
||||
Same substrate as t-paliad-146 + t-paliad-160 + t-paliad-138 (paliadin,
|
||||
approval pipeline, eye-pill UI). Pattern-fluent Sonnet work.
|
||||
- (a) **Any pattern-fluent Sonnet coder** — recommended. ← **inventor's pick**
|
||||
- (b) The same coder who shipped t-paliad-160 (deepest context on the
|
||||
approval pipeline).
|
||||
- (c) Two coders: one on Slices A-C (modal + context), one on Slices D-E
|
||||
(agent-suggest + visual language).
|
||||
|
||||
---
|
||||
|
||||
## 12 · Out of scope (for now) — preserved
|
||||
|
||||
Per m's brief:
|
||||
|
||||
- Direct Paliadin write permission (no RLS bypass, no agent service-role
|
||||
identity). The approval gate stays the only path agents take into prod
|
||||
data.
|
||||
- Multi-turn agent loops — no chained writes without per-step user
|
||||
approval.
|
||||
- Production-v1 Anthropic API cutover for the existing standalone
|
||||
`/paliadin` route (recommended in §6 as a *prerequisite* for opening
|
||||
beyond owner-only, but not committed in this task).
|
||||
- Edits / completes / project-tree as agent-suggestible entities (§7.4
|
||||
defers to Phase 2).
|
||||
- Persona separation infrastructure (§9 defers indefinitely).
|
||||
|
||||
---
|
||||
|
||||
## 13 · Trade-offs flagged
|
||||
|
||||
| Trade-off | What we accept | Mitigation |
|
||||
|---|---|---|
|
||||
| Tmux-relay v1 caps concurrency at one turn per user | Owner-only v1 makes this fine | Spec the relay-interface seam (§6.4) so API cutover is non-disruptive |
|
||||
| Mechanical `<PaliadinWidget />` pass touches ~30 files | Same pattern as t-paliad-042 PWAHead, low risk | One commit per slice keeps blame surface tight |
|
||||
| Agent suggestions unconditionally route through approval | Some users may find it tedious | Phase 2 auto-approve setting (m wants Q11 = no, so this isn't urgent) |
|
||||
| Two glyphs (👀 + ✨) might confuse first-time approvers | Slight onboarding cost | Tooltip on hover; admin/onboarding doc one-liner |
|
||||
| Selection-text in context payload risks accidental info leakage | Low (data already in DB) | Cap + redaction in admin dashboard (§4.3) |
|
||||
| Per-route starter registry needs maintenance as routes evolve | Yes; cost is real | Default fallback ensures no route is silent; route renames are caught by build (registry imports route names as a const map) |
|
||||
|
||||
---
|
||||
|
||||
## 14 · Implementation hygiene
|
||||
|
||||
- **No bare CSS tokens.** New `.paliadin-widget*` + `.approval-pill--agent`
|
||||
CSS uses existing `--color-*` / `--accent-*` / `--bg-soft` tokens. The
|
||||
reminder from t-paliad-150 (third occurrence of bare-token leaks) holds.
|
||||
- **No RAISE EXCEPTION in migration 070** — Maria's build constraint.
|
||||
- **No `2>&1` on diagnostic** — global rule.
|
||||
- **i18n must compile** — every new label gets a key in `client/i18n.ts`
|
||||
+ DE/EN values; `bun run build` regenerates `i18n-keys.ts`.
|
||||
- **Build + vet + test gate** — `go build ./...` + `go vet ./...` +
|
||||
`go test ./...` + `cd frontend && bun run build` all clean before push.
|
||||
- **Don't self-merge** — push branch, comment on Gitea #20, await m's
|
||||
merge gate.
|
||||
- **Don't close issue #20** — m closes issues. Set `done` label on
|
||||
approval.
|
||||
|
||||
---
|
||||
|
||||
## 15 · End-of-shift checklist (this design)
|
||||
|
||||
- [x] Read m/paliad#20 + Klaus's reply (msg #1563 / comment).
|
||||
- [x] Read existing `paliadin.go` + `paliadin_remote.go` + `approval_service.go` + `paliadin-shim` + `install-paliadin-skill` + `~/.claude/skills/paliadin/SKILL.md`.
|
||||
- [x] Read youpc.org reference: `sidebar-widget.html` + `sidebar.js` + `ai-chat-client.js` + `youpc_ai_relay.go`.
|
||||
- [x] Verify live state: paliad.de up, migration tracker at 69, schema columns matched expectations, eye-pill 👀 already wired.
|
||||
- [x] Take a position on every decision in the brief (see §0 table; §11 for the open questions).
|
||||
- [x] No hour estimates anywhere in the doc.
|
||||
- [x] Recommend implementer + phasing.
|
||||
- [ ] Commit this doc on `mai/dirac/inventor-inline-paliadin`.
|
||||
- [ ] Push branch.
|
||||
- [ ] Comment on Gitea #20 with summary + doc link.
|
||||
- [ ] File mBrian synthesis node under `topic-paliadin` (or equivalent).
|
||||
- [ ] `mai report completed "DESIGN READY FOR REVIEW: …"` and **stop**. Do not auto-flip to coder.
|
||||
|
||||
---
|
||||
|
||||
*Inventor parked after this commit. The head will surface to m for the
|
||||
go/no-go gate before any coder shift begins. Skipping that gate has
|
||||
burned commits before (m/mAi#142); the gate is non-negotiable.*
|
||||
Reference in New Issue
Block a user