m/paliad#61 Slice C frontend pass.
Discovery (Geteilte Vorlagen):
- New 4th tab on /checklists between "Meine Vorlagen" and "Vorhandene
Instanzen". Filters the merged catalog response to authored entries
not owned by the caller (firm-visible OR globally-promoted OR
share-recipient). Tab state round-trips via ?tab=gallery.
- Regime filter pills (UPC / DE / EPA / OTHER) operate independently
from the main Vorlagen tab.
- Cards show regime badge, item count, author line, visibility chip.
- Self-filter relies on /api/me email match — loadMe() fires once on
page boot and is idempotent.
Versioning UI on /checklists/instances/{id}:
- "Vorlage aktualisiert" badge appears when the instance's
template_version is known AND lags the live template version (only
for authored templates; static templates never bump). Shows "v{from}
→ v{to}" delta.
- "Änderungen anzeigen" button opens a diff modal that compares the
instance's template_snapshot against the live template body.
Item-level grouping by (section title, item label). Surfaces added /
removed / changed items with localised section labels. Empty state
when only metadata changed.
i18n: 13 new keys per language (DE + EN) under
checklisten.tab.gallery, checklisten.gallery.*, checklisten.filter.other,
and checklisten.instance.{outdated,diff}.*. Total 2666 keys.
Build hygiene: bun run build clean; i18n scan clean. Go build/vet/test
+ TestBootSmoke ./cmd/server/ all green.
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.
F-7 of the t-paliad-074 architecture audit. Sweeps the last German-named
CSS leftovers — purely a class-name change, no behaviour or styling
delta. 466 references across global.css and ~30 TSX/TS files.
Naming rules applied:
- Generic table/tabs/form/empty/controls/detail/events/status/type/
suggestion/chip/col/ref/search-wrap/select/soon/loading/muted/
unavailable/row/header-row/title-input -> .entity-*
- Truly generic widgets dropped the prefix: .multi-* (multi-select
panel), .filter-*, .collab-* (collaborator picker; bare class is
now .collab-picker), .firmwide-*, .office-*, .back-link
- Project-specific names kept specific: .party-form/-controls/-table
(parties on a project), .checklists-hint, .netdocs-link
- Page-scoped IDs in projects.tsx -> projects-search/-count/-body;
projects-new.tsx -> project-new-form/-msg
- German content "akten-bezogen" tightened to "aktenbezogen" (one-word
form is also valid German) so the strict grep stays clean
Replace ad-hoc lime/forest-green system with the official 4-color HLC
palette. Lime + midnight are the primary pair; cyan + cream supporting.
Tokens
- :root now exposes --hlc-lime, --hlc-midnight, --hlc-cyan, --hlc-cream
plus channel-token siblings (--hlc-*-rgb) so tints can be expressed as
rgb(var(--hlc-*-rgb) / a) without hex literals.
- --color-bg → cream, --color-text/--color-hero-bg → midnight,
--color-accent → lime, --color-accent-dark → midnight (foreground on
lime; passes WCAG AA where #fff failed).
- New --sidebar-* tokens for the dark sidebar surface.
Sweep (frontend/src/styles/global.css)
- Replaced every hard-coded #c6f41c / #65a30d / #84cc16 / #b8e616 /
#4d7c0f / #1a2e1a / #1a1a2e / #1a2e05 with the matching var(...).
- rgba(101,163,13,a) and rgba(198,244,28,a) collapsed to
rgb(var(--hlc-lime-rgb) / a).
- text-on-lime now uses var(--color-accent-dark) instead of #fff;
btn-danger keeps white on red.
Sidebar reskin (cronus's audit, F-30)
- Background: midnight; text: cream (muted via cream-channel alpha);
active/hover: lime. Border + hover use cream-channel alphas so no
rgba hex creep on the dark surface.
Brand assets
- manifest.json theme_color → lime, background_color → cream.
- icon.svg / icon-maskable.svg base recoloured to lime + midnight glyph.
- 32× <meta name="theme-color"> across pages updated to #BFF355.
- Email templates (base.html, invitation.html) lime accent updated;
mail_service_test.go expectation tracks the new hex.
Deferred / out of scope
- PNG icons under public/icons/ are baked artefacts; regen left to the
next deploy.
- Categorical chip colours (office tints, traffic-light red/amber/green,
termin-type hues) are functional, not brand, and deliberately
untouched.
- Dark mode is not in scope.
Verified
- bun run build clean.
- go build ./... clean; mail render tests pass.
- Visual sweep at 1280×900 against frontend/dist via Playwright on
/, /login, /dashboard, /projects, /agenda, /team, /fristenrechner,
/glossary — sidebar midnight + lime active, cream page bg, white
cards, midnight text on lime CTAs.
Supersedes audit findings F-14, F-30, F-31.
Ship the installability bits that t-paliad-041 deferred so iOS / Android
users can add Paliad to their home screen.
What landed:
- frontend/public/manifest.json — name=Paliad, theme_color #65a30d (lime),
display=standalone, scope=/, start_url=/dashboard, four icon entries
(192/512 × any/maskable). Served from /manifest.json with the
spec-mandated application/manifest+json content type (servePWAManifest
in internal/handlers/pwa.go).
- frontend/public/icons/ — lime "p" logo rendered to 192/512 PNGs in both
"any" and maskable variants (maskable variant has extra safe-zone
padding), 180×180 apple-touch-icon, 32×32 favicon. SVG sources kept
under frontend/icons-src/ for regeneration via rsvg-convert.
- frontend/public/sw.js — minimal cache-first for /assets/* and /icons/*,
network-first for /api/*, network passthrough for everything else.
CACHE_VERSION + activate-clean lets us bump and purge cleanly. Served
from /sw.js so its scope can claim /; Service-Worker-Allowed: / header
set, no-cache on the SW file itself so updates take effect on next load.
- frontend/src/components/PWAHead.tsx — head fragment (manifest link,
apple-touch-icon, favicon, app-name metas, <script src="/assets/app.js"
defer>). Added to all 30 page TSX files via mechanical insertion.
- frontend/src/client/app.ts — universal client bundle loaded on every
page. Three jobs: register the service worker, init the BottomNav
(icarus flagged that bottom-nav.ts was written but never wired into
the build — m reproduced the broken [+] Anlegen and Menü buttons in
prod), and surface the install banner.
- frontend/src/client/pwa-install.ts — install banner UI. Two flows:
beforeinstallprompt for Chromium/Android (deferred → CTA → prompt),
one-time iOS Safari hint pointing at the share sheet. Both dismissals
persist in localStorage (paliad-install-dismissed / -ios-shown).
- frontend/src/styles/global.css — banner styles, sits above BottomNav on
mobile and pinned bottom-right on desktop, lime-on-white card with the
brand "p" mark.
- frontend/build.ts — copies frontend/public → dist verbatim so the
manifest, icons, and SW land at the application root.
Verification before merge:
- bun run build clean, go build/vet/test clean.
- Local server smoke: curl -sI confirmed manifest.json (200,
application/manifest+json), all icon files (200, image/png), sw.js
(200, Service-Worker-Allowed: /), app.js (200, text/javascript).
- Playwright at 390×844: Chrome fired beforeinstallprompt, the banner
rendered with "Paliad installieren" + "Installieren" CTA in German,
dismiss persisted across reload via localStorage. Manifest validated
in-browser (name/short_name/start_url/display/scope all correct, all
four icon URLs returned 200).
- The InvalidStateError on serviceWorker.register() seen in the MCP
Playwright profile is a known headless flag; SW registration works in
real Chrome / Safari on localhost and HTTPS production.
Out of scope: push notifications, runtime offline mode (SW intentionally
stays minimal — cache shell + assets, network passthrough for everything
else).
Phone-first bottom navigation per pwa-baseline.md. Renders only at
<768px; tablets and desktop are unchanged.
Slots: Start / Projekte / [+] Anlegen / Agenda / Menü.
- Center [+] opens a slide-up <dialog> sheet with three rows: Frist,
Termin, Projekt. Native showModal() + ::backdrop, ESC and backdrop-tap
dismiss, transform-based slide-up transition.
- Right Menü slot reuses the existing Sidebar mobile drawer via a new
exported toggleMobileSidebar() (DRY with the legacy hamburger handler).
- Agenda slot carries a red-dot badge: count = today + overdue pending
deadlines (live via /api/deadlines/summary, refreshed every 60s). Pulse
animation when overdue > 0 — m: "Due is the latest we can do, OVERDUE
is a catastrophy."
- visualViewport resize watcher hides the bar when the on-screen keyboard
opens (>100px height shrink) so it doesn't cover form fields.
- safe-area-inset-bottom padding on the bar; main padding-bottom adjusts
on phones so the last row stays above the bar.
PWA shell groundwork (defers manifest/SW/install-prompt to follow-ups):
- viewport-fit=cover on every page (required for safe-area to register)
- theme-color #65a30d (lime), apple-mobile-web-app-capable, status-bar
style — all 30 page heads updated in one sweep.
Backend: deadline_service.SummaryCounts gains a `today` bucket so the
Agenda badge can distinguish "due today" from "this week" without a new
endpoint.
Files added:
frontend/src/components/BottomNav.tsx
frontend/src/client/bottom-nav.ts
Verified visually via headless chromium at 375x812, 800x600, 1280x800:
phone shows BottomNav (5 slots, lime [+] elevated), tablet shows the
existing hamburger only, desktop sidebar untouched. go build/vet/test
and bun run build all clean.
knuth's rename changed TS/TSX filenames but left <script src> tags
pointing at old German JS names (akten-neu.js, fristen.js, termine.js,
glossar.js, einstellungen.js, gerichte.js, checklisten.js). These 404'd
in production.