Back to feed-lab
SOURCE / PINNED RELEASE

Made of little things.

feed-lab

Release
f34636df37d1…
Author-recorded commit
Not provided
License
Check the original source

The hash-verified playable HTML, which may contain bundled or minified code. This view does not execute it.

index.html
<!doctype html>
<html lang="en"><head><meta name="description" content="Bounded historical relay query test fixture."><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Feed Lab</title>  <script type="module" crossorigin>function et(){const t=window;if(!t.napplet)throw new Error("window.napplet is unavailable -- runtime did not inject the napplet namespace");return t.napplet}function a(t){const n=et()[t];if(!n)throw new Error(`window.napplet.${String(t)} is unavailable -- runtime did not inject this domain`);return n}var _={subscribe(t,e,n,r){return a("relay").subscribe(t,e,n,r)},publish(t,e){return a("relay").publish(t,e)},publishEncrypted(t,e,n="nip44"){return a("relay").publishEncrypted(t,e,n)},query(t){return a("relay").query(t)}},K={getPublicKey(){return a("identity").getPublicKey()},onChanged(t){return a("identity").onChanged(t)},getRelays(){return a("identity").getRelays()},getProfile(){return a("identity").getProfile()},getFollows(){return a("identity").getFollows()},getList(t){return a("identity").getList(t)},getZaps(){return a("identity").getZaps()},getMutes(){return a("identity").getMutes()},getBlocked(){return a("identity").getBlocked()},getBadges(){return a("identity").getBadges()}},q={getEvent(t,e){return a("outbox").getEvent(t,e)},query(t,e){return a("outbox").query(t,e)},subscribe(t,e){return a("outbox").subscribe(t,e)},publish(t,e){return a("outbox").publish(t,e)},resolveRelays(t){return a("outbox").resolveRelays(t)}};function s(t){const e=document.getElementById(t);if(!e)throw new Error("Missing fixture control");return e}const c=s("lab"),P=s("status"),x=s("events"),E=s("filters"),Q=s("query"),Z=s("empty"),z=s("subscribe"),G=s("close"),J=s("stream-status");let f=null,O=0,j=!1;const $=new Set,F=s("outbox-query"),v=s("outbox-status"),w=s("outbox-result"),X=["outbox-event","outbox-resolve-read","outbox-resolve-write"].map(t=>s(t)),H=s("outbox-start"),U=s("outbox-close"),m=s("outbox-stream-status"),V=s("outbox-options"),Y=s("outbox-publish"),nt=s("outbox-publish-options"),k=s("outbox-publish-status"),M=s("outbox-publish-result");let L=!1;const D=new Set;window.addEventListener("message",t=>{t.source===parent&&typeof t.data?.type=="string"&&t.data.type.startsWith("outbox.")&&t.data.type.length<=128&&D.size<32&&D.add(t.data.type)});let y=null,C=0,h=!1,i=!1,g=!1,d=!1;function l(){Y.disabled=!d||i||L,Q.disabled=Z.disabled=!d||g||i||f!==null,z.disabled=!d||g||i||f!==null,G.disabled=f===null||i,F.disabled=!d||i||h;for(const t of X)t.disabled=F.disabled;H.disabled=!d||i||h||y!==null,U.disabled=i||y===null}function tt(){if(!(g||i||!d)){g=!0,l(),x.replaceChildren(),P.textContent="Reading notes…",c.dataset.state="loading";try{const t=JSON.parse(E.value);if(!Array.isArray(t)||t.length<1||t.length>4||!t.every(e=>e&&typeof e=="object"&&!Array.isArray(e)))throw new Error("Enter a list of query filters.");_.query(t).then(e=>{if(!i){if(!Array.isArray(e)||e.length>32)throw new Error("Invalid query result.");for(const{event:n}of e){if(!n||typeof n.content!="string"||typeof n.id!="string")throw new Error("Invalid event result.");const r=document.createElement("li"),o=document.createElement("p"),b=document.createElement("code");o.textContent=n.content,b.textContent=n.id,r.append(o,b),x.append(r)}c.dataset.state=e.length?"success":"empty",P.textContent=e.length?`Received ${e.length} signed note${e.length===1?"":"s"}.`:"Query completed. No matching notes."}}).catch(T).finally(()=>{g=!1,l()})}catch(t){T(t),g=!1,l()}}}function T(t){i||(x.replaceChildren(),c.dataset.state="error",P.textContent=`Query failed: ${t instanceof Error?t.message:"unavailable"}`)}function B(){J.textContent=`Live · ${O} signed note${O===1?"":"s"}${j?" · history loaded":" · loading history"}.`}function S(){const t=f;f=null,t?.close(),J.textContent="Live feed closed.",c.dataset.stream="closed",l()}z.addEventListener("click",()=>{if(!(i||g||!d||f))try{const t=JSON.parse(E.value);if(!Array.isArray(t)||t.length<1||t.length>4||!t.every(e=>e&&typeof e=="object"&&!Array.isArray(e)))throw new Error("Enter a list of note filters.");x.replaceChildren(),$.clear(),O=0,j=!1,B(),c.dataset.stream="open",f=_.subscribe(t,({event:e})=>{if(i||!f||$.has(e.id))return;if(typeof e.content!="string"||typeof e.id!="string"){S();return}const n=document.createElement("li"),r=document.createElement("p"),o=document.createElement("code");if(n.dataset.eventId=e.id,r.textContent=e.content,o.textContent=e.id,n.append(r,o),x.prepend(n),$.add(e.id),O++,x.children.length>32){const b=x.lastElementChild;$.delete(b.dataset.eventId),b.remove()}if(O>=Number.MAX_SAFE_INTEGER){S();return}B()},()=>{!i&&f&&(j=!0,B())}),l()}catch(t){S(),J.textContent=`Live feed failed: ${t instanceof Error?t.message:"unavailable"}`}});G.addEventListener("click",S);window.addEventListener("message",t=>{t.source!==parent||!f||t.data?.type!=="relay.closed"||(f=null,c.dataset.stream="closed",J.textContent=`Live feed closed: ${typeof t.data.reason=="string"?t.data.reason:"unavailable"}`,l())});Q.addEventListener("click",()=>{tt()});Z.addEventListener("click",()=>{E.value='[{"kinds":[1],"limit":0}]',tt()});window.addEventListener("pagehide",()=>{S(),i=!0,l()},{once:!0});l();d=!0;c.dataset.ready="true";P.textContent="Ready to query.";l();const N=s("follows"),W=s("follows-result"),A=s("follows-status");let R=!1,u=0;function ot(){L=!1,M.textContent="",k.textContent="No publication requested.",I(),h=!1,w.textContent="",v.textContent="No Outbox query yet.",l(),u++,R=!1,N.disabled=i,W.textContent="",A.textContent="No follows request yet."}try{const t=K.onChanged(ot);window.addEventListener("pagehide",()=>{u++,N.disabled=!0,t.close()},{once:!0})}catch{}N.addEventListener("click",()=>{if(i||R)return;const t=u,e=()=>!i&&t===u;R=!0,N.disabled=!0,A.textContent="Reading public follows…",K.getFollows().then(n=>{e()&&(W.textContent=JSON.stringify(n),A.textContent="Follows read complete.")}).catch(n=>{e()&&(W.textContent="",A.textContent="Follows read failed: "+(n instanceof Error?n.message.slice(0,240):"identity unavailable"))}).finally(()=>{e()&&(R=!1,N.disabled=!1)})});F.addEventListener("click",()=>{if(!d||i||h)return;const t=u,e=()=>!i&&t===u;h=!0,l(),w.textContent="",v.textContent="Resolving author write relays…",c.dataset.outbox="loading";const n=o=>{e()&&(v.textContent=`Outbox failed: ${o instanceof Error?o.message:"unavailable"}`,c.dataset.outbox="error")},r=()=>{e()&&(h=!1,l())};try{const o=JSON.parse(E.value);if(!Array.isArray(o))throw Error("Enter a list of query filters.");const b=JSON.parse(V.value);q.query(o,Object.keys(b).length?b:void 0).then(p=>{if(e()){if(w.textContent=JSON.stringify(p),p.error)throw Error(p.error);v.textContent=`Outbox received ${p.events.length} signed event${p.events.length===1?"":"s"}${p.incomplete?" · incomplete":""}.`,c.dataset.outbox="success"}}).catch(n).finally(r)}catch(o){n(o),r()}});for(const t of X)t.addEventListener("click",()=>{if(!d||i||h)return;const e=u,n=()=>!i&&e===u;h=!0,l(),w.textContent="",v.textContent="Resolving Outbox read…",(async()=>{const r=JSON.parse(E.value);if(!Array.isArray(r)||r.length!==1||!r[0]||typeof r[0]!="object")throw Error("Enter one filter with the target author or event ID.");const o=r[0];if(t.id==="outbox-event"){if(o.ids?.length!==1)throw Error("Enter one event ID in the filter.");return q.getEvent(o.ids[0],o.authors?.length===1?{author:o.authors[0]}:{})}if(!o.authors?.length)throw Error("Enter target authors in the filter.");return q.resolveRelays({authors:o.authors,direction:t.id==="outbox-resolve-write"?"write":"read"})})().then(r=>{if(n()){if(w.textContent=JSON.stringify(r),"error"in r&&r.error)throw Error(r.error);v.textContent=t.id==="outbox-event"?"Outbox event read complete.":"Outbox relay plan resolved.",c.dataset.outbox="success"}}).catch(r=>{n()&&(v.textContent=`Outbox failed: ${r instanceof Error?r.message:"unavailable"}`,c.dataset.outbox="error")}).finally(()=>{n()&&(h=!1,l())})});function I(){const t=y;y=null,t?.close(),m.textContent="Live Outbox closed.",l()}H.addEventListener("click",()=>{if(!d||i||h||y)return;const t=u,e=[];C=0,w.textContent="";try{const n=JSON.parse(E.value),r=JSON.parse(V.value);y=q.subscribe(n,Object.keys(r).length?r:void 0),y.on("event",o=>{i||!y||u!==t||(e.push(o),e.length>4&&e.shift(),C++,w.textContent=JSON.stringify({events:e,count:C,wireTypes:[...D]}),m.textContent=`Live Outbox · ${C} signed event${C===1?"":"s"}.`)}),y.on("closed",o=>{i||u!==t||(y=null,m.textContent=`Live Outbox closed: ${o??"unavailable"}`,l())}),m.textContent="Live Outbox connecting…",l()}catch(n){I(),m.textContent=`Live Outbox failed: ${n instanceof Error?n.message:"unavailable"}`}});U.addEventListener("click",I);window.addEventListener("pagehide",I,{once:!0});Y.addEventListener("click",()=>{if(!d||i||L)return;const t=u,e=()=>!i&&t===u;L=!0,l(),M.textContent="",k.textContent="Waiting for shell review…";const n=o=>{e()&&(k.textContent="Publication failed: "+(o instanceof Error?o.message:"unavailable"))},r=()=>{e()&&(L=!1,l())};try{const o=s("draft").value;if(!o.trim())throw Error("Write a draft before requesting publication.");const b=JSON.parse(nt.value);q.publish({kind:1,content:o,tags:[],created_at:Math.floor(Date.now()/1e3)},b).then(p=>{e()&&(M.textContent=JSON.stringify(p),k.textContent=p.ok?"Primary event accepted. See the shell for per-relay and relay-list results.":"Publication failed: "+(p.error??"no relay acceptance"))}).catch(n).finally(r)}catch(o){n(o),r()}});
</script>
  <style>*{box-sizing:border-box}html{color-scheme:dark}body{margin:0;background:#140f0b;color:#f3efeb;font:16px/1.5 system-ui,sans-serif}main{max-width:42rem;margin:auto;padding:24px 20px 80px}h1{font-size:34px;line-height:1.1;margin:8px 0 18px}.eyebrow{color:#e8805d;font-size:12px;text-transform:uppercase;letter-spacing:.12em}label{display:block;margin:24px 0 8px;font-weight:600}textarea{display:block;width:100%;min-height:120px;resize:vertical;background:#241b14;color:inherit;border:1px solid #655143;border-radius:8px;padding:12px;font:16px/1.5 ui-monospace,monospace}.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}button{min-height:48px;border:1px solid #e8805d;border-radius:8px;background:#e8805d;color:#21150e;font:inherit;padding:10px 14px;cursor:pointer}button:disabled{opacity:.5;cursor:default}button:focus-visible,textarea:focus-visible{outline:3px solid #fff;outline-offset:3px}#status{min-height:48px}ol{list-style:none;padding:0}li{padding:14px 0;border-top:1px solid #655143}li p{white-space:pre-wrap;overflow-wrap:anywhere}code{display:block;color:#c7b6a7;font-size:11px;overflow-wrap:anywhere}[data-state=error] #status{color:#ffaf9b}#follows-result,#outbox-result,#outbox-publish-result{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}fieldset{min-inline-size:0}
</style>
</head>
<body><main id="lab" data-testid="lab"><p class="eyebrow">Hypergolic fixture</p><h1>Feed Lab</h1>
<p>Read signed notes through your shell’s relays.</p>
<label for="filters">Note filters</label><textarea id="filters" spellcheck="false" rows="4">[{"kinds":[1],"limit":8}]</textarea>
<div class="actions"><button id="query" type="button">Query notes</button><button id="empty" type="button">Query empty sample</button></div>
<div class="actions"><button id="subscribe" type="button">Start live feed</button><button id="close" type="button" disabled>Close live feed</button></div>
<p id="stream-status" aria-live="polite">Live feed closed.</p>
<label for="draft">Your draft</label><textarea id="draft" spellcheck="false" rows="2" placeholder="Write here while reading…"></textarea>
<p id="status" role="status" aria-live="polite">Connecting to shell…</p><ol id="events" aria-label="Query results"></ol>
<fieldset><legend>Selected identity</legend><button id="follows" type="button">Read follows</button>
<p id="follows-status" aria-live="polite">No follows request yet.</p><pre id="follows-result"></pre></fieldset>
<fieldset><legend>Outbox routing</legend><label for="outbox-options">Query and live options</label><textarea id="outbox-options" spellcheck="false" rows="2">{}</textarea><button id="outbox-query" type="button">Query author outbox</button><div class="actions"><button id="outbox-event" type="button">Fetch signed event</button><button id="outbox-resolve-read" type="button">Resolve author write relays</button><button id="outbox-resolve-write" type="button">Resolve author inbox relays</button></div><div class="actions"><button id="outbox-start" type="button">Start live outbox</button><button id="outbox-close" type="button" disabled>Close live outbox</button></div><p id="outbox-stream-status" aria-live="polite">Live Outbox closed.</p><p id="outbox-status" aria-live="polite">No Outbox query yet.</p><pre id="outbox-result"></pre></fieldset>
<fieldset><legend>Publish your draft through Outbox</legend><p>The shell reviews your exact event, destinations and any existing relay list before signing.</p><label for="outbox-publish-options">Publication options</label><textarea id="outbox-publish-options" spellcheck="false" rows="2">{}</textarea><button id="outbox-publish" type="button">Review draft publication</button><p id="outbox-publish-status" role="status" aria-live="polite">No publication requested.</p><pre id="outbox-publish-result"></pre></fieldset>
</main></body></html>