Files
neuron-web/scripts/restore-chat-js-with-preview.py
Will Anderson 0508cd77fd
Deploy marketing to Cloud Run / deploy (push) Successful in 3m43s
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m26s
Dev — Build & local smoke test / build-smoke (push) Successful in 2m29s
fix(chat): raise history cap and add 30s frontend timeout
The demo chat was silently dropping conversation context past 40 turns
and leaving the thinking bubble spinning forever when the soul backend
hung — visitors saw a frozen UI with no way to know what went wrong.

Changes:
  - Stored history cap raised from 40 → 200 messages so longer
    conversations actually persist across page refreshes.
  - History sent to backend per turn raised from 20 → 50 messages.
  - 30s AbortController timeout on the /api/demo fetch — surfaces a
    distinct "Took too long to respond" error instead of hanging.
  - Restore script (restore-chat-js-with-preview.py) is now correctly
    idempotent in both directions: detects when modal HTML is inlined
    but chat JS got extracted to an asset, and re-injects fresh source
    so extract-js picks up changes on the next build.
2026-05-04 01:57:38 -05:00

511 lines
25 KiB
Python

#!/usr/bin/env python3
"""
restore-chat-js-with-preview.py - Re-inline the chat-widget JS into
styles.el's page_close() so that extract-js.py picks up the freshly modified
source on the next build (instead of carrying the obfuscated old asset
forward forever).
What this writes:
- The original chat-widget IIFE (from commit 640813e^), modified to
capture bubble.innerHTML on Share-click and open a preview modal
instead of POSTing to /api/share immediately.
- Modal HTML (#neuron-share-preview-modal) inserted right before the
chat-widget script.
- The /api/share POST in publishSharePreview() sends both answer (legacy
plaintext), answer_html (rendered, sanitized server-side), and
answer_plaintext (og:desc).
Idempotent. Re-run is a no-op once the inline block is present.
"""
from __future__ import annotations
import re
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
STYLES_EL = REPO_ROOT / "src" / "styles.el"
MARKER = "neuron-share-preview-modal"
# El strings use \" for embedded ". Newlines stay literal. Backticks fine.
# We assemble the inline JS as a single literal: opening <script> ... </script>.
# Every double-quote inside must become \\" in the El source. We write it as a
# raw string and use string substitution at the end.
CHAT_HTML_AND_JS = r"""
<!-- Share preview modal: shown after the user clicks Share on an AI bubble.
Renders the share-card layout in an iframe (via srcdoc) so the visitor
sees exactly what the public card will look like before publishing. -->
<div id=\"neuron-share-preview-modal\" style=\"display:none;position:fixed;inset:0;z-index:200000;background:rgba(13,13,20,.55);align-items:center;justify-content:center;padding:1.5rem;font-family:'IBM Plex Sans',system-ui,sans-serif\">
<div style=\"background:#fff;width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;border-radius:12px;box-shadow:0 24px 80px rgba(0,0,0,.35);overflow:hidden\">
<div style=\"display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid rgba(0,0,0,.08)\">
<div>
<div style=\"font-size:.65rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#6B6B7E\">Preview</div>
<div style=\"font-size:1rem;font-weight:500;color:#0D0D14;margin-top:.15rem\">This is what you are about to publish</div>
</div>
<button type=\"button\" id=\"neuron-share-preview-close\" aria-label=\"Close\" style=\"background:none;border:none;cursor:pointer;color:#6B6B7E;padding:.25rem;line-height:1;font-size:1.5rem\">&times;</button>
</div>
<iframe id=\"neuron-share-preview-frame\" style=\"flex:1;width:100%;min-height:420px;border:0;background:#FAFAF8\" sandbox=\"allow-same-origin\"></iframe>
<div style=\"display:flex;align-items:center;justify-content:flex-end;gap:.6rem;padding:.85rem 1.25rem;border-top:1px solid rgba(0,0,0,.08);background:#FAFAF8\">
<button type=\"button\" id=\"neuron-share-preview-cancel\" style=\"background:#fff;border:1px solid rgba(0,0,0,.18);color:#3A3A4A;cursor:pointer;padding:.55rem 1rem;font:inherit;font-size:.8rem;font-weight:500;border-radius:6px\">Cancel</button>
<button type=\"button\" id=\"neuron-share-preview-publish\" style=\"background:#0052A0;border:1px solid #0052A0;color:#fff;cursor:pointer;padding:.55rem 1.1rem;font:inherit;font-size:.8rem;font-weight:600;letter-spacing:.04em;border-radius:6px\">Publish to gallery</button>
</div>
</div>
</div>
<script>
(function() {
if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true }); }
var TURNSTILE_SITE_KEY = '0x4AAAAAADHAZXyuRb3yD9mr';
var turnstileToken = '';
var turnstileWidgetId = null;
var turnstileVerified = false;
var isOpen = false;
var MAX = 10;
// ── Share preview modal helpers ──────────────────────────────────────────
// Captures the rendered (marked.js) HTML from the AI bubble and shows a
// preview before publishing. The actual /api/share POST + gallery insert
// only fires when the user clicks Publish in the modal.
var SHARE_CARD_CSS = \"*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}body{font-family:'IBM Plex Sans',system-ui,sans-serif;background:#FAFAF8;color:#0D0D14;padding:1.25rem .75rem;min-height:100vh}.chat-frame{background:#fff;border:1px solid rgba(0,0,0,.09);box-shadow:0 4px 32px rgba(0,0,0,.07),0 1px 4px rgba(0,0,0,.04);padding:1.25rem;display:flex;flex-direction:column;gap:1rem;max-width:560px;margin:0 auto}.chat-row-user{display:flex;flex-direction:row-reverse}.chat-row-ai{display:flex;flex-direction:row;align-items:flex-end;gap:.625rem}.bubble-user{background:#0052A0;color:#fff;border-radius:18px 18px 4px 18px;padding:11px 15px;max-width:78%;font-size:.875rem;line-height:1.55;word-break:break-word}.bubble-ai{background:#FAFAF8;color:#0D0D14;border:1px solid rgba(0,0,0,.07);border-radius:18px 18px 18px 4px;padding:11px 15px;max-width:88%;font-size:.875rem;font-weight:300;line-height:1.65;word-break:break-word;box-shadow:0 2px 6px rgba(0,0,0,.05)}.bubble-ai p{margin:0}.bubble-ai p+p{margin-top:.6rem}.bubble-ai ul,.bubble-ai ol{margin:.5rem 0 .5rem 1.25rem;padding:0}.bubble-ai li+li{margin-top:.25rem}.bubble-ai strong{font-weight:600}.bubble-ai em{font-style:italic}.bubble-ai code{font-family:'IBM Plex Mono','Menlo',monospace;font-size:.8rem;background:rgba(0,0,0,.05);padding:1px 4px;border-radius:3px}.bubble-ai pre{background:rgba(0,0,0,.05);padding:.75rem;border-radius:6px;overflow-x:auto;font-size:.8rem;margin:.5rem 0}.bubble-ai pre code{background:none;padding:0}.bubble-ai blockquote{border-left:3px solid rgba(0,82,160,.3);margin:.5rem 0;padding:.25rem 0 .25rem .75rem;color:#3A3A4A}.bubble-ai h1,.bubble-ai h2,.bubble-ai h3,.bubble-ai h4{font-weight:600;margin:.5rem 0 .25rem}.bubble-ai h1{font-size:1.05rem}.bubble-ai h2{font-size:1rem}.bubble-ai h3{font-size:.95rem}.bubble-ai h4{font-size:.9rem}.bubble-ai a{color:#0052A0;text-decoration:underline}.ai-col{display:flex;flex-direction:column;gap:.25rem}.ai-label{font-size:.6rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#0052A0}.avatar{width:26px;height:26px;border-radius:50%;flex-shrink:0;background:#fff;border:1px solid rgba(0,82,160,.15);display:flex;align-items:center;justify-content:center;font-size:.7rem;color:#0052A0;font-weight:600}\";
function _esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;'); }
function _buildPreviewSrcdoc(question, answerHtml) {
return '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>' + SHARE_CARD_CSS + '</style></head><body><div class=\"chat-frame\"><div class=\"chat-row-user\"><div class=\"bubble-user\">' + _esc(question || '(no prior question)') + '</div></div><div class=\"chat-row-ai\"><div class=\"avatar\">N</div><div class=\"ai-col\"><span class=\"ai-label\">Neuron</span><div class=\"bubble-ai\">' + (answerHtml || '') + '</div></div></div></div></body></html>';
}
var _sharePending = null;
function openSharePreview(question, answerHtml, answerPlain, originBtn) {
_sharePending = { question: question, answerHtml: answerHtml, answerPlain: answerPlain, btn: originBtn };
var modal = document.getElementById('neuron-share-preview-modal');
var frame = document.getElementById('neuron-share-preview-frame');
if (!modal || !frame) return;
frame.srcdoc = _buildPreviewSrcdoc(question, answerHtml);
modal.style.display = 'flex';
}
function closeSharePreview() {
var modal = document.getElementById('neuron-share-preview-modal');
if (modal) modal.style.display = 'none';
var frame = document.getElementById('neuron-share-preview-frame');
if (frame) frame.srcdoc = '';
_sharePending = null;
}
async function publishSharePreview() {
if (!_sharePending) return;
var pending = _sharePending;
var publishBtn = document.getElementById('neuron-share-preview-publish');
if (publishBtn) { publishBtn.disabled = true; publishBtn.textContent = 'Publishing...'; }
if (pending.btn) pending.btn.style.opacity = '0.4';
try {
var r = await fetch('/api/share', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
question: pending.question,
answer: pending.answerPlain,
answer_html: pending.answerHtml,
answer_plaintext: pending.answerPlain
})
});
var d = await r.json();
if (d && d.id) {
window.open('/share/' + d.id, '_blank');
}
} catch(e) {}
if (pending.btn) pending.btn.style.opacity = '1';
if (publishBtn) { publishBtn.disabled = false; publishBtn.textContent = 'Publish to gallery'; }
closeSharePreview();
}
// Wire modal buttons once DOM is ready. The modal lives outside the chat
// panel so it works whether the panel is open or closed.
function _wireShareModal() {
var pubBtn = document.getElementById('neuron-share-preview-publish');
var cnlBtn = document.getElementById('neuron-share-preview-cancel');
var clsBtn = document.getElementById('neuron-share-preview-close');
var modal = document.getElementById('neuron-share-preview-modal');
if (pubBtn) pubBtn.addEventListener('click', publishSharePreview);
if (cnlBtn) cnlBtn.addEventListener('click', closeSharePreview);
if (clsBtn) clsBtn.addEventListener('click', closeSharePreview);
if (modal) modal.addEventListener('click', function(ev) { if (ev.target === modal) closeSharePreview(); });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', _wireShareModal);
else _wireShareModal();
// Persistent session storage - survives page refreshes
function loadSession() {
try {
var s = localStorage.getItem('neuron_demo_session');
return s ? JSON.parse(s) : { messages: [], count: 0, context: '' };
} catch(e) { return { messages: [], count: 0, context: '' }; }
}
function saveSession(session) {
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
}
function clearSession() {
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
}
function _mg(s) { return s._m || { nodes: [], edges: [] }; }
function _um(s, nn, ne) {
if (!nn || !nn.length) return;
var g = _mg(s), nm = {}, ek = function(e) { return e.from+'->'+e.to; }, em = {};
g.nodes.forEach(function(n) { nm[n.id] = n; });
(nn || []).forEach(function(n) {
if (nm[n.id]) { nm[n.id].w = Math.min(1.0, (nm[n.id].w || 0.5) + 0.08); }
else { nm[n.id] = n; }
});
g.nodes = Object.values(nm);
g.edges.forEach(function(e) { em[ek(e)] = e; });
(ne || []).forEach(function(e) {
var k = ek(e);
if (em[k]) { em[k].weight = Math.min(1.0, (em[k].weight || 0.5) + 0.05); }
else { em[k] = e; }
});
g.edges = Object.values(em);
s._m = g; saveSession(s);
}
function _ra(g, q) {
if (!g || !g.nodes || !g.nodes.length) return [];
var words = q.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 3; });
var sc = {};
g.nodes.forEach(function(n) {
var t = (n.content || '').toLowerCase();
sc[n.id] = words.filter(function(w) { return t.indexOf(w) !== -1; }).length * 0.6 + (n.w || 0.5) * 0.4;
});
(g.edges || []).forEach(function(e) {
if (sc[e.from] > 0.1) sc[e.to] = (sc[e.to] || 0) + sc[e.from] * (e.weight || 0.5) * 0.4;
});
return g.nodes.filter(function(n) { return sc[n.id] > 0.2; })
.sort(function(a,b) { return sc[b.id]-sc[a.id]; }).slice(0,5)
.map(function(n) { return { id: n.id, content: n.content, score: sc[n.id] }; });
}
// ?reset=1 clears the session and reloads clean
if (window.location.search.indexOf('reset=1') !== -1) {
clearSession();
var clean = window.location.pathname;
window.history.replaceState({}, '', clean);
}
var session = loadSession();
// Ensure every user has a stable unique session ID.
if (!session.uid) {
session.uid = 'u' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
saveSession(session);
}
var msgCount = session.count || 0;
function updateCountdown() {
var el = document.getElementById('neuron-demo-countdown');
if (!el) return;
var remaining = MAX - msgCount;
el.textContent = remaining + ' question' + (remaining === 1 ? '' : 's') + ' left';
el.style.color = '#ffffff';
el.style.fontWeight = '700';
}
window.neuronDemoReset = function() {
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
session = { messages: [], count: 0, context: '' };
msgCount = 0;
var msgs = document.getElementById('neuron-demo-messages');
if (msgs) msgs.innerHTML = '';
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = false; input.placeholder = 'Ask me anything...'; }
var btn = document.getElementById('neuron-demo-send');
if (btn) btn.disabled = false;
addMsg('ai', 'Hey. What is on your mind?', true);
};
window.neuronDemoToggle = function() {
isOpen = !isOpen;
var panel = document.getElementById('neuron-demo-panel');
if (panel) panel.style.display = isOpen ? 'flex' : 'none';
var btn = document.getElementById('neuron-demo-btn');
if (btn) btn.style.display = isOpen ? 'none' : '';
var msgs = document.getElementById('neuron-demo-messages');
if (isOpen && turnstileVerified && msgs && msgs.style.display !== 'none' && msgs.children.length === 0) {
if (session.messages && session.messages.length > 0) {
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
var remaining = MAX - msgCount;
if (remaining <= 0) {
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
}
} else if (!session.greeted) {
addMsg('ai', 'Hey. What is on your mind?', true);
session.greeted = true;
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
}
}
var input = document.getElementById('neuron-demo-text');
if (isOpen && input && !input.disabled) input.focus();
updateCountdown();
if (isOpen && !turnstileWidgetId && typeof turnstile !== 'undefined') {
var container = document.getElementById('neuron-demo-turnstile');
if (container) {
turnstileWidgetId = turnstile.render(container, {
sitekey: TURNSTILE_SITE_KEY,
size: 'compact',
callback: function(token) {
turnstileToken = token;
turnstileVerified = true;
if (typeof turnstile !== 'undefined' && turnstileWidgetId !== null) {
try { turnstile.remove(turnstileWidgetId); } catch(e) {}
turnstileWidgetId = null;
}
var gate = document.getElementById('neuron-demo-gate');
var msgs = document.getElementById('neuron-demo-messages');
var inputRow = document.getElementById('neuron-demo-input-row');
if (gate) gate.style.display = 'none';
if (msgs) msgs.style.display = 'flex';
if (inputRow) inputRow.style.display = 'flex';
if (session.messages && session.messages.length > 0) {
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
var remaining = MAX - msgCount;
if (remaining <= 0) {
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
}
} else if (!session.greeted) {
addMsg('ai', 'Hey. What is on your mind?', true);
session.greeted = true;
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
}
updateCountdown();
var inp = document.getElementById('neuron-demo-text');
if (inp) inp.focus();
},
'expired-callback': function() {
turnstileToken = '';
turnstileVerified = false;
}
});
}
}
};
function addMsg(role, text, skipSave) {
var msgs = document.getElementById('neuron-demo-messages');
if (!msgs) return null;
var el = document.createElement('div');
el.className = 'demo-msg demo-msg-' + role;
var avatar = document.createElement('div');
avatar.className = 'demo-msg-avatar';
if (role === 'ai') {
var img = document.createElement('img');
img.src = '/assets/brand/neuron-brain.png';
img.alt = 'Neuron';
avatar.appendChild(img);
} else {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('width', '14'); svg.setAttribute('height', '14');
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2');
var p1 = document.createElementNS(svgNS, 'path');
p1.setAttribute('d', 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2');
var c1 = document.createElementNS(svgNS, 'circle');
c1.setAttribute('cx', '12'); c1.setAttribute('cy', '7'); c1.setAttribute('r', '4');
svg.appendChild(p1); svg.appendChild(c1);
avatar.appendChild(svg);
}
var bubble = document.createElement('div');
bubble.className = 'demo-msg-bubble';
if (role === 'ai' && typeof marked !== 'undefined') {
try { bubble.innerHTML = marked.parse(text); } catch(e) { bubble.textContent = text; }
} else {
bubble.textContent = text;
}
if (role === 'ai') {
var bodyWrap = document.createElement('div');
bodyWrap.className = 'demo-msg-ai-body';
bodyWrap.appendChild(bubble);
if (!skipSave) {
var shareBtn = document.createElement('button');
shareBtn.className = 'demo-share-pill';
shareBtn.title = 'Share this response';
shareBtn.textContent = 'Share ↗';
// Capture rendered HTML on click; preview before publish.
shareBtn.onclick = function() {
var prevUser = '';
if (session.messages) {
for (var i = session.messages.length - 1; i >= 0; i--) {
if (session.messages[i].role === 'user') { prevUser = session.messages[i].text; break; }
}
}
var answerHtml = bubble.innerHTML;
var answerPlain = text;
openSharePreview(prevUser, answerHtml, answerPlain, shareBtn);
};
bodyWrap.appendChild(shareBtn);
}
el.appendChild(avatar);
el.appendChild(bodyWrap);
} else {
el.appendChild(avatar);
el.appendChild(bubble);
}
msgs.appendChild(el);
msgs.scrollTop = msgs.scrollHeight;
if (!skipSave && role !== 'thinking') {
session.messages = session.messages || [];
session.messages.push({ role: role, text: text });
if (session.messages.length > 200) session.messages = session.messages.slice(-200);
saveSession(session);
}
return el;
}
window.neuronDemoSend = async function() {
if (msgCount >= MAX) return;
var input = document.getElementById('neuron-demo-text');
var btn = document.getElementById('neuron-demo-send');
if (!input || btn.disabled) return;
var msg = input.value.trim();
if (!msg) return;
input.value = '';
btn.disabled = true;
addMsg('user', msg);
var thinking = document.createElement('div');
thinking.className = 'demo-msg demo-msg-thinking';
var thAvatar = document.createElement('div');
thAvatar.className = 'demo-msg-avatar';
var thImg = document.createElement('img');
thImg.src = '/assets/brand/neuron-brain.png';
thImg.alt = 'Neuron';
thAvatar.appendChild(thImg);
thinking.appendChild(thAvatar);
var thDots = document.createElement('span');
thDots.className = 'demo-msg-thinking-dots';
thDots.innerHTML = '<span></span><span></span><span></span>';
thinking.appendChild(thDots);
var thMsgsEl = document.getElementById('neuron-demo-messages');
if (thMsgsEl) {
thMsgsEl.appendChild(thinking);
thMsgsEl.scrollTop = thMsgsEl.scrollHeight;
}
if (turnstileVerified && !session._cfSent) { session._cfSent = true; }
try {
var hist = (session.messages || []).slice(-50).filter(function(m){ return m.role !== 'thinking'; }).map(function(m){
return {role: m.role === 'ai' ? 'assistant' : 'user', content: m.text};
});
var activated_nodes = _ra(session._m, msg);
var questionsRemaining = (MAX - msgCount) - 1;
if (questionsRemaining < 0) questionsRemaining = 0;
// 30s frontend timeout — surfaces a real error if the soul hangs
// instead of leaving the thinking bubble spinning forever.
var ctrl = new AbortController();
var timeoutId = setTimeout(function() { ctrl.abort(); }, 30000);
var r;
try {
r = await fetch('/api/demo', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
signal: ctrl.signal,
body: JSON.stringify({
message: msg,
history: hist,
cf_token: turnstileVerified && !session._cfSent ? turnstileToken : '',
uid: session.uid || '',
activated_nodes: activated_nodes,
engram_node_count: (session._m && session._m.nodes) ? session._m.nodes.length : 0,
questions_remaining: questionsRemaining,
is_last_question: questionsRemaining === 0
})
});
} finally {
clearTimeout(timeoutId);
}
var d = await r.json();
if (thinking) thinking.remove();
_um(session, d.sn, d.se);
var reply = d.response || d.reply || d.message || '';
var isError = !reply || reply === 'Stepped out for a moment. Try again.';
if (!isError) {
msgCount++;
session.count = msgCount;
saveSession(session);
updateCountdown();
if (msgCount >= MAX && input) {
input.disabled = true;
input.placeholder = 'Interaction limit reached';
}
}
addMsg('ai', reply || 'Stepped out for a moment. Try again.');
} catch(e) {
if (thinking) thinking.remove();
var msg = (e && e.name === 'AbortError')
? 'Took too long to respond — try again.'
: 'Stepped out for a moment. Try again.';
addMsg('ai', msg);
}
if (msgCount < MAX && btn) btn.disabled = false;
if (input) input.focus();
};
var inp = document.getElementById('neuron-demo-text');
if (inp) {
inp.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); window.neuronDemoSend(); }
});
}
})();
</script>
"""
# Replace the line `<script src=\"/assets/js/fc247ef45b1d.js\" defer></script>`
# with our new inline content + script. extract-js will then re-extract this
# fresh inline block to a content-hashed asset on the next build.
OLD_LINE = '<script src=\\"/assets/js/de72b8b61d75.js\\" defer></script>'
def main():
src = STYLES_EL.read_text(encoding="utf-8")
# If the anchor `<script src=...de72b8b61d75.js...>` is still present, the
# asset extraction took the inline source out — so the chat JS in this
# file is the source-of-truth and we re-inject it. We fall through the
# MARKER check in that case, replacing both the modal HTML and the
# script-tag in one shot.
has_anchor = OLD_LINE in src
if MARKER in src and not has_anchor:
# Modal is inline AND asset has been re-extracted to a fresh hash.
# Re-running here would clobber the new hash. Bail.
print("styles.el already inlined and re-extracted - skipping")
return
if MARKER in src and has_anchor:
# Modal HTML is there but the chat JS is still in the obfuscated
# asset. Replace the entire modal+script block so extract-js picks
# up the fresh source on the next build.
import re
# Match from the modal comment opener to (and including) the OLD_LINE.
pattern = re.compile(
r"<!--\s*Share preview modal:.*?" + re.escape(OLD_LINE),
re.DOTALL,
)
if not pattern.search(src):
print("ERROR: modal block + anchor pair not found in styles.el")
raise SystemExit(1)
# Use a lambda so re.sub doesn't parse the replacement as a template
# (the chat HTML/JS contains `\s`, `\d`, etc. which would be treated
# as backreferences in a normal replacement string).
replacement = CHAT_HTML_AND_JS.strip()
new_src = pattern.sub(lambda _m: replacement, src, count=1)
STYLES_EL.write_text(new_src, encoding="utf-8")
print("styles.el patched: replaced existing modal+anchor with fresh chat-widget JS")
return
if not has_anchor:
# No marker, no anchor — file is in an unexpected state. Fail loud.
print(f"ERROR: anchor `{OLD_LINE}` not found in styles.el")
print(" Either it was renamed in a prior commit, or the file is already patched.")
raise SystemExit(1)
new_src = src.replace(OLD_LINE, CHAT_HTML_AND_JS.strip(), 1)
STYLES_EL.write_text(new_src, encoding="utf-8")
print("styles.el patched: chat-widget JS re-inlined with share preview modal")
if __name__ == "__main__":
main()