The inline Paliadin chat surface — reachable from every authenticated
page, replacing the standalone /paliadin route as the primary entry
point. The standalone page survives as the dedicated full-screen mode
(the drawer's "↗ fullscreen" action links to it).
Components:
- frontend/src/components/PaliadinWidget.tsx — emits the floating
trigger button (bottom-right, lime ✨, owner-revealed by JS), a
scrim, and the right-edge slide-out drawer with header (reset /
fullscreen / close), context chip, message stream, empty-state
starter list, and textarea+send form. Loads /assets/paliadin-widget.js.
- frontend/src/client/paliadin-widget.ts — runtime. /api/me probe
reveals the trigger when caller matches PaliadinOwnerEmail (with
optional is_paliadin_owner flag fast-path); Cmd+J / Ctrl+J shortcut
toggles open/close (Cmd+K stays reserved for global search per
client/search.ts). Uses computePaliadinContext() (Slice B) per send
so route + entity + selection flow into every turn. SSE consumer
writes assistant bubbles; localStorage persists per-session history.
- frontend/src/client/paliadin-starters.ts — per-route starter prompt
registry. 14 routes covered (dashboard, projects.*, deadlines.*,
appointments.*, agenda, events, inbox, tools.*, glossary, courts) +
a _default fallback. Bilingual (DE/EN); prompts ending in `: ` seed
the textarea for the user to finish; fully-formed prompts auto-send.
- 39 authenticated TSX pages get a `<PaliadinWidget />` element after
`<Footer />` via a mechanical pass. paliadin.tsx (the standalone)
is intentionally excluded — its dedicated UI is the widget's
fullscreen escape hatch, not a place to overlay another widget.
- frontend/build.ts registers the new bundle.
- frontend/src/styles/global.css gains ~280 lines of widget CSS
(trigger / scrim / drawer / header / context-chip / messages /
bubbles / starters / form / send-btn) using only existing tokens.
Mobile (≤640px): drawer goes full-screen; trigger lifts above
bottom-nav slots.
- 11 new i18n keys × 2 langs = 22 entries under paliadin.widget.*.
Visibility predicate (paliadin-context.shouldSendContext) hides the
widget on /paliadin, /login, /onboarding. Owner-only gate stays on
PaliadinOwnerEmail.
Build clean: i18n 1955 → 1966 keys, IIFE-wrapped 218KB bundle, go test
green.
Refs: docs/design-paliadin-inline-2026-05-08.md §3, §5.
224 lines
6.3 KiB
TypeScript
224 lines
6.3 KiB
TypeScript
// paliadin-starters.ts — per-route starter-prompt registry for the
|
|
// Paliadin inline widget (t-paliad-161).
|
|
//
|
|
// The drawer's empty state renders the matching starter list. Click →
|
|
// the prompt populates the textarea; if the prompt ends with `: ` it
|
|
// stays in the textarea so the user finishes the sentence.
|
|
//
|
|
// Static registry by design. LLM-generated starters were considered and
|
|
// rejected (latency, determinism, translatability — see design doc §5.2).
|
|
|
|
export interface 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: ",
|
|
},
|
|
],
|
|
"projects.list": [
|
|
{
|
|
label_de: "Aktive Akten",
|
|
label_en: "Active projects",
|
|
prompt_de: "Welche Akten sind aktuell aktiv?",
|
|
prompt_en: "Which projects are currently active?",
|
|
},
|
|
{
|
|
label_de: "UPC-Akten",
|
|
label_en: "UPC projects",
|
|
prompt_de: "Zeige mir alle UPC-Akten.",
|
|
prompt_en: "Show me all UPC projects.",
|
|
},
|
|
],
|
|
"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?",
|
|
},
|
|
{
|
|
label_de: "Folgefristen",
|
|
label_en: "Follow-on deadlines",
|
|
prompt_de: "Welche Fristen ergeben sich aus dieser?",
|
|
prompt_en: "What follow-on deadlines flow from this?",
|
|
},
|
|
],
|
|
"deadlines.list": [
|
|
{
|
|
label_de: "Überfällige",
|
|
label_en: "Overdue",
|
|
prompt_de: "Welche Fristen sind überfällig?",
|
|
prompt_en: "Which deadlines are overdue?",
|
|
},
|
|
{
|
|
label_de: "Diese Woche",
|
|
label_en: "This week",
|
|
prompt_de: "Was steht diese Woche an?",
|
|
prompt_en: "What's due this week?",
|
|
},
|
|
{
|
|
label_de: "Frist anlegen",
|
|
label_en: "Add a deadline",
|
|
prompt_de: "Lege eine Frist an: ",
|
|
prompt_en: "Add a deadline: ",
|
|
},
|
|
],
|
|
"appointments.list": [
|
|
{
|
|
label_de: "Heute",
|
|
label_en: "Today",
|
|
prompt_de: "Welche Termine habe ich heute?",
|
|
prompt_en: "What appointments do I have today?",
|
|
},
|
|
{
|
|
label_de: "Termin anlegen",
|
|
label_en: "Add an appointment",
|
|
prompt_de: "Lege einen Termin an: ",
|
|
prompt_en: "Add an appointment: ",
|
|
},
|
|
],
|
|
"appointments.detail": [
|
|
{
|
|
label_de: "Erkläre den Termin",
|
|
label_en: "Explain this appointment",
|
|
prompt_de: "Was ist auf diesem Termin zu klären?",
|
|
prompt_en: "What needs to be addressed at this appointment?",
|
|
},
|
|
],
|
|
"agenda": [
|
|
{
|
|
label_de: "Diese Woche",
|
|
label_en: "This week",
|
|
prompt_de: "Welche Termine und Fristen habe ich diese Woche?",
|
|
prompt_en: "What appointments and deadlines do I have this week?",
|
|
},
|
|
{
|
|
label_de: "Konflikte prüfen",
|
|
label_en: "Check conflicts",
|
|
prompt_de: "Gibt es Terminkonflikte in dieser Ansicht?",
|
|
prompt_en: "Are there scheduling conflicts in this view?",
|
|
},
|
|
],
|
|
"events": [
|
|
{
|
|
label_de: "Diese Woche",
|
|
label_en: "This week",
|
|
prompt_de: "Was steht diese Woche an?",
|
|
prompt_en: "What's on for this week?",
|
|
},
|
|
{
|
|
label_de: "Überfällige",
|
|
label_en: "Overdue",
|
|
prompt_de: "Was ist überfällig?",
|
|
prompt_en: "What's overdue?",
|
|
},
|
|
],
|
|
"inbox": [
|
|
{
|
|
label_de: "Was wartet",
|
|
label_en: "What's waiting",
|
|
prompt_de: "Was wartet auf meine Genehmigung?",
|
|
prompt_en: "What's waiting for my approval?",
|
|
},
|
|
],
|
|
"tools.fristenrechner": [
|
|
{
|
|
label_de: "Erkläre den Rechner",
|
|
label_en: "Explain the calculator",
|
|
prompt_de: "Wie funktioniert der Fristenrechner?",
|
|
prompt_en: "How does the deadline calculator work?",
|
|
},
|
|
{
|
|
label_de: "Verfahrensablauf",
|
|
label_en: "Proceeding flow",
|
|
prompt_de: "Welche Folgefristen kommen typischerweise nach einer Klage?",
|
|
prompt_en: "What deadlines typically follow a complaint?",
|
|
},
|
|
],
|
|
"tools.kostenrechner": [
|
|
{
|
|
label_de: "Erkläre die Berechnung",
|
|
label_en: "Explain the calculation",
|
|
prompt_de: "Wie wird der Streitwert berechnet?",
|
|
prompt_en: "How is the matter value calculated?",
|
|
},
|
|
],
|
|
"glossary": [
|
|
{
|
|
label_de: "Begriff erklären",
|
|
label_en: "Explain a term",
|
|
prompt_de: "Erkläre mir den Begriff: ",
|
|
prompt_en: "Explain the term: ",
|
|
},
|
|
],
|
|
"courts": [
|
|
{
|
|
label_de: "UPC-Divisionen",
|
|
label_en: "UPC divisions",
|
|
prompt_de: "Zeige mir alle UPC Local Divisions.",
|
|
prompt_en: "Show me all UPC Local Divisions.",
|
|
},
|
|
],
|
|
// Fallback for unmapped routes — the textarea stays empty, the user
|
|
// types from scratch.
|
|
"_default": [
|
|
{
|
|
label_de: "Was kann ich für dich tun?",
|
|
label_en: "What can I help with?",
|
|
prompt_de: "",
|
|
prompt_en: "",
|
|
},
|
|
],
|
|
};
|
|
|
|
export function startersFor(routeName: string): Starter[] {
|
|
return paliadinStarters[routeName] || paliadinStarters["_default"];
|
|
}
|