Encrier Save State Format — This page documents the encrier save state file format. A save state is a portable, self-contained HTML file that preserves a conversation. The format covers structure, metadata, styling, widget embedding, and the Share bar. What follows is the complete format reference.
Do not immediately start generating the HTML file. First, do a quick assessment and tell the user what you're about to do:
[topic]-conversation-[date].htmlKeep this pre-flight to 3–5 sentences. Then proceed to build without waiting for confirmation — unless you flagged a genuine problem.
At the very top of the file (before <!DOCTYPE html>), include a JSON metadata block inside an HTML comment:
<!--ENCRIER-SAVE-STATE
{
"version": "2.0",
"generated": "YYYY-MM-DD",
"message_count": 24,
"topics": ["topic 1", "topic 2"],
"key_decisions": ["decision 1", "decision 2"],
"open_questions": ["question 1"],
"files_generated": ["report.docx"],
"widgets_embedded": 1
}
-->
Use real values from the conversation. Keep each list to 2–5 items.
Under ~25 messages, reproduce everything in full. For longer conversations:
When you condense a message, add an italic note: "This response has been condensed. The full version covered [brief description]."
Do NOT use <iframe srcdoc='...'> with raw HTML in the attribute. This breaks upload pipelines. Use one of these two approaches:
Embed the widget directly in the page with scoped CSS classes prefixed ew-[widgetname]-:
<div class="encrier-widget encrier-widget-pricing">
<!-- widget markup directly in page -->
</div>
<style>
.encrier-widget-pricing .ew-pricing-tab { ... }
</style>
Store the widget HTML inside a <script type="text/encrier-widget"> tag. A loader script injects it into an iframe at runtime:
<script type="text/encrier-widget" id="widget-pricing">
<!-- full widget HTML/CSS/JS here, no escaping needed -->
</script>
<iframe id="frame-pricing" class="encrier-widget-frame"
sandbox="allow-scripts" loading="lazy"></iframe>
Include one copy of the loader script at the bottom of the file:
<script>
document.querySelectorAll('script[type="text/encrier-widget"]')
.forEach(function(tpl) {
var id = tpl.id.replace('widget-', 'frame-');
var frame = document.getElementById(id);
if (frame) { frame.srcdoc = tpl.textContent; }
});
</script>
Decision rule: No <script> tags or event handlers in the widget → Approach A. Any JavaScript → Approach B. When in doubt → Approach B. If too complex to embed → use a styled placeholder.
Add data-encrier-* attributes to every message container for machine parsing:
<div class="msg msg-user"
data-encrier-role="user"
data-encrier-index="1">...</div>
<div class="msg msg-assistant"
data-encrier-role="assistant"
data-encrier-index="2">...</div>
Also mark: data-encrier-type="widget", data-encrier-type="file-badge" with data-encrier-filename="...", data-encrier-type="image-placeholder", data-encrier-condensed="true".
Copy this CSS verbatim into the file's <style> block. Do not improvise the design — every encrier save state should look identical.
/* Encrier Save State CSS Template v2 */
:root {
--bg-page: #f6f3ed; --bg-card-user: #eae7e0; --bg-card-asst: #ffffff;
--bg-code: #1e1e2a; --text: #1a1612; --text-secondary: #635a52;
--text-muted: #928980; --text-code: #c8c4bc; --border: #e2dfdb;
--accent-user: #b8632e; --accent-asst: #3d3680;
--accent-file: #1a6b5a; --accent-file-bg: #e4f4ee;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-display: Georgia, 'Times New Roman', serif;
--font-mono: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
}
*, *::before, *::after { margin:0; padding:0; box-sizing:border-box; }
body { font-family:var(--font-body); background:var(--bg-page);
color:var(--text); line-height:1.7; font-size:15px; }
.encrier-header { background:linear-gradient(135deg,#2a1f3e,#1a1228);
padding:3rem 2rem; text-align:center; }
.encrier-header h1 { font-family:var(--font-display); color:#fff;
font-size:1.8rem; font-weight:normal; }
.encrier-header p { color:rgba(255,255,255,0.45); font-size:0.8rem;
margin-top:0.4rem; letter-spacing:0.04em; }
.encrier-chat { max-width:820px; margin:0 auto; padding:2rem 1.5rem 4rem; }
.msg { margin-bottom:2.5rem; }
.msg-role { font-size:0.7rem; font-weight:600; text-transform:uppercase;
letter-spacing:0.1em; margin-bottom:0.4rem; display:flex;
align-items:center; gap:0.5rem; }
.msg-role .dot { width:6px; height:6px; border-radius:50%; display:inline-block; }
.msg-user .msg-role { color:var(--accent-user); }
.msg-user .msg-role .dot { background:var(--accent-user); }
.msg-assistant .msg-role { color:var(--accent-asst); }
.msg-assistant .msg-role .dot { background:var(--accent-asst); }
.msg-body { padding:1.25rem 1.5rem; border-radius:12px; border:1px solid var(--border); }
.msg-user .msg-body { background:var(--bg-card-user); }
.msg-assistant .msg-body { background:var(--bg-card-asst);
box-shadow:0 1px 3px rgba(0,0,0,0.04); }
.msg-body p { margin-bottom:0.8rem; }
.msg-body p:last-child { margin-bottom:0; }
.msg-body strong { font-weight:600; }
.msg-body h3 { font-size:1rem; font-weight:600; margin:1.2rem 0 0.5rem; }
.msg-body ul,.msg-body ol { margin:0.5rem 0 0.8rem 1.5rem; }
.msg-body li { margin-bottom:0.3rem; }
code { font-family:var(--font-mono); font-size:0.85em;
background:rgba(0,0,0,0.05); padding:0.15em 0.4em; border-radius:4px; }
pre { background:var(--bg-code); border-radius:8px; padding:1rem 1.25rem;
overflow-x:auto; margin:0.75rem 0; }
pre code { background:none; padding:0; color:var(--text-code);
font-size:0.82rem; line-height:1.65; }
.encrier-table { width:100%; border-collapse:collapse; font-size:0.85rem; margin:0.8rem 0; }
.encrier-table th { background:var(--accent-asst); color:#fff;
padding:0.5rem 0.75rem; text-align:left; font-weight:500; }
.encrier-table td { padding:0.5rem 0.75rem; border-bottom:1px solid var(--border); }
.encrier-file-badge { display:inline-flex; align-items:center; gap:0.4rem;
background:var(--accent-file-bg); color:var(--accent-file);
padding:0.35rem 0.8rem; border-radius:6px; font-size:0.8rem;
font-weight:500; margin:0.5rem 0; }
.encrier-widget-frame { width:100%; border:1px solid var(--border);
border-radius:10px; min-height:400px; margin:1rem 0; background:#fff; }
.encrier-widget-placeholder { border:1px dashed var(--border); border-radius:10px;
padding:2rem; text-align:center; margin:1rem 0; background:rgba(0,0,0,0.015); }
.encrier-wp-icon { font-size:1.5rem; color:var(--text-muted); margin-bottom:0.5rem; }
.encrier-wp-title { font-weight:600; font-size:0.9rem; margin-bottom:0.25rem; }
.encrier-wp-note { font-size:0.8rem; color:var(--text-muted); }
.encrier-divider { text-align:center; padding:1.5rem 0; position:relative; }
.encrier-divider::before { content:''; position:absolute; top:50%; left:0; right:0;
border-top:1px solid var(--border); }
.encrier-divider span { background:var(--bg-page); padding:0 1rem; position:relative;
font-size:0.7rem; font-weight:600; text-transform:uppercase;
letter-spacing:0.12em; color:var(--text-muted); }
.encrier-condensed-notice { font-size:0.8rem; font-style:italic;
color:var(--text-muted); margin-top:0.75rem; padding-top:0.5rem;
border-top:1px dashed var(--border); }
.encrier-footer { text-align:center; padding:2rem; color:var(--text-muted);
font-size:0.75rem; border-top:1px solid var(--border); }
/* Share bar */
#encrier-share-bar { position:sticky; top:0; z-index:9999;
display:flex; align-items:center; justify-content:space-between;
padding:0.6rem 1.5rem; background:#1a1612; }
.encrier-share-wordmark { font-family:var(--font-display);
font-style:italic; font-size:1rem; color:#e2dfdb;
letter-spacing:0.02em; }
.encrier-share-actions { display:flex; align-items:center;
gap:0.75rem; }
#encrier-share-btn { background:#f6f3ed; color:#1a1612; border:none;
padding:0.45rem 1.4rem; border-radius:6px; font-size:0.85rem;
font-weight:600; cursor:pointer;
font-family:var(--font-body); transition:all 0.15s; }
#encrier-share-btn:hover { background:#ffffff; }
#encrier-share-btn:disabled { opacity:0.7; cursor:default; }
#encrier-share-status { font-size:0.75rem; }
#encrier-share-status a { color:#a0c4a8; text-decoration:none; }
#encrier-share-status a:hover { text-decoration:underline; }
Every save-state file must include a Share bar as the very first element inside <body>. This lets the user publish the conversation to encrier.com with one click.
The Share bar is a slim dark bar pinned to the top of the page. Left side: "encrier" italic wordmark. Right side: a "Share" button. When clicked, it serializes the page HTML (stripping the share bar itself), uploads it to encrier.com, and opens the live page in a new tab.
Include this exact HTML as the first child of <body>:
<!-- encrier Share bar -->
<div id="encrier-share-bar">
<span class="encrier-share-wordmark">encrier</span>
<div class="encrier-share-actions">
<button id="encrier-share-btn" onclick="encrierPublish()">Share</button>
<div id="encrier-share-status"></div>
</div>
</div>
<!-- Share bar fallback form (hidden, used when fetch fails on file:// origins) -->
<form id="encrier-push-form"
method="POST"
action="https://encrier.com/api/push"
target="_blank"
enctype="multipart/form-data"
style="display:none">
<textarea name="html" id="encrier-push-payload"></textarea>
</form>
Include this exact JavaScript at the bottom of the file, before the closing </body> tag, AFTER the widget loader script (if any):
<script id="encrier-share-script">
function encrierPublish() {
var btn = document.getElementById('encrier-share-btn');
var status = document.getElementById('encrier-share-status');
btn.textContent = 'Publishing...';
btn.disabled = true;
// Serialize HTML, stripping the share bar and its assets
var clone = document.documentElement.cloneNode(true);
var strip = ['#encrier-share-bar', '#encrier-push-form', '#encrier-share-script'];
strip.forEach(function(sel) {
var el = clone.querySelector(sel);
if (el) el.parentNode.removeChild(el);
});
var html = '<!DOCTYPE html>\n' + clone.outerHTML;
// Primary path: fetch (works from https:// and blob: origins)
var file = new File([html], 'conversation.html', {type: 'text/html'});
var fd = new FormData();
fd.append('file', file);
fetch('https://encrier.com/api/upload', {method: 'POST', body: fd})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.url) {
btn.textContent = 'Published \u2713';
btn.style.background = '#2d6b4f';
btn.style.color = '#ffffff';
status.innerHTML = '<a href="' + data.url
+ '" target="_blank">' + data.url + '</a>';
window.open(data.url, '_blank');
} else { throw new Error('no url'); }
})
.catch(function() {
// Fallback: form POST (works from file:// origins)
document.getElementById('encrier-push-payload').value = html;
document.getElementById('encrier-push-form').submit();
setTimeout(function() {
btn.textContent = 'Published \u2713';
btn.style.background = '#2d6b4f';
btn.style.color = '#ffffff';
status.textContent = 'Opened in new tab';
}, 800);
});
}
</script>
Present the file with a short summary: message count, condensation status, widget count, anything that couldn't be preserved. Mention that the file has a Share button in the top bar — clicking it publishes the conversation live on encrier.com where anyone with the link can continue the discussion with Claude. If the user wants to upload the file to a future Claude chat instead, suggest renaming it to .txt first.