apps › Dashboard · version 1 ·

Open the app

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Your apps</title>
<style>
:root{--paper:#fbfaf7;--card:#fff;--ink:#1d1d1b;--soft:#6b6a66;--line:#e4e1da;--indigo:#9a4b12;--accent:#2e8b57;--display:'Manrope',system-ui,-apple-system,'Segoe UI',sans-serif;--body:system-ui,-apple-system,'Segoe UI',sans-serif;color-scheme:light dark}
@media (prefers-color-scheme:dark){:root{--paper:#141413;--card:#1d1d1b;--ink:#ecebe6;--soft:#9c9a93;--line:#33322f;--indigo:#f0a35e;--accent:#6cc08a}}
*{box-sizing:border-box}
html,body{margin:0;background:var(--paper);color:var(--ink);font:calc(16px*var(--fz,1))/1.5 var(--body)}
.wrap{max-width:980px;margin:0 auto;padding:10px clamp(12px,3vw,28px) 48px}
h1{font:800 calc(clamp(26px,4vw,36px)*var(--fz,1))/1.1 var(--display);letter-spacing:-.02em;margin:10px 0 4px}
.sub{color:var(--soft);margin:0 0 18px}
.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}
.tile{display:flex;flex-direction:column;gap:6px;border:1px solid var(--line);border-radius:14px;background:var(--card);padding:14px 16px;cursor:pointer;text-decoration:none;color:inherit;transition:border-color .15s,transform .15s}
.tile:hover{border-color:var(--indigo);transform:translateY(-1px)}
.tile .ic{font-size:28px;line-height:1}
.tile b{font:700 calc(17px*var(--fz,1)) var(--display)}
.tile span{color:var(--soft);font-size:calc(14px*var(--fz,1))}
.tile.soon{opacity:.55;cursor:default}
.new{border-style:dashed;align-items:flex-start;justify-content:center}
.think{margin:26px 0 0;border:1px solid var(--line);border-radius:14px;background:var(--card);padding:14px 16px}
.think h2{font:700 calc(16px*var(--fz,1)) var(--display);margin:0 0 8px}
.box{min-height:3.4em;border:1px solid var(--line);border-radius:10px;padding:9px 11px;background:var(--paper);white-space:pre-wrap;outline:none}
.box:empty::before{content:attr(data-ph);color:var(--soft)}
.row{display:flex;gap:8px;align-items:center;margin-top:8px;flex-wrap:wrap}
.btn{font:600 calc(14px*var(--fz,1)) var(--body);border:1px solid var(--line);border-radius:8px;padding:5px 12px;background:var(--card);color:var(--ink);cursor:pointer;text-decoration:none}
.btn.primary{background:var(--ink);color:var(--paper);border-color:var(--ink)}
.small{color:var(--soft);font-size:calc(13px*var(--fz,1))}
.ai{margin:14px 0 0;display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap;border:1px solid var(--line);border-radius:14px;background:var(--card);padding:12px 16px}.ai b{font:700 calc(15px*var(--fz,1)) var(--display)}
.foot{margin-top:22px;display:flex;gap:14px;flex-wrap:wrap}
.foot a{color:var(--soft)}
</style></head>
<body><div class="wrap">
<h1>Your apps</h1>
<p class="sub">Everything your AI made for you. Ask it for another one in plain words.</p>
<div class="tiles" id="tiles"><p class="small">Loading your apps…</p></div>
<section class="think"><h2>A thought</h2>
<div class="box" id="tx" contenteditable="plaintext-only" role="textbox" aria-multiline="true" aria-label="A thought" data-ph="An idea for an app, a change, something to remember…"></div>
<div class="row"><button class="btn primary" type="button" id="save">Save it</button><a class="btn" href="#" data-go="/thoughts">Your thoughts</a><span class="small" id="said"></span></div></section>
<section class="ai"><div><b>Your AI</b><div class="small">Lost it, or starting a new chat? Connect it again: the same steps as on your first day.</div></div><a class="btn" href="#" data-go="/ai/tokens?connect=1">Reconnect your AI →</a></section>
<p class="foot small"><a href="#" data-go="/topics/Home">Your first page</a><a href="#" data-go="/topics/Spec">Specification</a><a href="#" data-go="/topics/Help">Help</a></p>
</div>
<script>
(()=>{const D=window.d2||{},$=id=>document.getElementById(id),esc=s=>String(s??"").replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
const api=(m,u,b)=>D.api?Promise.resolve(D.api(m,u,b)):Promise.reject(new Error("this page runs inside d2"));
const go=u=>{if(D.go)D.go(u);else location.href=u};
const tx=$("tx");if(tx.contentEditable!=="plaintext-only")tx.contentEditable="true";
// a link in a Home card: [[Topic]], /path, https://… or "soon"
const where=l=>{l=String(l||"").trim();const w=/^\[\[([^\]|#]+)(?:#([^\]|]+))?(?:\|[^\]]*)?\]\]$/.exec(l);if(w)return "/topics/"+encodeURIComponent(w[1].trim())+(w[2]?"#"+encodeURIComponent(w[2].trim()):"");if(/^(\/|https?:\/\/)/.test(l))return l;return ""};
const SYSTEM=/^\/(topics\/Spec|pipeline)$/;
// the tiles are the cards on Home: the AI adds a card there for every app it makes (heroHome: "Every app your AI makes gets a card")
async function tiles(){let text="";try{const r=await api("GET","/api/v2/topics/Home");text=typeof r==="string"?r:(r&&r.text)||""}catch(e){}
text=text.split(/\n##\s+Start from an example/)[0];   // the examples section links other projects, not this one's apps
const cards=[];for(const b of text.matchAll(/```cards\r?\n([\s\S]*?)```/g))for(const l of b[1].split("\n")){const p=l.split("|").map(x=>x.trim());if(p.length<3||!p[1])continue;const to=where(p[2]);if(SYSTEM.test(to))continue;cards.push({ic:p[0],t:p[1],to,soon:p[2].toLowerCase()==="soon",d:p[3]||""})}
const seen=new Set(),mine=cards.filter(c=>!c.soon&&!seen.has(c.t)&&seen.add(c.t));
$("tiles").innerHTML=mine.map(c=>`<a class="tile" href="#" data-go="${esc(c.to)}"><span class="ic">${esc(c.ic)}</span><b>${esc(c.t)}</b><span>${esc(c.d)}</span></a>`).join("")
 +`<div class="tile new"><b>${mine.length?"Another app?":"No apps yet"}</b><span>Tell your AI what you want, in plain words: <i>an app for my book club</i>, <i>track my runs</i>.</span></div>`}
document.addEventListener("click",e=>{const a=e.target.closest("[data-go]");if(!a)return;e.preventDefault();if(a.dataset.go)go(a.dataset.go)});
$("save").onclick=async()=>{const t=(tx.innerText||"").trim();if(!t){tx.focus();return}
 try{await api("POST","/api/v2/thoughts",{text:t});tx.textContent="";$("said").textContent="Saved in your thoughts."}
 catch(err){$("said").textContent=/E_SCOPE/.test(String(err&&err.message))?"An app can't save a thought yet: open Your thoughts (your text is still here).":"Not saved: "+String(err&&err.message||err).replace(/^E_[A-Z_]+:\s*/,"")}};
tiles()})();
</script></body></html>