πŸ“’ Witness Ledger

Evidence of change β€” sealed (SHA-256), verifiable, and merge-friendly for groups.

New Entry

Group Mode (co-witnessing) β€” add a Session ID and co-witnesses

Tip: everyone logs the same Session ID. Use Export/Import to merge group runs across devices. Co-witnesses are embedded in the sealed payload.

πŸ—“οΈ Streak: 0 days Ξ avg (7d): β€” Entries (7d): β€”
DatePhraseCarrierΞ TagsSessionHashActions
🌱 Γ—7 πŸ›°οΈ Γ—33 🜚 Γ—144

Verification notes: Seal I = SHA-256 of the canonical payload. β€œOK” means the recomputed hash matches the stored one.

`; win.document.open(); win.document.write(html); win.document.close(); } /* ---------- Export / Import / Verify All ---------- */ $('#btnExport').addEventListener('click', () => { const data = read(); if (!data.length) return alert('No entries to export.'); const blob = new Blob([JSON.stringify({version:'sof-ledger-v2', entries:data}, null, 2)], {type:'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'scroll-of-fire-ledger.json'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); }); $('#btnImport').addEventListener('click', () => $('#fileImport').click()); $('#fileImport').addEventListener('change', async e => { const f=e.target.files?.[0]; if(!f) return; try{ const txt=await f.text(); const parsed=JSON.parse(txt); const incoming = Array.isArray(parsed) ? parsed : (Array.isArray(parsed.entries) ? parsed.entries : []); // verify incoming where possible const verified = []; for (const item of incoming){ try{ const r = await verifyEntry(item); if(r.ok) verified.push(item); }catch{ /* ignore bad */ } } const existing = read(); const ids = new Set(existing.map(x=>x.hash || (x.when+'|'+x.phrase))); const merged = verified.filter(x=>!ids.has(x.hash || (x.when+'|'+x.phrase))).concat(existing); write(merged); render(merged); alert(`Imported ${verified.length} verified entr${verified.length===1?'y':'ies'} (merged).`); }catch(err){ alert('Import failed: '+(err.message||err)); } e.target.value=''; }); $('#btnVerifyAll').addEventListener('click', async () => { const L = read(); let ok=0, bad=0; for (const e of L) { const r = await verifyEntry(e); if (r.ok) ok++; else bad++; } alert(`Verified: ${ok} OK, ${bad} mismatch.`); render(L); }); /* ---------- Wire add / filter / shortcuts ---------- */ $('#btnAdd').addEventListener('click', add); iWitness.addEventListener('keydown', e => { if((e.ctrlKey||e.metaKey) && e.key==='Enter') add(); }); filter.addEventListener('input', ()=>render()); /* ---------- Init ---------- */ render(); })();