Replaces the one-sentence "endpoint" stub with a proper landing: features list, update flow explainer, fresh-install download link, contact line. Renders the served version live from version.json. Paliad palette (midnight/lime). This is what the HL Patents Style ribbon's Info dialog now links to on OK.
Hosts the manifest + .dotm that the Word ribbon's Check-for-Updates button polls. paliad.msbls.de is the primary endpoint; hihlc.msbls.de mirrors it (hihlc/main b871ded). Files live in frontend/public/patentstyle/, copied into dist/ by the frontend build. Cache-Control: no-cache via noCacheAssets so version.json never serves stale after a release.
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.
After step 2 deployed the IIFE-wrapped bundles, m's browser still saw
the broken page because /assets/projects.js was being served from the
local HTTP cache (no Cache-Control, just heuristic freshness from
Last-Modified). Even after the new SW activated and cleared its own
caches, its cacheFirst handler did `fetch(req)` which goes through the
browser HTTP cache — re-fed the SW cache from the stale bundle and the
loop perpetuated forever.
Three mutually reinforcing fixes:
1. SW cacheFirst now does `fetch(req, { cache: "reload" })` for the
network leg. Forces the network fetch to bypass the browser's HTTP
cache, so the SW always seeds its own cache from a true network read.
2. Go static handlers for /assets/* and /icons/* set
`Cache-Control: no-cache, must-revalidate`. Combined with the
Last-Modified that http.FileServer already emits, browsers send
If-Modified-Since and the server replies 304 when unchanged — fast
for repeat loads, fresh on every deploy. Users without a SW (or after
the kill-switch unregistered theirs) now also pick up new bundles
immediately.
3. pwa-install.ts gates the install banner on
`(min-width: 768px)` — same breakpoint the BottomNav and other
mobile-shell elements use. Desktop partners no longer get an install
prompt covering their work area.
ROOT CAUSE of /projects empty state: the per-page bundles (app.js,
projects.js, dashboard.js, …) were emitted by bun build without an IIFE
wrapper, and loaded as classic <script> tags. Every top-level `var`,
`let`, `const`, and `function` declaration therefore became a property
of the global object.
After t-paliad-042 added app.js to every page (loaded with defer, before
DOMContentLoaded), the minified `var d = "patholo-sidebar-pinned"`
inside app.js (the legacy sidebar-pinned localStorage key constant)
clobbered projects.js's minified `function d() { … }` (the
`applyTranslations` helper). When projects.js's DOMContentLoaded handler
called initI18n → applyTranslations → `d()`, `d` was now the string
"patholo-sidebar-pinned" → "TypeError: d is not a function" → the
fetch to /api/projects never even fired → table stayed empty → empty
state showed.
Fix: pass `format: "iife"` to Bun.build so every entry is wrapped in
`(()=>{ … })()`. Top-level identifiers are now scoped per bundle and
cannot collide on `window`. Verified locally: window.d, window.r,
window.K all `undefined` after both app.js and projects.js execute.
While here, replace the t-paliad-043 step 1 kill-switch SW with the
proper versioned cache pattern the brief asked for:
- frontend/public/sw.js carries `__PALIAD_BUILD_VERSION__` placeholder
- frontend/build.ts substitutes `v<Date.now()>` after copying public/
into dist/, so every deploy opens a fresh `<version>-static` cache
- activate handler deletes any cache whose name doesn't match current,
which evicts both the old paliad-v1-static cache and any kill-switch
survivors the moment a user lands on the new deploy
- skipWaiting + clients.claim so the new SW takes over on the next
navigation rather than waiting for every tab to close
Emergency: t-paliad-042 shipped a service worker that cached a broken
/assets/projects.js (crashes on init with "d is not a function"), making
/projects show the empty state. Mobile Safari users have no devtools to
manually unregister the SW.
Replace sw.js with a self-destructing variant: on activate, delete every
cache, unregister itself, and force every open client to navigate to a
fresh page. /sw.js is served with no-cache headers so browsers refetch
on the next navigation and propagate the kill-switch automatically.
Step 2 (separate commit): fix the projects.js bundle bug, then ship a
properly versioned SW that evicts stale caches on every deploy.
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).