Compare commits

...

4 Commits

Author SHA1 Message Date
m
2201c6da73 fix(t-paliad-150): Paliadin chat — bubble alignment + dark-mode contrast + friendly tmux-unavailable error
Three visual bugs from the t-146 PoC ship.

1. Bubble alignment robustness — keep `align-self: flex-end/-start`
   but also pin with `margin-left/right: auto`. align-self was already
   correct in CSS, but layered margin-auto makes the alignment
   bulletproof against any future cross-axis override.

2. Dark-mode contrast — paliadin CSS used three undefined tokens
   (`--color-accent-tint`, `--color-status-red`, `--color-status-red-tint`,
   `--color-surface-hover`) whose hardcoded fallbacks (`#e8fbb2`, `#fee`,
   etc.) always fired. In dark mode the user bubble rendered light cream
   text on light-lime background, the error bubble light cream on light
   pink — both unreadable. Repointed to the project's actual tokens:
   `--color-bg-lime-tint` (defined in both modes), `--status-red-fg/bg/border`
   (defined in both modes), `--color-surface-2` for the starter hover.
   Added explicit `color: var(--color-text)` to `.paliadin-bubble` and
   `color: var(--status-red-fg)` to the error variant. Same root cause as
   t-paliad-144's contrast sweeps (cf. memory `paliad: undefined --color-bg-muted token`).

3. Friendly tmux-unavailable error — Dokploy container has no tmux/claude
   CLI per CLAUDE.md, so prod hits `event: error` with
   `{"code":"tmux_unavailable", ...}`. The client used to dump the raw
   JSON into the bubble. Now `friendlyErrorMessage()` parses the payload
   and shows a localised "Paliadin läuft nur lokal" notice (DE+EN), with
   a `connection_lost` fallback for native EventSource transport errors
   (no `data`) or anything we don't recognise. Same code path also
   replaces the generic "Fehler beim Senden: …" pre-SSE catch block with
   `paliadin.error.upstream` so transport errors don't leak `String(err)`
   into the UI either.
2026-05-07 22:21:27 +02:00
m
8bdebe9bc1 Merge: landing page text — Patent Litigation + Administration/Knowledge/Tools 2026-05-07 22:11:37 +02:00
m
d53cc3553c fix: landing page text reflects current product — 'Patent Knowledge' → 'Patent Litigation'; 'Leitfäden, Vorlagen und Dokumente' → 'Administration, Knowledge und Tools' (DE+EN) 2026-05-07 22:11:37 +02:00
m
f4aa2033f9 Merge: t-paliad-148 — split project_teams.role into firm-level profession + project-level responsibility (migration 059 + ApprovalService tuple-with-gate ladder + 3-col team table + admin-team profession + onboarding picker) 2026-05-07 22:00:57 +02:00
6 changed files with 49 additions and 18 deletions

View File

@@ -50,7 +50,7 @@ worker:
max_workers: 5
persistent: true
head:
name: "maria"
name: "paliadin"
max_loops: 50
infinity_mode: false
max_idle_duration: 2h0m0s

View File

@@ -82,9 +82,9 @@ const translations: Record<Lang, Record<string, string>> = {
"footer.text": "\u00a9 2026 Paliad \u2014 ein Werkzeug von",
// Landing page
"index.title": `Paliad \u2014 Patentwissen f\u00fcr ${FIRM}`,
"index.title": `Paliad \u2014 Patent Litigation f\u00fcr ${FIRM}`,
"index.hero.accent": `f\u00fcr ${FIRM}`,
"index.hero.sub": `Leitf\u00e4den, Vorlagen und Dokumente f\u00fcr das ${FIRM} Patent-Team.`,
"index.hero.sub": `Administration, Knowledge und Tools f\u00fcr das ${FIRM} Patent-Team.`,
"index.guides.title": "Leitf\u00e4den",
"index.guides.desc": "Praxisleitf\u00e4den zu Verfahren vor dem EPA, BPatG und UPC. Schritt-f\u00fcr-Schritt-Anleitungen f\u00fcr typische Workflows.",
"index.templates.title": "Vorlagen",
@@ -1478,6 +1478,9 @@ const translations: Record<Lang, Record<string, string>> = {
"paliadin.send": "Senden",
"paliadin.stop": "Stop",
"paliadin.reset": "Neue Unterhaltung",
"paliadin.error.local_only": "Paliadin läuft nur lokal. Diese Instanz hat kein tmux/claude installiert — lokal mit ./paliad starten.",
"paliadin.error.connection_lost": "Verbindung verloren.",
"paliadin.error.upstream": "Fehler beim Senden.",
"nav.admin.paliadin": "Paliadin Monitor",
"admin.paliadin.title": "Paliadin Monitor — Paliad",
"admin.paliadin.heading": "Paliadin Monitor",
@@ -2004,9 +2007,9 @@ const translations: Record<Lang, Record<string, string>> = {
"footer.text": "\u00a9 2026 Paliad \u2014 a tool by",
// Landing page
"index.title": `Paliad \u2014 Patent Knowledge for ${FIRM}`,
"index.title": `Paliad \u2014 Patent Litigation for ${FIRM}`,
"index.hero.accent": `for ${FIRM}`,
"index.hero.sub": `Guides, templates, and documents for the ${FIRM} patent team.`,
"index.hero.sub": `Administration, knowledge, and tools for the ${FIRM} patent team.`,
"index.guides.title": "Guides",
"index.guides.desc": "Practical guides for proceedings before the EPO, Federal Patent Court, and UPC. Step-by-step instructions for typical workflows.",
"index.templates.title": "Templates",
@@ -3391,6 +3394,9 @@ const translations: Record<Lang, Record<string, string>> = {
"paliadin.send": "Send",
"paliadin.stop": "Stop",
"paliadin.reset": "New conversation",
"paliadin.error.local_only": "Paliadin only runs locally. This instance has no tmux/claude installed — start it locally via ./paliad.",
"paliadin.error.connection_lost": "Connection lost.",
"paliadin.error.upstream": "Send failed.",
"nav.admin.paliadin": "Paliadin Monitor",
"admin.paliadin.title": "Paliadin Monitor — Paliad",
"admin.paliadin.heading": "Paliadin Monitor",

View File

@@ -1,4 +1,4 @@
import { initI18n, getLang } from "./i18n";
import { initI18n, getLang, t } from "./i18n";
import { initSidebar } from "./sidebar";
// Paliadin chat panel client (t-paliad-146 PoC).
@@ -144,7 +144,7 @@ async function sendTurn(text: string): Promise<void> {
turnRes = await r.json();
} catch (err) {
placeholder.querySelector(".paliadin-bubble-text")!.textContent =
"Fehler beim Senden: " + String(err);
t("paliadin.error.upstream");
placeholder.dataset.streaming = "false";
placeholder.classList.add("paliadin-bubble--error");
toggleStopButton(false);
@@ -188,10 +188,8 @@ async function sendTurn(text: string): Promise<void> {
});
es.addEventListener("error", (ev) => {
const msg = (ev as MessageEvent).data
? "Fehler: " + (ev as MessageEvent).data
: "Verbindung verloren.";
placeholder.querySelector(".paliadin-bubble-text")!.textContent = msg;
placeholder.querySelector(".paliadin-bubble-text")!.textContent =
friendlyErrorMessage((ev as MessageEvent).data);
placeholder.classList.add("paliadin-bubble--error");
placeholder.dataset.streaming = "false";
cleanupTurn();
@@ -202,6 +200,26 @@ async function sendTurn(text: string): Promise<void> {
});
}
// Server emits SSE error events as JSON `{code, message}`. Map known
// codes to localised, user-friendly text; fall through to a generic
// "connection lost" for anything we don't recognise (including raw
// EventSource transport errors where data is absent).
function friendlyErrorMessage(data: unknown): string {
if (typeof data !== "string" || data === "") {
return t("paliadin.error.connection_lost");
}
try {
const parsed = JSON.parse(data) as { code?: string };
if (parsed.code === "tmux_unavailable") {
return t("paliadin.error.local_only");
}
} catch {
// Not JSON — fall through to the generic connection-lost message
// rather than leaking a raw payload into the bubble.
}
return t("paliadin.error.connection_lost");
}
function cleanupTurn(): void {
if (currentEventSource) {
currentEventSource.close();

View File

@@ -1421,6 +1421,9 @@ export type I18nKey =
| "palette.footer.open"
| "palette.section.actions"
| "paliadin.empty"
| "paliadin.error.connection_lost"
| "paliadin.error.local_only"
| "paliadin.error.upstream"
| "paliadin.heading"
| "paliadin.input.placeholder"
| "paliadin.reset"

View File

@@ -34,9 +34,9 @@ export function renderIndex(): string {
<main>
<section className="hero">
<div className="container">
<h1>Patent Knowledge<br /><span className="hero-accent" data-i18n="index.hero.accent">{`für ${FIRM}`}</span></h1>
<h1>Patent Litigation<br /><span className="hero-accent" data-i18n="index.hero.accent">{`für ${FIRM}`}</span></h1>
<p className="hero-sub" data-i18n="index.hero.sub">
{`Leitfäden, Vorlagen und Dokumente für das ${FIRM} Patent-Team.`}
{`Administration, Knowledge und Tools für das ${FIRM} Patent-Team.`}
</p>
</div>
</section>

View File

@@ -11131,7 +11131,7 @@ dialog.quick-add-sheet::backdrop {
}
.paliadin-starter:hover {
background: var(--color-surface-hover, var(--color-surface));
background: var(--color-surface-2);
border-color: var(--color-accent);
}
@@ -11141,23 +11141,27 @@ dialog.quick-add-sheet::backdrop {
border-radius: 12px;
line-height: 1.5;
word-wrap: break-word;
color: var(--color-text);
}
.paliadin-bubble--user {
align-self: flex-end;
background: var(--color-accent-tint, #e8fbb2);
margin-left: auto;
background: var(--color-bg-lime-tint);
border: 1px solid var(--color-accent);
}
.paliadin-bubble--assistant {
align-self: flex-start;
margin-right: auto;
background: var(--color-surface);
border: 1px solid var(--color-border);
}
.paliadin-bubble--error {
border-color: var(--color-status-red, #c54);
background: var(--color-status-red-tint, #fee);
color: var(--status-red-fg);
border-color: var(--status-red-border);
background: var(--status-red-bg);
}
.paliadin-bubble-role {
@@ -11182,7 +11186,7 @@ dialog.quick-add-sheet::backdrop {
.paliadin-chip {
display: inline-block;
background: var(--color-accent-tint, #e8fbb2);
background: var(--color-bg-lime-tint);
border: 1px solid var(--color-accent);
color: var(--color-text);
padding: 0.15rem 0.5rem;