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).
Ship the appointments feature with bidirectional CalDAV synchronisation.
Closes KanzlAI audit §1.3 by encrypting CalDAV passwords at rest with
AES-256-GCM; plaintext credentials never touch the DB or API responses.
Backend
- `internal/services/termin_service.go`: CRUD with per-row visibility.
Personal Termine (akte_id NULL) visible only to created_by; Akte-attached
Termine follow AkteService.GetByID. Every Akte-attached mutation appends
an akten_events row for the audit trail.
- `internal/services/caldav_service.go` (+ caldav_client.go, caldav_ical.go,
caldav_crypto.go): per-user goroutine, 60s tick, push VEVENT + pull with
UID/ETag reconciliation. Last-write-wins on conflict; conflicts on
Akte-attached Termine append to akten_events.
- CALDAV_ENCRYPTION_KEY env var (32-byte AES-256, base64). Server refuses
to start with malformed key; unset key leaves CalDAV disabled and all
/api/caldav-config* endpoints return 501.
- Migration 013: paliad.user_caldav_config (password_encrypted bytea) +
paliad.caldav_sync_log (last-5 per user). RLS: user owns their row only.
- HTTP handlers: GET/POST/PATCH/DELETE /api/termine, GET
/api/akten/{id}/termine, /api/caldav-config CRUD + /test + /log.
Frontend
- Termine list / detail / new / kalender pages (Bun TSX + per-page client
TS), calendar month grid with type-coloured dots and click-popup.
- Einstellungen/CalDAV settings page: URL/user/password (write-only),
test-connection button, status card, sync log table, delete button that
purges credentials.
- Akten detail "Termine" tab replaces the Phase D placeholder — inline
add-termin form + list.
- Sidebar: Termine entry activated; new "Einstellungen" group with CalDAV.
- DE/EN i18n complete for every new surface.
Security posture
- AES-GCM with 12-byte random nonce prepended to ciphertext
- Password field has `json:"-"` on the model; API never returns it
- Frontend always sends password via write-only <input type=password>
- DeleteConfig purges the encrypted blob from the primary row
- TestConnection without stored creds requires explicit password
t-paliad-010
Replace Go HTML template rendering with a Bun + TSX build-time static
site generator. Go backend becomes API-only for auth.
Frontend:
- Custom JSX-to-HTML-string factory (zero dependencies)
- TSX components for Header, Footer, index page, login page
- Client-side login.ts handles tab switching and fetch()-based auth
- Bun bundler compiles client JS, build.ts renders pages to dist/
Backend:
- Auth handlers return JSON (POST /api/login, POST /api/register)
- Login page served as static HTML from dist/
- Static assets served from /assets/ (public)
- Auth middleware unchanged (cookie check, redirect to /login)
- Removed template parsing and renderPage
Dockerfile:
- 3-stage build: Bun frontend -> Go backend -> alpine runtime
- Frontend dist copied to /app/dist in final image
Removed: templates/, static/css/ (replaced by frontend/)
The .gitignore pattern "server" was matching cmd/server/ directory,
preventing main.go from being tracked. Anchored binary patterns to
repo root with leading slash.
Go web server (net/http, port 8080) serving HTML templates with a
professional landing page for patholo.de. Multi-stage Dockerfile
and docker-compose.yml ready for Dokploy deployment.
- cmd/server/main.go: HTTP server entry point
- internal/handlers: route registration and template rendering
- templates/: base layout + bilingual landing page (DE/EN)
- static/css/: clean, responsive CSS with HL navy branding
- Dockerfile: multi-stage build (golang:1.23-alpine -> alpine:3.21)
- docker-compose.yml: single web service on port 8080