-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcore.html
More file actions
589 lines (571 loc) · 44.4 KB
/
Copy pathcore.html
File metadata and controls
589 lines (571 loc) · 44.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'none'; base-uri 'none'; form-action 'none'; object-src 'none'">
<meta name="color-scheme" content="light dark">
<meta name="description" content="On-site reading view of CAPSULE_CORE.md.">
<title>Core spec — twelve rules, one page · HTML Capsule</title>
<script id="capsule-manifest" type="application/json">
{
"spec_version": "0.3.0",
"uuid": "455d1f0e-cdd4-5844-ab64-9c5c1573dfc8",
"capsule_version": "1.0.0",
"title": "Core spec — twelve rules, one page",
"description": "On-site reading view of CAPSULE_CORE.md. Auto-generated by compiler/build_md_capsules.py from the source markdown (SHA-256 522cb4e84705af79…). The host serves raw .md as text/markdown, so this Capsule renders the same content as a sealed HTML artifact with a comfortable reading view, integrity hash, and standard export capabilities. Source markdown lives at the same path in the GitHub repo if you want raw.",
"type": "spec",
"profile": "static",
"created_at": "2026-07-07T16:35:07Z",
"generator": {
"name": "compiler/build_md_capsules.py",
"version": "1.0.0",
"kind": "compiler"
},
"source": {
"origin": "repository_markdown",
"snapshot_type": "markdown_render",
"snapshot_id": "snapshot:CAPSULE_CORE.md-2026-07-07",
"included_records": 1,
"spec_received": "v0.3.0 · 2026-05-19",
"source_path": "CAPSULE_CORE.md",
"source_sha256": "522cb4e84705af7956f9abdbd110501b1a0c65bd02debe4b92e42decd3ef7e88",
"source_bytes": 24811
},
"privacy": {
"visibility": "public",
"contains_private_data": false,
"redaction_applied": false,
"external_dependencies": false
},
"capabilities": [
"about",
"copy_as_json",
"download_json",
"download_capsule",
"print_to_pdf"
],
"synthesis": {
"kind": "deterministic_markdown_render",
"model": "compiler/build_md_capsules.py v1.0.0",
"human_reviewed": false
},
"integrity": {
"hash_scope": "data+manifest",
"algorithm": "sha256",
"content_hash": "sha256:61af21358bbb207c17bc9d9529dd0fdd40fbbb0a2195ecac87e52318d84d068c"
}
}
</script>
<script id="capsule-data" type="application/json">{"source_path":"CAPSULE_CORE.md","source_sha256":"522cb4e84705af7956f9abdbd110501b1a0c65bd02debe4b92e42decd3ef7e88","source_bytes":24811,"source_lines":266,"rendered_at":"2026-07-07T16:35:07Z","breadcrumb":"Spec / Core","lead_color":"indigo"}</script>
<style id="capsule-style">
:root {
color-scheme: light dark;
--paper: #f5f6f8;
--paper-soft: #ebedf1;
--paper-page: #ffffff;
--ink: #0c0e13;
--ink-soft: #2a2e38;
--ink-mute: #5d6470;
--ink-faint: #8a909c;
--rule: #dde0e6;
--rule-soft: #e8eaef;
--indigo: #4f46e5;
--indigo-soft: #eef0fe;
--indigo-deep: #3730a3;
--violet: #7c3aed;
--violet-soft: #f3edff;
--violet-deep: #5b21b6;
--teal: #0d9488;
--teal-soft: #e0f5f1;
--teal-deep: #115e59;
--amber: #b45309;
--amber-soft: #fbeed5;
--amber-deep: #7c2d12;
--rose: #be123c;
--rose-soft: #fce4ea;
--lead: var(--indigo);
--lead-soft: var(--indigo-soft);
--lead-deep: var(--indigo-deep);
--sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
--mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
--measure: 70ch;
}
@media (prefers-color-scheme: dark) {
:root {
--paper: #0c0e13;
--paper-soft: #1a1d24;
--paper-page: #11141a;
--ink: #edf1f7;
--ink-soft: #c5cad4;
--ink-mute: #8a909c;
--ink-faint: #5d6470;
--rule: #2b313d;
--rule-soft: #1f242d;
--indigo: #818cf8;
--indigo-soft: #1e1b4b;
--indigo-deep: #c7cbf6;
--violet: #a78bfa;
--violet-soft: #2e1065;
--violet-deep: #d8b4fe;
--teal: #2dd4bf;
--teal-soft: #134e4a;
--teal-deep: #5eead4;
--amber: #fbbf24;
--amber-soft: #451a03;
--amber-deep: #fde68a;
--rose: #fb7185;
--rose-soft: #4c0519;
}
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--paper); color: var(--ink); }
body {
font-family: var(--sans);
font-size: 17px;
line-height: 1.62;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 1rem; top: 1rem; background: var(--ink); color: var(--paper); padding: .5rem .75rem; border-radius: 6px; z-index: 99; }
main {
max-width: 880px;
margin: 0 auto;
padding: 0 clamp(20px, 4vw, 48px) 80px;
}
/* ─── Top nav ─── */
.topnav {
display: flex; align-items: center; gap: 12px;
padding: 18px 0 14px;
border-bottom: 1px solid var(--rule);
font-size: 14px;
flex-wrap: wrap;
}
.topnav .home {
display: inline-flex; align-items: center; gap: 8px;
color: var(--ink);
font-weight: 600;
text-decoration: none;
border-bottom: 0;
}
.topnav .home svg { color: var(--lead); }
.topnav .crumb {
color: var(--ink-mute);
font-family: var(--mono);
font-size: 12.5px;
}
.topnav-spacer { flex: 1 1 16px; }
.topnav-links { display: inline-flex; gap: 14px; }
.topnav-links a {
color: var(--ink-mute);
font-size: 13px;
text-decoration: none;
border-bottom: 1px solid transparent;
}
.topnav-links a:hover { color: var(--lead); border-bottom-color: var(--lead); }
/* ─── Markdown body ─── */
.md-body {
padding: 32px 0 16px;
max-width: var(--measure);
}
.md-body h1, .md-body h2, .md-body h3, .md-body h4, .md-body h5, .md-body h6 {
letter-spacing: -.012em;
color: var(--ink);
margin: 1.6em 0 .55em;
line-height: 1.18;
text-wrap: balance;
}
.md-body h1 { font-size: clamp(28px, 4vw, 38px); margin-top: 0; font-weight: 700; }
.md-body h1::before {
content: "";
display: block;
width: 32px; height: 3px;
background: var(--lead);
margin-bottom: 18px;
border-radius: 2px;
}
.md-body h2 { font-size: 23px; padding-top: 8px; border-top: 1px solid var(--rule-soft); font-weight: 600; }
.md-body h3 { font-size: 19px; font-weight: 600; }
.md-body h4 { font-size: 16px; font-weight: 600; color: var(--ink-soft); }
.md-body h5, .md-body h6 { font-size: 14px; font-weight: 600; color: var(--ink-mute); text-transform: uppercase; letter-spacing: .04em; }
.md-body p {
margin: 0 0 1.05em;
color: var(--ink-soft);
text-wrap: pretty;
}
.md-body strong { color: var(--ink); font-weight: 600; }
.md-body em { font-style: italic; color: var(--ink); }
.md-body a {
color: var(--lead);
text-decoration: none;
border-bottom: 1px solid color-mix(in oklab, var(--lead) 30%, transparent);
}
.md-body a:hover, .md-body a:focus-visible {
color: var(--lead-deep);
border-bottom-color: var(--lead-deep);
}
.md-body code {
font-family: var(--mono);
font-size: .9em;
background: var(--paper-soft);
padding: 1px 6px;
border-radius: 4px;
color: var(--lead-deep);
}
.md-body pre {
background: var(--paper-soft);
border: 1px solid var(--rule);
border-radius: 8px;
padding: 16px 18px;
margin: 1em 0 1.4em;
overflow-x: auto;
font-family: var(--mono);
font-size: 13px;
line-height: 1.55;
}
.md-body pre code {
background: transparent;
padding: 0;
color: var(--ink-soft);
font-size: 13px;
}
.md-body ul, .md-body ol { padding-left: 1.6em; margin: 0 0 1.05em; color: var(--ink-soft); }
.md-body ul li, .md-body ol li { margin-bottom: .3em; }
.md-body blockquote {
margin: 1em 0 1.4em;
padding: .55em 1.1em;
background: var(--lead-soft);
border-left: 3px solid var(--lead);
border-radius: 0 8px 8px 0;
color: var(--lead-deep);
}
.md-body blockquote p:last-child { margin-bottom: 0; }
.md-body hr {
border: 0;
border-top: 1px solid var(--rule);
margin: 2em 0;
}
.md-body img {
max-width: 100%; height: auto; border-radius: 8px;
}
.md-body table.md-table {
width: 100%;
border-collapse: collapse;
margin: 1em 0 1.4em;
font-size: 14.5px;
}
.md-body table.md-table th, .md-body table.md-table td {
padding: 8px 12px;
border-bottom: 1px solid var(--rule);
text-align: left;
vertical-align: top;
}
.md-body table.md-table th {
font-weight: 600;
color: var(--ink);
background: var(--paper-soft);
}
.md-body table.md-table tbody tr:hover { background: color-mix(in oklab, var(--lead) 4%, transparent); }
/* ─── About ─── */
.about {
margin-top: 48px;
background: var(--paper-page);
border: 1px solid var(--rule);
border-radius: 8px;
padding: 14px 18px;
}
.about > summary {
cursor: pointer;
font-family: var(--mono);
font-size: 13px;
letter-spacing: .04em;
color: var(--ink-mute);
list-style: none;
}
.about > summary::-webkit-details-marker { display: none; }
.about > summary::before { content: "+ "; color: var(--ink-mute); }
.about[open] > summary::before { content: "− "; }
.about-body { margin-top: 14px; }
.about-body p { font-size: 14px; color: var(--ink-soft); margin: 0 0 14px; }
.about-body .row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.about-body button {
appearance: none;
background: var(--paper-soft);
color: var(--ink);
border: 1px solid var(--rule);
border-radius: 6px;
padding: 6px 12px;
font: 500 12.5px/1 var(--sans);
cursor: pointer;
}
.about-body button:hover, .about-body button:focus-visible {
background: var(--lead); color: var(--paper-page); border-color: var(--lead); outline: none;
}
.about-body pre {
background: var(--paper-soft);
border: 1px solid var(--rule);
border-radius: 6px;
padding: 14px;
overflow: auto;
max-height: 360px;
font-family: var(--mono);
font-size: 12px;
color: var(--ink-soft);
margin: 0;
}
/* ─── Footer ─── */
footer.site {
margin-top: 24px;
padding-top: 18px;
border-top: 1px solid var(--rule);
text-align: center;
}
.footer-line { font-family: var(--mono); font-size: 12px; color: var(--ink-mute); margin: 0; }
.footer-line a { color: var(--lead); text-decoration: none; border-bottom: 1px solid color-mix(in oklab, var(--lead) 30%, transparent); }
@media print {
.topnav, .about, footer.site { display: none !important; }
.md-body { max-width: none; }
body { background: white; color: black; }
}
</style>
</head>
<body>
<a class="skip" href="#capsule-root">Skip to content</a>
<main id="capsule-root">
<nav class="topnav" aria-label="Top">
<a class="home" href="/">
<svg viewBox="0 0 32 18" width="22" height="14" aria-hidden="true" focusable="false">
<rect x="1" y="1" width="30" height="16" rx="8" ry="8" fill="none" stroke="currentColor" stroke-width="2"/>
<g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M 9.5 5.5 L 6 9 L 9.5 12.5"/>
<path d="M 13.5 13 L 18.5 5"/>
<path d="M 22.5 5.5 L 26 9 L 22.5 12.5"/>
</g>
</svg>
<span>HTML Capsule</span>
</a>
<span class="crumb">Spec / Core</span>
<span class="topnav-spacer"></span>
<span class="topnav-links">
<a href="/notes">Notes</a>
<a href="/exploration">Long-form</a>
<a href="https://github.com/bigfancygarden/htmlcapsule/blob/main/CAPSULE_CORE.md">Source</a>
</span>
</nav>
<article class="md-body">
<h1 id="capsule-core">Capsule — Core</h1>
<p><strong>Version: v0.3.0 · 2026-05-19</strong></p>
<p>The short spec. One page. Pasteable into an LLM prompt. The full spec is in <a href="spec/CAPSULE_SPEC.md"><code>spec/CAPSULE_SPEC.md</code></a>.</p>
<p>A <strong>Capsule</strong> is a versioned, self-contained HTML file for work, data, or information worth preserving. It is a <em>memory object</em> — human-readable, machine-readable, and provenance-bearing in one. It packages a bounded data snapshot, a machine-readable manifest, embedded UI logic, and structured export behavior into one shareable, archivable, re-loadable file. It's a profile of HTML — not a new file format. The same outer contract works for any domain (recipes, research notes, decisions, journal entries, LLM-extracted summaries, maps, logs); domain-specific content lives in the data block. Capsule keeps a fixed five-block envelope; optional <strong>profiles</strong> are validation overlays that describe how those blocks are used. The strongest claim the format makes is <strong>multi-producer interop</strong>: LLMs (Claude, ChatGPT, Gemini), deterministic compilers (Python/Node build scripts), and human authors all produce the same envelope shape.</p>
<blockquote><p><strong>Terminology note.</strong> Claude's "artifacts," ChatGPT's "Canvas," and similar features are <em>working canvases</em> — editable, iterable, the live output that sits next to a chat. Capsules are what those become when sealed for preservation, sharing, and archival. Different roles, complementary tools: think of capsules as the seal step that comes after the canvas step.</p></blockquote>
<blockquote><p><strong>Note for LLMs producing capsules:</strong> Use a <em>thinking</em> or <em>extended-reasoning</em> mode if your platform has one — Claude's Extended Thinking, ChatGPT's Thinking variants, Gemini's deep-think. In practice these produce noticeably more careful capsules: better personal-use defaults, light and dark themes, working markdown exports, CSP headers, richer data structures. Standard modes still work but produce rougher capsules; if you're in standard mode, reread the rules below carefully before producing.</p></blockquote>
<h2 id="the-twelve-rules">The twelve rules</h2>
<ol><li><strong>One file.</strong> The capsule is a single <code>.html</code> document. No companion folders, no sidecar files, no external assets.</li></ol>
<ol><li><strong>No network — the capsule boundary is definitional.</strong> Zero <code>fetch</code>, <code>XHR</code>, CDN references, ES-module imports, or external CSS. Everything inlined. The capsule must work identically opened via <code>file://</code> and over <code>http://</code>. <em>The seal is not a restriction on what capsules can do — it's what makes capsules possible.</em> An artifact that fetches live data, calls an external service, or depends on network availability at runtime is a different category of artifact (web app, live dashboard, connected document) — not a degraded capsule. Without the seal, there's no floor, no archive, no durability, no "open this in ten years and it still works." See <code>spec/CAPSULE_SPEC.md</code> §1.5 for the conceptual treatment.</li></ol>
<ol><li><strong>Five required blocks</strong>, with these exact IDs:</li></ol>
<ul><li><code><script id="capsule-manifest" type="application/json"></code> — the manifest</li><li><code><script id="capsule-data" type="application/json"></code> — the data snapshot</li><li><code><style id="capsule-style"></code> — all CSS</li><li><code><main id="capsule-root"></code> — the UI root</li><li><code><script id="capsule-runtime"></code> — all JavaScript</li></ul>
<ol><li><strong>Manifest has these required fields:</strong> <code>spec_version</code>, <code>uuid</code> (v4 — the canonical identifier for the capsule), <code>capsule_version</code> (semver), <code>title</code>, <code>description</code>, <code>type</code>, <code>created_at</code> (ISO 8601 UTC), <code>generator</code> (<code>name</code> + <code>version</code> + <code>kind</code>), <code>source</code> (<code>origin</code> + <code>snapshot_type</code> + <code>snapshot_id</code> + <code>included_records</code>), <code>privacy</code> (<code>visibility</code> + <code>contains_private_data</code> + <code>redaction_applied</code> + <code>external_dependencies: false</code>), <code>capabilities</code> (array including <code>"about"</code> plus at least one export). <strong>Optional fields:</strong> <code>profile</code> (<code>"static"</code>, <code>"interactive"</code>, or <code>"data"</code> — validation overlay only; the five-block envelope stays the same); <code>presentations</code> (declared capsule-owned views such as <code>reader</code>, <code>mobile</code>, <code>print-letter</code>, <code>slides</code>, <code>reel</code>, or <code>interactive</code>; entries are fragment selectors like <code>#capsule-root</code>; hosts must not infer undeclared capsule-owned views; if you declare presentations, the default is one required <code>reader</code> entry pointing at <code>#capsule-root</code>; do not declare <code>desktop</code>, because the reader view is the normal browser/desktop view; a presentation may declare <code>chrome: "capsule" | "host" | "none"</code> to prevent host/app controls from duplicating capsule-owned story, slide, play/pause, close, or replay chrome); <code>parents</code> (Capsule-to-Capsule provenance — see rule 13 below); <code>derived_from</code> (non-Capsule provenance — array of objects with required <code>type</code> + <code>title</code> and recommended <code>reference</code> URL/URN, <code>role</code>, plus optional <code>hash</code>, <code>date</code>; for compositions, datasets, chats, documents, photographs, surveys, and anything else that isn't a Capsule but informs the artifact. Added in spec v0.3.6; full shape in <code>spec/CAPSULE_SPEC.md</code> §11.2); <code>source.spec_received</code> (version string of this Core spec, e.g., <code>"v0.3.0 · 2026-05-19"</code>, taken from the version line at the top of this file — should match the <code>spec_version</code> you set above); and <code>source.prompt_received</code> (the verbatim prompt the user gave you). These let future readers correlate output with the Core version and prompt that produced it. <strong>Deprecated fields</strong> (still accepted in v0.3, planned for removal in v0.4): <code>capsule_id</code> (a human-readable slug — redundant with <code>title</code>, and not guaranteed unique); <code>artifact_id</code> and <code>artifact_version</code> (the v0.1 names superseded by <code>capsule_id</code>/<code>capsule_version</code>); <code>related</code> (was unused soft-association array — provenance now lives in <code>parents</code> and <code>derived_from</code>).</li></ol>
<ol><li><strong>Honest provenance.</strong> <code>generator.kind</code> is one of <code>"compiler"</code>, <code>"llm"</code>, <code>"human"</code>, <code>"hybrid"</code>. If an LLM produced the HTML, set <code>kind: "llm"</code> and add <code>version: "<model-id>"</code>. If an LLM synthesized the data content (extracted from an article, summarized a transcript, etc.), add an optional <code>synthesis</code> block: <code>{ kind, model, human_reviewed }</code>. Don't claim to be the reference compiler.</li></ol>
<ol><li><strong>Read-only data.</strong> The runtime must never modify the <code>capsule-data</code> block. User state (annotations, verdicts, selections, inputs) lives in JavaScript memory only and materializes only when the recipient triggers an export.</li></ol>
<ol><li><strong>Capabilities don't lie.</strong> Every capability declared in the manifest must have a working implementation. Required minimum: <code>"about"</code> (a panel showing the manifest, usually a <code><details></code> block) plus at least one export — one of <code>copy_as_json</code>, <code>copy_as_markdown</code>, <code>download_json</code>, <code>download_capsule</code>, <code>print_to_pdf</code>, or <code>export_response</code>. <strong>Core capabilities</strong> are controlled names (the list above plus <code>filter</code>, <code>sort</code>, <code>search</code>, <code>annotate</code>, <code>highlight</code>, <code>rank</code>, <code>group</code>, <code>compare</code>, <code>copy_as_csv</code>, <code>copy_as_prompt</code>, <code>download_csv</code>, and documented core families such as <code>media.<em></code>). <strong>Restricted capabilities</strong> such as <code>storage.local</code>, <code>native_bridge</code>, and <code>ai_context.export</code> are declarations, not permissions; hosts and readers deny them unless explicitly allowed. <strong>Prohibited capabilities</strong> such as <code>network.request</code> are invalid in Capsules because Rule 2 forbids network dependency. <strong>Extension capabilities</strong> use <code>x-</em></code> or reverse-DNS/dotted names such as <code>x-mining.map</code> or <code>org.example.domain_feature</code>. The dotted form signals "domain-specific consumers may understand this; generic consumers may ignore it."</li></ol>
<ol><li><strong>Response export structure.</strong> When the capsule supports <code>export_response</code>, the exported JSON must follow this shape: ``<code>json { "response_schema_version": "0.1.0", "capsule_reference": { "uuid": "...", "capsule_version": "...", "snapshot_id": "..." }, "response": { "type": "annotation | ranking | selection | decision | feedback | form_data | freeform | patch", "created_at": "<ISO 8601>", "payload": { ... } } } </code>``</li></ol>
<ol><li><strong>Accessibility baseline.</strong> Semantic HTML, keyboard navigable, ARIA labels on iconic controls, <code>prefers-reduced-motion</code> respected, skip-to-content link, <code><html lang="..."></code> set.</li></ol>
<ol><li><strong>Capsules cannot be unshared.</strong> Treat redaction and audience decisions as final before sharing. There's no mechanism to retract a capsule a recipient has already received.</li></ol>
<ol><li><strong>Runtime JS string-literal rule.</strong> In the <code>capsule-runtime</code> block, <strong>any string that contains or could contain a newline must use a backtick template literal</strong>, never a <code>"..."</code> or <code>'...'</code> regular string literal with a raw line break inside. A raw line terminator inside a regular string literal is a JavaScript <code>SyntaxError</code> and breaks the entire runtime silently — this is the single most common bug class in LLM-produced capsules. This is a mechanical rule with no exceptions: if a string could contain a newline (markdown exports, multi-line templates, joined arrays), use backticks.</li></ol>
<p>```js // WRONG — a real newline character inside "..." is a SyntaxError: const md = lines.join(" ");</p>
<p>// RIGHT — backtick template literal, immune to this bug: const md = lines.join(<code>\n</code>);</p>
<p>// ALSO RIGHT — the two-character escape sequence \n inside "...": const md = lines.join("\n"); ```</p>
<p>The distinction that matters: in the third example, <code>\n</code> is two characters (backslash + n) that the JS parser interprets as a newline. In the first example, the source file contains an actual newline byte inside the quotes, which is illegal. LLMs often get these confused when generating long outputs — using backticks everywhere eliminates the failure mode entirely.</p>
<ol><li><strong>Render content in the HTML, not at runtime.</strong> The <code><main id="capsule-root"></code> body must already contain the full readable artifact when the file is opened — title, summary, key sections, prose, embedded media (<code><img src="data:..."></code>, <code><audio src="data:..."></code>, <code><video src="data:..."></code>), tables, lists, metadata, or other human-readable fallback content appropriate to the artifact. Runtime JavaScript may <em>enhance</em> the capsule (wire up export buttons, dynamic UI, copy-to-clipboard, search, filter, visualization) but <strong>must not be required</strong> to produce the readable content. <code>capsule-data</code> may contain richer structured data; <code>capsule-runtime</code> may hydrate enhanced views from it. Runtime code must not be the only path by which a reader can understand the artifact's primary content. Presentation declarations follow the same rule: declare views in <code>manifest.presentations[]</code>; do not ask hosts to guess them from layout. Capsules are archives, not apps; they must remain readable in environments that don't execute inline scripts — iOS Files / QuickLook previews, email client previews, screen readers, search indexers, archive viewers, and future browsers whose JS support has drifted from today's APIs.</li></ol>
<p>```html WRONG — empty placeholders waiting for JS to render content:</p>
<main id="capsule-root">
<h2 id="title"></h2>
<figure id="photo-frame"></figure>
<p id="caption"></p>
<dl id="meta"></dl>
</main>
<script>
<p>// 200 lines of <code>el.textContent = data.title</code> etc.</p>
</script>
<p>RIGHT — content is already in the HTML; JS is optional polish:</p>
<main id="capsule-root">
<h2>The actual title</h2>
<figure>
<img src="data:image/jpeg;base64,..." alt="...">
</figure>
<p>The actual caption.</p>
<dl>
<dt>Date</dt><dd>~1993</dd>
<dt>Place</dt><dd>Campbell River, BC</dd>
</dl>
</main>
<script>
<p>// ~50 lines: just button click handlers</p>
</script>
<p>```</p>
<p>Why this matters: a capsule whose <code>capsule-root</code> is mostly empty <code><div id="..."></code> containers will render as a blank page in any environment that blocks or restricts JS. The format claims to be portable, archival, and durable across decades — that claim only holds if the rendered artifact lives in the HTML itself. Pre-render at build time; use JS only where JS is genuinely required (clipboard, downloads, print dialog, dynamic UI). If a capsule has no interactive features, it may have no runtime JS at all.</p>
<p>If you are an LLM asked to "make a Capsule according to this spec," produce the canonical readable layer first and declare at most the views you actually built. Use <code>reader</code> by default. Add <code>print-letter</code> only if you create <code>#capsule-print</code>, <code>slides</code> only if you create <code>#capsule-slides</code>, <code>mobile</code> only if you create a deliberately separate <code>#capsule-mobile</code>, and <code>interactive</code> only if you create a meaningful <code>#capsule-interactive</code>. Never declare <code>desktop</code>.</p>
<p>If the user asks for an <strong>adaptive presentation Capsule</strong>, prefer producing a valid reader-only Capsule first unless you can follow the exact <code>presentations[]</code> shape. Adaptive sets are best for compiler/hybrid or manually reviewed outputs. If you do create one directly, include multiple declared presentations in one file: required <code>reader</code> at <code>#capsule-root</code>, <code>mobile</code> at <code>#capsule-mobile</code>, <code>reel</code> at <code>#capsule-reel</code> with <code>navigation: "sequence"</code>, and <code>slides</code> at <code>#capsule-slides</code> with <code>navigation: "paged"</code>. All views must derive from the same <code>capsule-data</code> / reader content and must not introduce independent facts. For LLM producer prompts, the better pattern is to include <code>presentation_model.cards[]</code> in <code>capsule-data</code> with stable card ids, roles such as <code>cover</code> / <code>story</code> / <code>end</code>, short title/body fields, and <code>source_sections[]</code> links back to canonical reader sections; a deterministic compiler can then generate polished story or slide surfaces.</p>
<h2 id="data-block-shape">Data block shape</h2>
<p>The <code>capsule-data</code> block is free-form JSON — whatever the domain needs. Two patterns recur:</p>
<ul><li><strong><code>records[]</code> array</strong> when the content is a set of discrete items (decision options, claims, photos, table rows, ranked choices).</li><li><strong>Single document with named sections</strong> when the content is a synthesis of one topic (a summary, briefing, research note, glossary, reference document). Top-level keys are themes appropriate to the topic — <code>summary</code>, <code>key_takeaways</code>, <code>decision_matrix</code>, <code>risk_register</code>, <code>inflammation_explainer</code>, <code>quick_recommendations</code>, whatever the content calls for. The <em>shape</em> is "top-level object with thematic sections"; the specific keys are free.</li></ul>
<p>Use whichever fits the actual content. LLMs producing synthesis capsules from conversations consistently reach for the single-document shape; that's the natural fit for "summarize this." LLMs producing decision-support or list-shaped artifacts reach for <code>records[]</code>. Both are first-class.</p>
<h2 id="minimum-manifest-example">Minimum manifest example</h2>
<pre><code class="language-json">{
"spec_version": "0.3.0",
"uuid": "00000000-0000-4000-8000-000000000000",
"capsule_version": "1.0.0",
"title": "Example Capsule",
"description": "A minimum valid capsule.",
"type": "reference",
"profile": "static",
"created_at": "2026-05-18T00:00:00Z",
"generator": { "name": "claude.ai", "version": "claude-opus-4-7", "kind": "llm" },
"source": {
"origin": "private_database",
"snapshot_type": "portable_excerpt",
"snapshot_id": "snapshot:example_001",
"included_records": 1
},
"privacy": {
"visibility": "shared",
"contains_private_data": false,
"redaction_applied": false,
"external_dependencies": false
},
"capabilities": ["about", "copy_as_json"]
}</code></pre>
<h2 id="provenance-when-this-capsule-was-forked-from-earlier-ones">Provenance: when this capsule was forked from earlier ones</h2>
<p>If the user started this conversation by pasting one or more existing capsules — "let's continue this," "compare these two," "build on this one" — record each one as a parent in the manifest's optional <code>parents</code> array. The recipient (months later, possibly a different person) needs to know what the conversation built on.</p>
<p>Each parent is a <code>{ uuid, title }</code> pair. The UUID is the load-bearing pointer (machine-actionable, globally unique); the title is denormalized at fork-time as a human-readability hint so readers don't have to dereference the UUID to know what the parent was.</p>
<pre><code class="language-json">"parents": [
{
"uuid": "a7c3e9f8-1234-4abc-9def-1234567890ab",
"title": "TN Visa Briefing"
}
]</code></pre>
<p>Multiple parents are supported and meaningful — a capsule that compares two earlier capsules, or that merges a second capsule into the conversation partway through, records all of them. The order is <em>introduction order</em>: the parent that seeded the conversation comes first; parents added later append. Don't record parents the user didn't actually paste in — <code>parents</code> is hard provenance, not "thematically related work."</p>
<p>If the conversation didn't start from a capsule, omit <code>parents</code> entirely (don't include an empty array — absent and empty are equivalent, and absent is cleaner).</p>
<h2 id="how-to-ask-an-llm-to-produce-a-capsule">How to ask an LLM to produce a capsule</h2>
<blockquote><p><strong>Use the canonical name when you write your prompt.</strong> The format is called <strong>Capsule</strong> (singular). The v0.1 name <em>"Artifact Capsule"</em> was renamed in v0.2 and only persists in this repo's naming-history notes; legacy field values (<code>artifact_id</code>, <code>artifact_version</code>) are still accepted under v0.2 compatibility, but new prompts and new content should use just "Capsule." Empirically — see <a href="RESEARCH.md">RESEARCH.md F25</a> — the legacy term persists in stored user prompt templates and leaks into the <code>prompt_received</code> field of newly-produced capsules. Update your template once and the leak goes away.</p></blockquote>
<p>A working prompt fragment:</p>
<blockquote><p>Produce a Capsule conforming to the rules below. The output should be a single <code>.html</code> file with no external dependencies, no network requests, and these five embedded blocks: <code>capsule-manifest</code>, <code>capsule-data</code>, <code>capsule-style</code>, <code>capsule-root</code>, <code>capsule-runtime</code>. Set <code>generator.kind</code> to <code>"llm"</code> and <code>generator.version</code> to your model ID. Include at minimum the <code>about</code> capability (a <code><details></code> panel showing the manifest) and <code>copy_as_json</code> (a button that copies the data block to the clipboard). Use semantic HTML, keyboard-accessible interactions, and <code>textContent</code> (never <code>innerHTML</code>) when rendering data values.</p>
<p>Pay particular attention to two rules and two encoding pitfalls that empirically trip LLM producers up:</p>
<ul><li><strong>Rule 12</strong> (render content in the HTML, not at runtime) — the <code>capsule-root</code> body must already contain the full readable artifact when the file is opened. Don't write empty <code><h2 id="title"></h2></code> placeholders and fill them in JS; write <code><h2>The actual title</h2></code> directly. JS is for <em>enhancement</em> (export buttons, dynamic UI), not for producing the basic rendered content.</li><li><strong>Rule 11</strong> (runtime JS string-literal rule) — for any multi-line string in your runtime JavaScript, use backtick template literals.</li><li><strong>Don't HTML-encode JSON inside <code><script></code> blocks.</strong> <code><script></code> and <code><style></code> are raw-text elements in HTML5: entities are NOT decoded inside them. If you write <code>&quot;</code> inside <code><script id="capsule-manifest" type="application/json"></code>, the browser reads it as six literal characters and <code>JSON.parse(textContent)</code> throws at runtime. Write raw JSON: <code>{"key": "value"}</code>, with real <code>"</code> characters. HTML entities belong inside <code><pre></code> blocks where you're displaying JSON for humans, not inside data blocks where the runtime parses them.</li><li><strong><code>snapshot_id</code> must start with the literal prefix <code>snapshot:</code>.</strong> This is a fixed namespace marker, not a content descriptor — same shape as <code>urn:uuid:</code>. Don't substitute <code>conversation:</code>, <code>chat:</code>, <code>record:</code>, or any other prefix even if it feels semantically truer; the slug <em>after</em> the colon is where you describe the content. Correct: <code>snapshot:vmsl-soccer-culture-2026-05-19</code>. Wrong: <code>conversation:vmsl-soccer-culture-2026-05-19</code>.</li></ul>
<h3 id="be-thorough-about-real-content">Be thorough about real content</h3>
<p>Capsules are preserved records, not chat replies. The recipient may open this file in five years. Do not truncate real content for brevity or for the sake of looking concise.</p>
<ul><li>If the conversation produced ten meaningful takeaways, include all ten — don't pick a "best five" to keep things short.</li><li>If the conversation referenced URLs, papers, documents, datasets, or official sources, capture them (see the next section for the recommended shape).</li><li>If the conversation had caveats, nuance, uncertainty, dead ends, or open questions, include those — they're often the most useful preservation later.</li><li>If the conversation involved an embedded image, screenshot, chart, or other media that's central to the meaning, embed it as a <code>data:</code> URI (the CSP already permits <code>img-src data:</code>).</li></ul>
<p>The format has a 20 MB hard cap (in the full spec, raised from 15 MB in v0.3.3, with a 15 MB soft warning for email-attachment compatibility) and typical capsules sit well under 1 MB. There is no penalty for thoroughness and a real cost to omission.</p>
<p><strong>The one limit:</strong> be thorough about content that <em>actually existed in the conversation</em>. Do not invent. The goal is faithful preservation, not embellishment.</p>
<h3 id="capture-sources-and-links">Capture sources and links</h3>
<p>When the conversation references external materials — URLs, papers, official documents, datasets, datasets — capture them in a structured <code>sources</code> array in the data block, not only inline in prose. A recommended shape (use whatever fields fit the content):</p>
<pre><code class="language-json">"sources": [
{
"label": "City of Vancouver — False Creek South leases on City land",
"url": "https://vancouver.ca/home-property-development/false-creek-south-leases-on-city-land.aspx",
"role": "primary_evidence",
"accessed_at": "2026-05-17",
"note": "Used for City ownership, leasehold structure, and LISL payment context."
}
]</code></pre>
<p>Roles that have been useful so far: <code>"primary_evidence"</code>, <code>"background"</code>, <code>"citation"</code>, <code>"policy_basis"</code>, <code>"data_source"</code>, <code>"counterargument"</code>. Inline prose mentions are still fine — but the structured array makes sources queryable across capsules and survives when the prose is later summarized further. Capture sources even when they came from your training data and not a tool call, as long as you can name them honestly.</p>
<h3 id="embed-a-qr-code-for-the-uuid-if-you-have-code-execution">Embed a QR code for the UUID (if you have code execution)</h3>
<p>If you have Python or another code-execution environment available, generate a QR code encoding <code>urn:uuid:<uuid></code> (the manifest's <code>uuid</code> in RFC 4122 URN form) and embed it as a <code>data:image/png;base64,...</code> URI inside the rendered capsule.</p>
<p><strong>Place it at the top-right of the page, visible above the fold.</strong> Not inside the about panel, not in the footer. The QR is the capsule's visible identity badge: scannable from a printed page, recognizable across capsules, anchored to the canonical UUID. Top-right placement works for both general capsules (where the badge sits next to or beside the title) and single-page printable layouts like briefings (where it lives in the header strip). Keep it small (around 80–96 px square on screen, ~1.5 cm in print).</p>
<p>Suggested layout pattern:</p>
<pre><code class="language-html"><header style="display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;">
<div>
<h1>Capsule Title</h1>
<p class="lede">...</p>
</div>
<figure class="capsule-qr" style="margin:0;display:flex;flex-direction:column;align-items:center;gap:0.2rem;font:10px ui-monospace,monospace;color:#6b7280;">
<img src="{data_uri}" alt="QR code for capsule UUID {uuid}" style="width:88px;height:88px;image-rendering:pixelated;display:block;">
<figcaption>{uuid_short}</figcaption>
</figure>
</header></code></pre>
<p>A minimal Python recipe with the <code>qrcode</code> library:</p>
<pre><code class="language-python">import qrcode, io, base64
qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=10, border=2)
qr.add_data(f"urn:uuid:{uuid}")
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buf = io.BytesIO(); img.save(buf, format="PNG")
data_uri = f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode('ascii')}"</code></pre>
<p>If you don't have code execution available, omit the QR. Tooling can add it at ingest time. Don't fake a QR by hand-drawing SVG or guessing the encoding — a wrong QR is worse than no QR.</p>
<p>Here are the rules:</p>
<p>[paste the twelve rules above]</p>
<p>The data I want to put in the capsule is:</p>
<p>[paste your data]</p></blockquote>
<h2 id="what-this-short-spec-does-not-cover">What this short spec does NOT cover</h2>
<p>For anything below, see <a href="spec/CAPSULE_SPEC.md"><code>spec/CAPSULE_SPEC.md</code></a>:</p>
<ul><li>Content hash protocol and integrity verification</li><li>Asset embedding rules and size tiers</li><li>Full response payload schemas (per response type)</li><li>Security rules (CSP, runtime sanitization, import validation)</li><li>Registry and import workflow</li><li>Versioning semantics across spec evolution</li><li>Related-work positioning (TiddlyWiki, RO-Crate, MHTML, etc.)</li><li>The full set of artifact types and capabilities</li></ul>
<p>The Core defines what makes a file recognizable as a capsule. The full spec defines what makes it trustworthy at protocol level. A capsule that follows the Core but skips the full spec's hashing/security details is still a capsule — just one with weaker verification properties.</p>
</article>
<details class="about" id="about">
<summary>About this page · manifest · exports</summary>
<div class="about-body">
<p>This is a sealed HTML Capsule per Core spec v0.3.0. Five required inline blocks, no network dependencies, integrity hash over data + manifest. The content above is rendered from <code>CAPSULE_CORE.md</code> by the deterministic <code>compiler/build_md_capsules.py</code> at the time of the last source change.</p>
<div class="row" role="group" aria-label="Exports">
<button type="button" data-capsule-action="copy_as_json">Copy data as JSON</button>
<button type="button" data-capsule-action="download_json">Download JSON</button>
<button type="button" data-capsule-action="download_capsule">Download capsule</button>
<button type="button" data-capsule-action="print_to_pdf">Print to PDF</button>
</div>
<pre id="manifest-view">Loading manifest…</pre>
</div>
</details>
<footer class="site">
<p class="footer-line">capsule:<code>455d1f0e</code> · sealed 2026-07-07 · source <code>CAPSULE_CORE.md</code> · <a href="/">htmlcapsule.org</a></p>
</footer>
</main>
<script id="capsule-runtime">
(function () {
'use strict';
var manifest = JSON.parse(document.getElementById('capsule-manifest').textContent);
var data = JSON.parse(document.getElementById('capsule-data').textContent);
var mEl = document.getElementById('manifest-view');
if (mEl) mEl.textContent = JSON.stringify(manifest, null, 2);
function safeName(s) { return (s || 'capsule').replace(/[^a-z0-9_\-]+/gi, '_'); }
function downloadBlob(filename, mime, content) {
var blob = (content instanceof Blob) ? content : new Blob([content], { type: mime });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url; a.download = filename;
document.body.appendChild(a); a.click(); document.body.removeChild(a);
setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
}
function copyText(text) {
if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(text);
return Promise.resolve(false);
}
var actions = {
copy_as_json: function () { copyText(JSON.stringify(data, null, 2)); },
download_json: function () {
downloadBlob(safeName(manifest.title) + '.json', 'application/json', JSON.stringify(data, null, 2));
},
download_capsule: function () {
var html = '<!DOCTYPE html>\n' + document.documentElement.outerHTML;
downloadBlob(safeName(manifest.title) + '_capsule.html', 'text/html', html);
},
print_to_pdf: function () { setTimeout(function () { window.print(); }, 150); },
about: function () {
var d = document.getElementById('about');
if (d) d.open = !d.open;
}
};
document.querySelectorAll('[data-capsule-action]').forEach(function (el) {
var name = el.getAttribute('data-capsule-action');
if (!actions[name]) return;
el.addEventListener('click', function (e) {
if (el.tagName === 'BUTTON') e.preventDefault();
try { actions[name](); } catch (err) { console.error('[md-capsule]', name, err); }
});
});
(manifest.capabilities || []).forEach(function (cap) {
if (!actions[cap]) console.warn('[md-capsule] declared capability "' + cap + '" has no runtime handler — Rule 7 violation');
});
})();
</script>
</body>
</html>