Save state format reference · about this project
An encrier save state is a self-contained HTML file that preserves a conversation. The file works offline, requires no external dependencies, and can be uploaded back into a future conversation to restore full context. This page documents the format.
A conformant save state is a single .html file containing all CSS in an inline style block. It uses system fonts only — no Google Fonts or external stylesheets. It does not use localStorage, sessionStorage, or position:fixed. It is built with vanilla HTML, CSS, and JavaScript only, no frameworks.
A JSON metadata block appears at the very top of the file, before the DOCTYPE, 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
}
-->
<pre><code> blocks. When code was iterated, only the final version is included.Conversations under ~25 messages are reproduced in full. For longer conversations:
Condensed messages include an italic note: "This response has been condensed. The full version covered [brief description]."
The format does not use <iframe srcdoc='...'> with raw HTML in the attribute. Two approaches are used instead:
Approach A — Inline DOM (widgets with no JavaScript): The widget markup is placed directly in the page with scoped CSS classes prefixed ew-[widgetname]-.
Approach B — Template + loader (widgets with JavaScript): The widget HTML is stored inside a <script type="text/encrier-widget"> tag. A loader script injects it into a sandboxed iframe at runtime:
<script type="text/encrier-widget" id="widget-pricing">
<!-- full widget HTML/CSS/JS here -->
</script>
<iframe id="frame-pricing" class="encrier-widget-frame"
sandbox="allow-scripts" loading="lazy"></iframe>
<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>
The decision rule: no script tags or event handlers in the widget → Approach A. Any JavaScript → Approach B. If too complex → a styled placeholder.
Every message container carries data-encrier-* attributes 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>
Additional markers: data-encrier-type="widget", data-encrier-type="file-badge" with data-encrier-filename="...", data-encrier-type="image-placeholder", data-encrier-condensed="true".
All conformant save states use this exact CSS. It is copied verbatim into the file's style block:
/* 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 includes a Share bar as the first element inside the body tag. The bar allows one-click publishing to encrier.com, where the conversation becomes a live, interactive page.
The Share bar HTML:
<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>
<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>
The Share bar script, placed before the closing body tag:
<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;
var clone = document.documentElement.cloneNode(true);
['#encrier-share-bar','#encrier-push-form','#encrier-share-script']
.forEach(function(sel) {
var el = clone.querySelector(sel);
if (el) el.parentNode.removeChild(el);
});
var html = '<!DOCTYPE html>\n' + clone.outerHTML;
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 = '#fff';
status.innerHTML = '<a href="'+data.url+'" target="_blank">'+data.url+'</a>';
window.open(data.url, '_blank');
} else { throw new Error('no url'); }
})
.catch(function() {
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 = '#fff';
status.textContent = 'Opened in new tab';
}, 800);
});
}
</script>
Before generating a save state, a brief assessment is performed: message count, any widgets or uploaded files that need special handling, a proposed filename following the pattern [topic]-conversation-[date].html, and whether condensation is needed for conversations exceeding ~30 messages.