{"schemaVersion":"hay.bundle/1","manifest":{"schemaVersion":"hay.app/1","id":"halil.documents","name":"Documents","version":"0.1.1","description":"Read documents, keep notes alongside the source, and work through the details with Hay.","author":"Halil Cetiner","entrypoint":"index.html","icon":"FileText","color":"#20242C","capabilities":["storage","files.read","models.invoke","browser.read"],"views":[{"id":"review","title":"Document","description":"Check the scope, renewal terms, and open questions."},{"id":"renewals","title":"Renewal review","description":"Compare renewal language with the original in view."}],"intents":[{"id":"review","label":"Find the key obligations","prompt":"Review the selected document. Summarise the key obligations and questions I should check, with short source excerpts. Do not give legal advice or invent missing facts.","viewId":"review"},{"id":"renewals","label":"Check the renewal terms","prompt":"Find renewal and termination notice terms in my documents. Quote the source language, distinguish explicit dates from inferred dates, and explain what needs verification.","viewId":"renewals"},{"id":"questions","label":"Draft questions to ask","prompt":"Draft questions to clarify the scope, renewal terms, and approval process in my documents. Quote relevant excerpts, distinguish stated facts from inferences, and do not invent missing clauses.","viewId":"review"}],"defaultTask":{"title":"Review the Northstar agreement","viewId":"review"},"agent":{"instructions":"Help review and explain documents, including contractual obligations, renewal dates and notice periods. Quote short exact excerpts and identify the document. Distinguish stated dates and obligations from inferences, explain facts that need verification, and avoid claiming to give legal advice. Never invent clauses or treat fictional sample documents as real agreements."}},"html":"<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<style>\n:root {font-family:'Instrument Sans','Helvetica Neue',Arial,sans-serif;color:#20242C;background:#FFFFFF;font-synthesis:none;color-scheme:light;-webkit-font-smoothing:antialiased;}\n* {box-sizing:border-box;}\nbody {margin:0;font-size:13px;}\nbutton,textarea {font:inherit;color:inherit;}\nbutton {cursor:pointer;}\nbutton:focus-visible,textarea:focus-visible {outline:2px solid #3155DF;outline-offset:2px;}\nbutton:disabled {opacity:.55;cursor:wait;}\n.top {display:flex;align-items:center;gap:12px;padding:18px 32px 0;}\n.top>div {display:flex;align-items:center;flex-wrap:wrap;gap:6px;}\n.eyebrow {font-size:11px;font-weight:500;color:#666666;letter-spacing:0;margin-bottom:9px;}\n.top .eyebrow,.pill {display:none;}\nh1 {font:400 11px/1.5 'Instrument Sans',Arial,sans-serif;color:#666666;margin:0;}\n.subtitle {font-size:11px;color:#666666;margin:0;}\n.subtitle:before {content:'·';margin-right:6px;}\n.layout {display:flex;flex-direction:column;min-height:0;}\n.sidebar {padding:10px 32px 19px;border-bottom:1px solid #D9D9D9;background:#FFFFFF;}\n.section-label,.sidebar-note {display:none;}\n#document-list {display:flex;flex-wrap:wrap;gap:6px;}\n.doc-button {position:relative;display:inline-flex;align-items:center;gap:7px;max-width:100%;border:0;border-radius:4px;padding:8px 9px;background:none;text-align:left;font-size:11px;line-height:1.5;color:#666666;overflow-wrap:anywhere;}\n.doc-button.active {background:#EDEDED;color:#20242C;}\n.doc-button:hover {background:#F7F7F7;}\n.doc-icon {font-size:14px;color:#20242C;}\n.small {position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;}\n.main {min-width:0;padding:32px;}\n.paper {max-width:720px;margin:auto;}\n.paper-head {display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px;flex-wrap:wrap;}\n.paper-head>div {min-width:0;flex:1 1 240px;}\n.paper h2 {font:400 31px/1.1 'Newsreader',Georgia,serif;letter-spacing:-.7px;margin:0;overflow-wrap:anywhere;}\n.date {font-size:11px;color:#666666;line-height:1.7;margin-top:10px;overflow-wrap:anywhere;}\n.text {white-space:pre-wrap;overflow-wrap:anywhere;font:400 17px/1.6 'Newsreader',Georgia,serif;padding:8px 0 30px;border-bottom:1px solid #D9D9D9;}\n.action {border:1px solid #D9D9D9;background:#FFFFFF;padding:8px 10px;border-radius:5px;color:#20242C;font-size:11px;white-space:nowrap;}\n.action:hover {background:#F7F7F7;}\n.notes {padding-top:24px;}\n.notes label {display:block;font-weight:500;font-size:12px;margin-bottom:10px;}\n.notes textarea {display:block;width:100%;min-height:130px;border:1px solid #D9D9D9;border-radius:5px;padding:14px;background:#FFFFFF;line-height:1.7;resize:vertical;}\ntextarea::placeholder {color:#666666;}\n.save-status {font-size:11px;margin-top:9px;min-height:17px;color:#666666;text-align:right;}\n.notice {padding:12px 0;border-bottom:1px solid #D9D9D9;font-size:12px;line-height:1.6;color:#666666;}\n.error {color:#20242C;font-weight:500;}\n.empty {padding:35px 0;color:#666666;line-height:1.7;}\n.empty h2 {color:#20242C;font:400 28px/1.15 'Newsreader',Georgia,serif;margin:0 0 12px;}\n.renewal-title {font:500 26px/1.15 'Instrument Sans',Arial,sans-serif;letter-spacing:-.7px;margin:0;}\n.renewal-intro {font-size:12px;line-height:1.7;color:#666666;margin:12px 0 23px;}\n.renewal-card {background:#FFFFFF;border:0;border-top:1px solid #D9D9D9;padding:24px 0;margin:23px 0 0;}\n.renewal-card h3 {font-size:13px;font-weight:500;line-height:1.5;overflow-wrap:anywhere;margin:0 0 11px;}\n.excerpt {font:400 18px/1.55 'Newsreader',Georgia,serif;white-space:pre-wrap;overflow-wrap:anywhere;}\n.tag {display:block;font-size:11px;color:#666666;margin-bottom:15px;}\n.renewal-card button {border:0;background:none;color:#20242C;font-size:12px;padding:17px 0 0;text-decoration:underline;text-underline-offset:3px;}\n.pinned-label {font-size:11px;color:#666666;margin-bottom:13px;}\n.renewals-with-pin {display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:32px;}\n.renewals-with-pin .paper h2 {font-size:26px;}\n.renewals-with-pin .paper-head {flex-direction:column;}\n.renewals-with-pin .paper-head>div {flex-basis:auto;}\n.hide {display:none!important;}\n@media(max-width:760px) {.renewals-with-pin {grid-template-columns:1fr;}.renewals-with-pin .paper {margin:0;border-bottom:1px solid #D9D9D9;padding-bottom:24px;}}\n@media(max-width:460px) {.top {padding:18px 22px 0;}.sidebar {padding:10px 22px 19px;}.main {padding:28px 22px;}.paper-head {flex-direction:column;}.paper-head>div {flex-basis:auto;}.paper h2 {font-size:29px;}}\n@media(pointer:coarse) {button {min-height:44px;}textarea {font-size:16px;}}\n</style></head>\n<body><header class=\"top\"><div><div class=\"eyebrow\">Documents</div><h1 id=\"heading\">Files in this task</h1><p class=\"subtitle\" id=\"subheading\"></p></div><span class=\"pill\">Stored on your device</span></header><div id=\"status\" role=\"status\" aria-live=\"polite\"></div><div class=\"layout\"><aside class=\"sidebar\"><div class=\"section-label\">Documents</div><nav id=\"document-list\" aria-label=\"Documents\"></nav><p class=\"sidebar-note\">Use “Import documents” in Hay to bring your own text or Markdown files into this workspace.</p></aside><main class=\"main\" id=\"main\"><div class=\"empty\">Opening documents…</div></main></div>\n<script>\n(() => {\n  const $ = id => document.getElementById(id);\n  const escape = value => String(value ?? '').replace(/[&<>\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]));\n  let docs = [], selectedId, notes = {}, context = { viewId:'review', pinned:false }, busy = false, loaded = false, refreshing = false;\n  let saveVersion = 0;\n  function status(message, error = false) { $('status').className = message ? 'notice'+(error?' error':'') : ''; $('status').style.margin = message ? '14px 24px 0' : '0'; $('status').textContent = message; }\n  async function persistNotes() {\n    const version = ++saveVersion;\n    const snapshot = {...notes};\n    // Dispatch during the input event. The trusted host serializes storage commits.\n    const pendingSave = Hay.storage.set('notes', snapshot);\n    try { await pendingSave; const el = $('saved'); if(el && version === saveVersion)el.textContent='Saved on your device'; } catch(e) { const el = $('saved'); if(el && version === saveVersion)el.textContent='Not saved — '+e.message; }\n  }\n  function selected() { return docs.find(d => d.id === selectedId) || docs[0]; }\n  async function select(id) { await persistNotes(); selectedId = id; render(); try { await Hay.storage.set('selectedDocument', id); } catch(e) {status(e.message,true);} }\n  function documentHtml(doc) {\n    if (!doc) return '<div class=\"empty\"><h2>Add your first document</h2><p>Use “Add files” in the toolbar to import a text or Markdown file. Keep your working notes alongside it.</p></div>';\n    const title=doc.name.replace(/\\.[^.]+$/,'').split(' · ').at(-1);\n    return '<section class=\"paper\">'+(context.pinned?'<div class=\"pinned-label\">⌖ Pinned document · stays in view</div>':'')+'<div class=\"paper-head\"><div><h2>'+escape(title)+'</h2><div class=\"date\">'+escape(doc.name)+'<br>'+(doc.sample?'Fictional sample document':'Imported '+new Date(doc.importedAt).toLocaleDateString(undefined,{day:'numeric',month:'short',year:'numeric'}))+'</div></div><button class=\"action\" id=\"review-action\"'+(busy?' disabled':'')+'>Review with Hay</button></div><div class=\"text\">'+escape(doc.text)+'</div><div class=\"notes\"><label for=\"note\">Your working notes</label><textarea id=\"note\" placeholder=\"Questions, details, and points to follow up.\">'+escape(notes[doc.id] || '')+'</textarea><div id=\"saved\" class=\"save-status\" role=\"status\">'+(notes[doc.id]?'Saved on your device':'Notes save on this device.')+'</div></div></section>';\n  }\n  function renewalHtml() {\n    const cards = docs.map(doc => {\n      const paragraphs = doc.text.split(/\\n\\s*\\n|(?<=\\.)\\s+(?=[A-Z])/).filter(p => /renew|notice|terminat|expir/i.test(p));\n      return '<article class=\"renewal-card\"><h3>'+escape(doc.name)+'</h3><span class=\"tag\">'+(doc.sample?'FICTIONAL SAMPLE':'SOURCE EXCERPTS')+'</span><div class=\"excerpt\">'+(paragraphs.length?paragraphs.slice(0,4).map(escape).join('\\n\\n'):'No renewal or notice language found by keyword. Ask Hay to review the full text.')+'</div><button data-select=\"'+escape(doc.id)+'\">Read document ↗</button></article>';\n    }).join('');\n    return '<section><div class=\"eyebrow\">Source excerpts</div><h2 class=\"renewal-title\">Renewals & notice</h2><p class=\"renewal-intro\">Exact language found by keyword. These excerpts are not a legal assessment or calculated deadlines.</p><button class=\"action\" id=\"renewal-action\"'+(busy?' disabled':'')+'>Compare terms with Hay</button>'+ (cards || '<div class=\"empty\">Add documents to review their renewal terms.</div>')+'</section>';\n  }\n  function render() {\n    $('heading').textContent = 'Files in this task';\n    $('subheading').textContent = docs.length+' document'+(docs.length===1?'':'s');\n    $('document-list').innerHTML = docs.map(doc => '<button class=\"doc-button'+(selected()?.id===doc.id?' active':'')+'\" aria-pressed=\"'+(selected()?.id===doc.id)+'\" data-doc=\"'+escape(doc.id)+'\"><span class=\"doc-icon\" aria-hidden=\"true\">▤</span><span>'+escape(doc.name)+'</span><span class=\"small\">'+(doc.sample?'Sample':'Local document')+'</span></button>').join('') || '<span class=\"eyebrow\">No documents yet</span>';\n    document.querySelectorAll('[data-doc]').forEach(el => el.onclick = () => select(el.dataset.doc));\n    const renewal = context.viewId === 'renewals';\n    $('main').className = 'main'+(renewal && context.pinned?' renewals-with-pin':'');\n    $('main').innerHTML = renewal ? (context.pinned?documentHtml(selected()):'')+renewalHtml() : documentHtml(selected());\n    document.querySelectorAll('[data-select]').forEach(el => el.onclick = () => select(el.dataset.select));\n    if ($('note')) $('note').oninput = event => { const doc = selected(); if(!doc)return; notes[doc.id] = event.target.value; $('saved').textContent='Saving…'; void persistNotes(); };\n    if ($('review-action')) $('review-action').onclick = () => run('Review the document titled \"'+selected().name+'\". Summarise obligations and questions to check. Cite short exact excerpts and do not invent facts.');\n    if ($('renewal-action')) $('renewal-action').onclick = () => run('Compare renewal and termination notice terms in my documents. Quote exact excerpts, distinguish stated dates from inferences, and list facts that need verification. Do not give legal advice.');\n  }\n  async function run(prompt) {busy=true; status('Hay is reviewing your request. Follow the response in the assistant panel.'); render(); try {await Hay.agent.run(prompt); status('Request sent. Follow the live response in the assistant panel.');} catch(e){status(e.message,true);} finally{busy=false;render();}}\n  async function refresh() {\n    if(refreshing)return; refreshing=true;\n    try {const next=await Hay.documents.list(); if(!Array.isArray(next))throw new Error('The document library could not be read.'); if(JSON.stringify(next)!==JSON.stringify(docs)){docs=next;render();}}catch(e){status(e.message,true);}finally{refreshing=false;}\n  }\n  Hay.onContext(next => { const changed = context.viewId !== next.viewId || context.pinned !== next.pinned; context = next; if(loaded){if(changed){persistNotes();render();} refresh();} });\n  Hay.ready.then(async () => {try {const values=await Promise.all([Hay.documents.list(),Hay.storage.get('selectedDocument'),Hay.storage.get('notes')]);docs=Array.isArray(values[0])?values[0]:[];selectedId=typeof values[1]==='string'?values[1]:docs[0]?.id;notes=values[2]&&typeof values[2]==='object'?values[2]:{};loaded=true;render();}catch(e){$('main').innerHTML='<div class=\"empty\"><h2>Your library needs permission.</h2><p>Enable document and storage access in Hay’s application settings.</p></div>';status(e.message,true);}});\n})();\n</script></body></html>\n"}
