Index: src/views/shell.ejs
===================================================================
--- src/views/shell.ejs	(revision 34875673eb7989a5ab1986334ece32f689bab211)
+++ src/views/shell.ejs	(revision 48027f29830b79c87770fe10fc1f9970d91fcc63)
@@ -1,8 +1,13 @@
 <%
 // ── Helpers used inside this template ─────────────────────────────
+// Escape for double-quoted HTML attributes. IMPORTANT: emit this with the RAW EJS output
+// tag, never the escaping one — escaping it a second time turned og:title "Jason's" into the
+// double-escaped "Jason&amp;#39;s", and naive OG scrapers (Signal/WhatsApp) show that literally.
+// We deliberately do NOT escape the apostrophe: it is safe inside a double-quoted attribute and
+// a literal apostrophe is what link-preview scrapers expect.
 function _e(s) {
   return String(s == null ? '' : s)
     .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
-    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
+    .replace(/"/g, '&quot;');
 }
 
@@ -104,5 +109,5 @@
 }
 %><!DOCTYPE html>
-<html lang="<%= _e(lang) %>" data-palette="<%= _e((typeof palette !== 'undefined' && palette) ? palette : (safeSite.palette || 'klonkt')) %>">
+<html lang="<%- _e(lang) %>" data-palette="<%- _e((typeof palette !== 'undefined' && palette) ? palette : (safeSite.palette || 'klonkt')) %>">
 <head>
 <meta charset="utf-8">
@@ -111,29 +116,29 @@
 
 <title><%= _finalTitle %></title>
-<meta name="description" content="<%= _e(_socialDescr) %>">
-<meta name="theme-color" content="<%= _e(safeAccent) %>">
+<meta name="description" content="<%- _e(_socialDescr) %>">
+<meta name="theme-color" content="<%- _e(safeAccent) %>">
 <meta name="robots" content="<%= _shouldIndex ? 'index,follow' : 'noindex,nofollow' %>">
-<% if (safeSite.author) { %><meta name="author" content="<%= _e(safeSite.author) %>"><% } %>
-<% if (_canonical) { %><link rel="canonical" href="<%= _e(_canonical) %>"><% } %>
+<% if (safeSite.author) { %><meta name="author" content="<%- _e(safeSite.author) %>"><% } %>
+<% if (_canonical) { %><link rel="canonical" href="<%- _e(_canonical) %>"><% } %>
 
 <!-- Search-engine verification -->
-<% if (safeSite.google_verification)    { %><meta name="google-site-verification" content="<%= _e(safeSite.google_verification) %>"><% } %>
-<% if (safeSite.bing_verification)      { %><meta name="msvalidate.01" content="<%= _e(safeSite.bing_verification) %>"><% } %>
-<% if (safeSite.pinterest_verification) { %><meta name="p:domain_verify" content="<%= _e(safeSite.pinterest_verification) %>"><% } %>
-<% if (safeSite.yandex_verification)    { %><meta name="yandex-verification" content="<%= _e(safeSite.yandex_verification) %>"><% } %>
+<% if (safeSite.google_verification)    { %><meta name="google-site-verification" content="<%- _e(safeSite.google_verification) %>"><% } %>
+<% if (safeSite.bing_verification)      { %><meta name="msvalidate.01" content="<%- _e(safeSite.bing_verification) %>"><% } %>
+<% if (safeSite.pinterest_verification) { %><meta name="p:domain_verify" content="<%- _e(safeSite.pinterest_verification) %>"><% } %>
+<% if (safeSite.yandex_verification)    { %><meta name="yandex-verification" content="<%- _e(safeSite.yandex_verification) %>"><% } %>
 
 <!-- Feed autodiscovery -->
 <% if (site) { %>
-<link rel="alternate" type="application/rss+xml"  title="<%= _e(_siteTitle) %> — RSS"  href="<%= _e(safeUrlBase + '/feed.xml') %>">
-<link rel="alternate" type="application/atom+xml" title="<%= _e(_siteTitle) %> — Atom" href="<%= _e(safeUrlBase + '/atom.xml') %>">
+<link rel="alternate" type="application/rss+xml"  title="<%- _e(_siteTitle) %> — RSS"  href="<%- _e(safeUrlBase + '/feed.xml') %>">
+<link rel="alternate" type="application/atom+xml" title="<%- _e(_siteTitle) %> — Atom" href="<%- _e(safeUrlBase + '/atom.xml') %>">
 <% } %>
 
 <!-- PWA -->
-<link rel="manifest" href="<%= _e(safeUrlBase + '/manifest.webmanifest') %>">
+<link rel="manifest" href="<%- _e(safeUrlBase + '/manifest.webmanifest') %>">
 <meta name="mobile-web-app-capable" content="yes">
 <meta name="apple-mobile-web-app-capable" content="yes">
 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
-<meta name="apple-mobile-web-app-title" content="<%= _e(_siteTitle.slice(0, 16)) %>">
-<link rel="apple-touch-icon" href="<%= _e(safeSite.profile_photo || '/favicon.ico') %>">
+<meta name="apple-mobile-web-app-title" content="<%- _e(_siteTitle.slice(0, 16)) %>">
+<link rel="apple-touch-icon" href="<%- _e(safeSite.profile_photo || '/favicon.ico') %>">
 <link rel="icon" type="image/svg+xml" href="/favicon.svg?v=sf">
 <link rel="alternate icon" href="/favicon.ico?v=sf">
@@ -141,11 +146,11 @@
 <!-- OpenGraph -->
 <meta property="og:type"        content="<%= _ogType %>">
-<meta property="og:title"       content="<%= _e(_socialTitle) %>">
-<meta property="og:description" content="<%= _e(_socialDescr) %>">
-<meta property="og:site_name"   content="<%= _e(_siteTitle) %>">
-<meta property="og:locale"      content="<%= _e(ogLocale) %>">
+<meta property="og:title"       content="<%- _e(_socialTitle) %>">
+<meta property="og:description" content="<%- _e(_socialDescr) %>">
+<meta property="og:site_name"   content="<%- _e(_siteTitle) %>">
+<meta property="og:locale"      content="<%- _e(ogLocale) %>">
 <% if (_socialImage) { %>
-<meta property="og:image"     content="<%= _e(_socialImage) %>">
-<meta property="og:image:alt" content="<%= _e(_socialTitle) %>">
+<meta property="og:image"     content="<%- _e(_socialImage) %>">
+<meta property="og:image:alt" content="<%- _e(_socialTitle) %>">
 <% if (_ogGenerated) { %>
 <meta property="og:image:width"  content="1200">
@@ -154,5 +159,5 @@
 <% } %>
 <% } %>
-<% if (_canonical) { %><meta property="og:url" content="<%= _e(_canonical) %>"><% } %>
+<% if (_canonical) { %><meta property="og:url" content="<%- _e(_canonical) %>"><% } %>
 <%
 // Fediverse/social PLAYER card for posts with audio: instead of shipping the raw
@@ -167,6 +172,6 @@
 %>
 <% if (_postAudio) { %>
-<meta property="og:video"            content="<%= _e(_embedUrl) %>">
-<meta property="og:video:secure_url" content="<%= _e(_embedUrl) %>">
+<meta property="og:video"            content="<%- _e(_embedUrl) %>">
+<meta property="og:video:secure_url" content="<%- _e(_embedUrl) %>">
 <meta property="og:video:type"       content="text/html">
 <meta property="og:video:width"      content="480">
@@ -174,20 +179,20 @@
 <% } %>
 <% if (typeof post !== 'undefined' && post && post.published_at) { %>
-<meta property="article:published_time" content="<%= _e(post.published_at) %>">
-<% if (post.author_username) { %><meta property="article:author" content="<%= _e(post.author_username) %>"><% } %>
-<% } %>
-<% if (safeSite.facebook_app_id) { %><meta property="fb:app_id" content="<%= _e(safeSite.facebook_app_id) %>"><% } %>
+<meta property="article:published_time" content="<%- _e(post.published_at) %>">
+<% if (post.author_username) { %><meta property="article:author" content="<%- _e(post.author_username) %>"><% } %>
+<% } %>
+<% if (safeSite.facebook_app_id) { %><meta property="fb:app_id" content="<%- _e(safeSite.facebook_app_id) %>"><% } %>
 
 <!-- Twitter Cards -->
 <meta name="twitter:card"        content="<%= _postAudio ? 'player' : (_socialImage ? 'summary_large_image' : 'summary') %>">
 <% if (_postAudio) { %>
-<meta name="twitter:player"        content="<%= _e(_embedUrl) %>">
+<meta name="twitter:player"        content="<%- _e(_embedUrl) %>">
 <meta name="twitter:player:width"  content="480">
 <meta name="twitter:player:height" content="480">
 <% } %>
-<meta name="twitter:title"       content="<%= _e(_socialTitle) %>">
-<meta name="twitter:description" content="<%= _e(_socialDescr) %>">
-<% if (_socialImage)         { %><meta name="twitter:image"   content="<%= _e(_socialImage) %>"><% } %>
-<% if (safeSite.twitter)     { %><meta name="twitter:creator" content="<%= _e(safeSite.twitter) %>"><meta name="twitter:site" content="<%= _e(safeSite.twitter) %>"><% } %>
+<meta name="twitter:title"       content="<%- _e(_socialTitle) %>">
+<meta name="twitter:description" content="<%- _e(_socialDescr) %>">
+<% if (_socialImage)         { %><meta name="twitter:image"   content="<%- _e(_socialImage) %>"><% } %>
+<% if (safeSite.twitter)     { %><meta name="twitter:creator" content="<%- _e(safeSite.twitter) %>"><meta name="twitter:site" content="<%- _e(safeSite.twitter) %>"><% } %>
 
 <% if (_jsonLd) { %>
@@ -266,7 +271,7 @@
   :root,
   [data-palette] {
-    --accent: <%= _e(safeAccent) %>;
-    --accent-soft: color-mix(in srgb, <%= _e(safeAccent) %> 80%, white);
-    --accent-tint: color-mix(in srgb, <%= _e(safeAccent) %> 12%, transparent);
+    --accent: <%- _e(safeAccent) %>;
+    --accent-soft: color-mix(in srgb, <%- _e(safeAccent) %> 80%, white);
+    --accent-tint: color-mix(in srgb, <%- _e(safeAccent) %> 12%, transparent);
   }
 </style>
@@ -323,5 +328,5 @@
 </head>
 
-<body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-feed-view="<%= _e(safeSite.feed_view_default || 'timeline') %>" data-grid-cols="3" data-site-base="<%= _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>">
+<body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-feed-view="<%- _e(safeSite.feed_view_default || 'timeline') %>" data-grid-cols="3" data-site-base="<%- _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>">
 
 <% if (typeof isViewer !== 'undefined' && isViewer) { %>
