0
TOOLS#4f396f51
GSD Bug Tracker – Log & Manage Issues with GitHub Sync
@Owner·deposited 2d ago·updated 2d ago·14 views
TOOLS#4f396f51
GSD Bug Tracker – Log & Manage Issues with GitHub Sync
OW
@Owner
14Views
0Comments
0Forks
0Saves
SHARE · REMIX
GSD Bug Tracker – Log & Manage Issues with GitHub Sync — a HTML Tools widget by @Owner.
CONTROLS
#bug-tracker#get-shit-done#gsd#claude
No comments yet. Be the first!
✦ Remix with AI
SDK in this widget
vibes.onReadyvibes.savevibes.load
- raw fetch(): Raw fetch() detected. Prefer vibes.fetch for proxied requests.
Generated prompt
You are helping me modify a vibe-coded widget from itjustvibes.com.
[VIBE CODE: "GSD Bug Tracker – Log & Manage Issues with GitHub Sync" by @Owner]
Source: https://itjustvibes.com/Owner/gsd-bug-tracker-log-manage-issues-with-github-sync
Type: HTML
--- SOURCE CODE ---
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Berkeley+Mono:wght@400;700&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{
--bg:#0d0d0d;--bg2:#141414;--bg3:#1a1a1a;--bg4:#222;
--b:rgba(255,255,255,.08);--b2:rgba(255,255,255,.14);
--t:#f0ede8;--t2:#8a8780;--t3:#525050;
--A:#c8f135;--Ad:rgba(200,241,53,.1);--Ab:rgba(200,241,53,.28);
--R:#ff5050;--Rd:rgba(255,80,80,.1);
--O:#f5a623;--Od:rgba(245,166,35,.1);
--U:#4d9fff;--Ud:rgba(77,159,255,.1);
--G:#3de68a;--Gd:rgba(61,230,138,.1);
--r:6px;--rl:10px;
--mono:'Berkeley Mono',monospace;--sans:'DM Sans',sans-serif;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--t);font-family:var(--sans);min-height:100vh;line-height:1.6}
.app{display:grid;grid-template-columns:220px 1fr;min-height:100vh}
.sb{background:var(--bg2);border-right:1px solid var(--b);display:flex;flex-direction:column;position:sticky;top:0;height:100vh;overflow-y:auto}
.logo{padding:20px 18px 16px;border-bottom:1px solid var(--b)}
.logo-ey{font-family:var(--mono);font-size:10px;color:var(--A);letter-spacing:.12em;text-transform:uppercase;margin-bottom:3px}
.logo-nm{font-size:16px;font-weight:500}
.logo-sb{font-size:11px;color:var(--t3);margin-top:1px}
.nav{flex:1;padding:10px 8px}
.ni{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--r);cursor:pointer;font-size:13px;color:var(--t2);border:none;background:none;width:100%;text-align:left;margin-bottom:2px;transition:all .12s}
.ni:hover{background:var(--bg3);color:var(--t)}
.ni.active{background:var(--Ad);color:var(--A);border:1px solid var(--Ab)}
.ni svg{width:14px;height:14px;flex-shrink:0;opacity:.8}
.nbadge{margin-left:auto;background:var(--A);color:#0a0a0a;font-size:9px;font-weight:700;padding:1px 5px;border-radius:99px;font-family:var(--mono)}
.nbadge-o{background:var(--O)}
.sfot{padding:12px 14px;border-top:1px solid var(--b)}
.rchip{display:flex;align-items:center;gap:7px;padding:8px 10px;background:var(--bg3);border:1px solid var(--b);border-radius:var(--r);cursor:pointer;transition:border-color .15s}
.rchip:hover{border-color:var(--b2)}
.rdot{width:6px;height:6px;border-radius:50%;background:var(--t3);flex-shrink:0;transition:background .3s}
.rdot.on{background:var(--G);box-shadow:0 0 6px rgba(61,230,138,.5)}
.rtxt{flex:1;overflow:hidden}
.rlbl{font-family:var(--mono);font-size:11px;color:var(--t2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rbr{font-size:10px;color:var(--t3)}
.main{padding:28px 36px;max-width:860px}
.ph{margin-bottom:24px}
.ph-ti{font-size:20px;font-weight:500;margin-bottom:3px}
.ph-su{font-size:13px;color:var(--t2)}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:24px}
.sc{background:var(--bg2);border:1px solid var(--b);border-radius:var(--rl);padding:12px 14px}
.sc-n{font-family:var(--mono);font-size:24px;font-weight:700}
.sc-l{font-size:10px;color:var(--t3);margin-top:1px;text-transform:uppercase;letter-spacing:.05em}
.fl{display:block;font-family:var(--mono);font-size:10px;color:var(--t3);text-transform:uppercase;letter-spacing:.06em;margin-bottom:5px}
.fi,.fs,.ft{width:100%;background:var(--bg2);border:1px solid var(--b);border-radius:var(--r);color:var(--t);font-family:var(--sans);font-size:14px;padding:8px 12px;outline:none;transition:border-color .15s;-webkit-appearance:none}
.fi:focus,.fs:focus,.ft:focus{border-color:var(--Ab);box-shadow:0 0 0 3px var(--Ad)}
.fi::placeholder,.ft::placeholder{color:var(--t3)}
.ft{resize:vertical;min-height:72px;line-height:1.6}
.fs{cursor:pointer}.fs option{background:var(--bg3)}
.fr{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}
.ff{margin-bottom:12px}
.fh{font-size:11px;color:var(--t3);margin-top:4px}
.fh a{color:var(--U);text-decoration:none}
.btn{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:var(--r);font-family:var(--sans);font-size:13px;font-weight:500;cursor:pointer;border:1px solid var(--b);background:var(--bg3);color:var(--t2);transition:all .12s}
.btn:hover{background:var(--bg4);color:var(--t);border-color:var(--b2)}
.btn:active{transform:scale(.97)}
.btn:disabled{opacity:.35;cursor:not-allowed;transform:none}
.btn svg{width:13px;height:13px;flex-shrink:0}
.btn-a{background:var(--A);color:#0a0a0a;border-color:var(--A);font-weight:600}
.btn-a:hover{background:#d4f545;border-color:#d4f545;color:#0a0a0a}
.btn-g{background:var(--Gd);color:var(--G);border-color:rgba(61,230,138,.2)}
.btn-g:hover{background:rgba(61,230,138,.18);color:var(--G);border-color:rgba(61,230,138,.35)}
.btn-d{color:var(--R);border-color:rgba(255,80,80,.2)}
.btn-d:hover{background:var(--Rd);color:var(--R);border-color:rgba(255,80,80,.35)}
.btn-sm{padding:4px 9px;font-size:11.5px}
.btn-sm svg{width:11px;height:11px}
.brow{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
hr.dv{border:none;border-top:1px solid var(--b);margin:20px 0}
.pill{display:inline-flex;align-items:center;gap:3px;padding:2px 7px;border-radius:99px;font-size:10px;font-weight:500;font-family:var(--mono)}
.p-bug{background:var(--Rd);color:var(--R);border:1px solid rgba(255,80,80,.18)}
.p-todo{background:var(--Ud);color:var(--U);border:1px solid rgba(77,159,255,.18)}
.p-perf{background:var(--Od);color:var(--O);border:1px solid rgba(245,166,35,.18)}
.p-high{background:var(--Rd);color:var(--R)}
.p-med{background:var(--Od);color:var(--O)}
.p-low{background:var(--Gd);color:var(--G)}
.p-open{background:rgba(255,255,255,.04);color:var(--t2)}
.p-wip{background:var(--Od);color:var(--O)}
.p-fixed{background:var(--Gd);color:var(--G)}
.p-ghost{background:rgba(255,255,255,.03);color:var(--t3);border:1px solid var(--b)}
.card{background:var(--bg2);border:1px solid var(--b);border-radius:var(--rl);padding:14px 16px;margin-bottom:8px;transition:border-color .12s}
.card:hover{border-color:var(--b2)}
.ch{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:7px}
.ct{font-size:14px;font-weight:500;flex:1}
.cm{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px;align-items:center}
.cd{font-size:13px;color:var(--t2);line-height:1.6;margin-bottom:8px}
.cp{display:inline-flex;align-items:center;gap:4px;font-family:var(--mono);font-size:10.5px;color:var(--t3);background:var(--bg3);padding:2px 7px;border-radius:4px;margin-bottom:7px}
.cc{font-size:11.5px;color:var(--t3);margin-bottom:6px}
.ca{display:flex;gap:5px;flex-wrap:wrap;margin-top:8px}
.ptag{display:inline-flex;align-items:center;gap:3px;font-size:10.5px;font-family:var(--mono);color:var(--G)}
.dtag{font-size:10.5px;font-family:var(--mono);color:var(--t3)}
.fbar{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:16px}
.fc{padding:3px 10px;border-radius:99px;font-size:11px;font-family:var(--mono);cursor:pointer;border:1px solid var(--b);background:transparent;color:var(--t3);transition:all .12s}
.fc:hover{color:var(--t2);border-color:var(--b2)}
.fc.active{background:var(--Ad);color:var(--A);border-color:var(--Ab)}
.gsd-block{background:var(--bg);border:1px solid var(--b);border-radius:var(--r);padding:12px 14px;font-family:var(--mono);font-size:11px;color:var(--t2);line-height:1.8;white-space:pre-wrap;word-break:break-word;margin-bottom:8px}
.notice{display:flex;gap:9px;padding:10px 13px;border-radius:var(--r);font-size:13px;margin-bottom:16px;line-height:1.5;align-items:flex-start}
.notice svg{width:14px;height:14px;flex-shrink:0;margin-top:2px}
.n-w{background:var(--Od);color:var(--O);border:1px solid rgba(245,166,35,.2)}
.n-ok{background:var(--Gd);color:var(--G);border:1px solid rgba(61,230,138,.2)}
.n-i{background:var(--Ud);color:var(--U);border:1px solid rgba(77,159,255,.2)}
.n-e{background:var(--Rd);color:var(--R);border:1px solid rgba(255,80,80,.2)}
.push-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--bg2);border:1px solid var(--b);border-radius:var(--rl);padding:12px 14px;margin-bottom:12px}
.push-ct{font-family:var(--mono);font-size:24px;font-weight:700;color:var(--A)}
.push-lbl{font-size:12px;color:var(--t2)}
.fm{background:var(--bg2);border:1px solid var(--b);border-radius:var(--rl);overflow:hidden;margin-bottom:16px}
.fmr{display:flex;gap:11px;padding:12px 14px;border-bottom:1px solid var(--b)}
.fmr:last-child{border-bottom:none}
.fmic{color:var(--A);flex-shrink:0;margin-top:1px}
.fmp{font-family:var(--mono);font-size:11.5px;color:var(--t);margin-bottom:2px}
.fmd{font-size:11.5px;color:var(--t3);line-height:1.5}
.cl{background:var(--bg2);border:1px solid var(--b);border-radius:var(--rl);overflow:hidden;max-height:300px;overflow-y:auto}
.cr{display:flex;align-items:flex-start;gap:10px;padding:10px 13px;border-bottom:1px solid var(--b);transition:background .1s}
.cr:last-child{border-bottom:none}
.cr:hover{background:var(--bg3)}
.cdot{width:7px;height:7px;border-radius:50%;flex-shrink:0;margin-top:5px}
.dot-ok{background:var(--G)}.dot-err{background:var(--R)}
.cmsg{font-size:13px;flex:1;line-height:1.4}
.ctime{font-family:var(--mono);font-size:10px;color:var(--t3);white-space:nowrap}
.csha{font-family:var(--mono);font-size:10px;color:var(--U);margin-top:1px}
.cerr{font-size:11px;color:var(--R);margin-top:1px}
.gh-hd{font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.09em;color:var(--t3);margin-bottom:12px;padding-bottom:7px;border-bottom:1px solid var(--b)}
.stsec{margin-bottom:26px}
#toast{position:fixed;bottom:20px;right:20px;background:var(--bg3);border:1px solid var(--b2);border-radius:var(--r);padding:9px 14px;font-size:12px;font-family:var(--mono);color:var(--t);opacity:0;transform:translateY(5px);transition:opacity .18s,transform .18s;pointer-events:none;z-index:999;max-width:280px}
#toast.show{opacity:1;transform:translateY(0)}
.spin{display:inline-block;width:12px;height:12px;border:2px solid rgba(255,255,255,.12);border-top-color:var(--A);border-radius:50%;animation:rot .65s linear infinite;vertical-align:-2px}
@keyframes rot{to{transform:rotate(360deg)}}
.empty{text-align:center;padding:40px 0;color:var(--t3)}
.empty svg{width:28px;height:28px;margin:0 auto 10px;opacity:.3;display:block}
.empty p{font-size:13px}
.panel{display:none}.panel.active{display:block}
.load-screen{display:flex;align-items:center;justify-content:center;min-height:200px;flex-direction:column;gap:10px;color:var(--t3);font-family:var(--mono);font-size:12px}
@media(max-width:640px){
.app{grid-template-columns:1fr}
.sb{position:static;height:auto;flex-direction:row;flex-wrap:wrap;padding:10px;gap:4px}
.logo,.sfot{display:none}
.nav{display:flex;flex-wrap:wrap;padding:0}
.ni{width:auto;padding:6px 10px}
.main{padding:16px}
.stats{grid-template-columns:1fr 1fr}
.fr{grid-template-columns:1fr}
}
</style>
<div class="app">
<aside class="sb">
<div class="logo">
<div class="logo-ey">GSD Tracker</div>
<div class="logo-nm">Bug Queue</div>
<div class="logo-sb">itjustvibes.com</div>
</div>
<nav class="nav">
<button class="ni active" onclick="show('log',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>Log item
</button>
<button class="ni" onclick="show('queue',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></svg>Queue
<span class="nbadge" id="badge" style="display:none"></span>
</button>
<button class="ni" onclick="show('github',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>GitHub
<span class="nbadge nbadge-o" id="ghbadge" style="display:none"></span>
</button>
<button class="ni" onclick="show('gsd',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M8 9l3 3-3 3M13 15h3"/><rect x="2" y="4" width="20" height="16" rx="2"/></svg>GSD commands
</button>
<button class="ni" onclick="show('settings',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>Settings
</button>
</nav>
<div class="sfot">
<div class="rchip" onclick="show('settings',document.querySelectorAll('.ni')[4])">
<span class="rdot" id="rdot"></span>
<div class="rtxt">
<div class="rlbl" id="rlbl">not connected</div>
<div class="rbr" id="rbr"></div>
</div>
</div>
</div>
</aside>
<main class="main">
<div id="loading" class="load-screen">
<span class="spin" style="width:20px;height:20px;border-width:3px"></span>
<span>loading...</span>
</div>
<div id="app-content" style="display:none">
<!-- LOG -->
<div class="panel active" id="panel-log">
<div class="ph"><div class="ph-ti">Log a bug or to-do</div>
<div class="ph-su">Items push directly to <code style="font-family:var(--mono);font-size:11px">AI_CONTEXT/05_BUG_QUEUE.md</code> in your repo</div>
</div>
<div id="log-notice"></div>
<div class="fr">
<div class="ff"><label class="fl">Type</label>
<select class="fs" id="type"><option value="bug">Bug</option><option value="todo">To-do</option><option value="perf">Perf issue</option></select>
</div>
<div class="ff"><label class="fl">Priority</label>
<select class="fs" id="priority"><option value="high">High</option><option value="med">Medium</option><option value="low">Low</option></select>
</div>
</div>
<div class="ff"><label class="fl">Title</label><input class="fi" type="text" id="title" placeholder="Short description"></div>
<div class="ff"><label class="fl">Description / steps to reproduce</label><textarea class="ft" id="desc" placeholder="What broke, what you expected, how to reproduce..."></textarea></div>
<div class="fr">
<div class="ff"><label class="fl">Component</label>
<select class="fs" id="component">
<option value="">-- pick one --</option>
<option value="widget-sandbox">Widget sandbox (iframe)</option>
<option value="sdk">SDK / sdk-bridge</option>
<option value="auth">Auth / OAuth</option>
<option value="feed">Feed / browse</option>
<option value="widget-detail">Widget detail page</option>
<option value="submit">Submit / deposit</option>
<option value="collections">Collections</option>
<option value="leaderboard">Leaderboard / challenges</option>
<option value="mcp-api">MCP server / REST API</option>
<option value="runner">Runner app</option>
<option value="layout">Layout / responsive</option>
<option value="moderation">Moderation</option>
<option value="other">Other</option>
</select>
</div>
<div class="ff"><label class="fl">Viewport</label>
<select class="fs" id="viewport">
<option value="">N/A</option>
<option value="390px mobile">390px mobile</option>
<option value="480px mobile">480px mobile</option>
<option value="640px small tablet">640px tablet</option>
<option value="820px tablet">820px tablet</option>
<option value="1024px">1024px</option>
<option value="1200px">1200px</option>
<option value="1440px">1440px</option>
</select>
</div>
</div>
<div class="ff"><label class="fl">File path (the file you think is responsible)</label>
<input class="fi" type="text" id="filepath" placeholder="apps/web/components/WidgetCard.tsx">
</div>
<div class="ff"><label class="fl">Suspected cause</label>
<input class="fi" type="text" id="cause" placeholder="e.g. SDK bridge not posting after reload">
</div>
<hr class="dv">
<div class="brow">
<button class="btn btn-a" id="btn-push" onclick="saveAndPush()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
Save + push to repo
</button>
<button class="btn" onclick="saveLocal()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
Save locally only
</button>
<span id="push-st" style="font-size:11.5px;font-family:var(--mono)"></span>
</div>
</div>
<!-- QUEUE -->
<div class="panel" id="panel-queue">
<div class="ph"><div class="ph-ti">Bug queue</div><div class="ph-su">All logged items.</div></div>
<div class="stats" id="stats"></div>
<div class="fbar">
<button class="fc active" onclick="setF('all',this)">all</button>
<button class="fc" onclick="setF('open',this)">open</button>
<button class="fc" onclick="setF('wip',this)">in progress</button>
<button class="fc" onclick="setF('fixed',this)">fixed</button>
<button class="fc" onclick="setF('bug',this)">bugs</button>
<button class="fc" onclick="setF('todo',this)">to-dos</button>
<button class="fc" onclick="setF('high',this)">high priority</button>
<button class="fc" onclick="setF('unpushed',this)">not pushed</button>
</div>
<div id="queue-list"></div>
</div>
<!-- GITHUB -->
<div class="panel" id="panel-github">
<div class="ph"><div class="ph-ti">GitHub sync</div><div class="ph-su">Push items to your repo and view the commit log.</div></div>
<div class="stsec">
<div class="gh-hd">Connection</div>
<div id="gh-conn"></div>
<div class="brow" style="margin-bottom:14px">
<button class="btn" id="btn-test-gh" onclick="testConn(false)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12.55a11 11 0 0 1 14.08 0M1.42 9a16 16 0 0 1 21.16 0M8.53 16.11a6 6 0 0 1 6.95 0"/><circle cx="12" cy="20" r="1"/></svg>
Test connection
</button>
<span id="test-res-gh" style="font-size:11.5px;font-family:var(--mono)"></span>
</div>
</div>
<div class="stsec">
<div class="gh-hd">Unpushed items</div>
<div class="push-bar">
<div><div class="push-ct" id="up-count">0</div><div class="push-lbl">items waiting</div></div>
<button class="btn btn-g" id="btn-push-all" onclick="pushAll()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
Push all to repo
</button>
</div>
<div id="up-list"></div>
</div>
<div class="stsec">
<div class="gh-hd">Worker files</div>
<div class="fm">
<div class="fmr"><svg class="fmic" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
<div><div class="fmp" id="fmp-q">AI_CONTEXT/05_BUG_QUEUE.md</div><div class="fmd">Auto-populated worker file. Each push appends a formatted entry. Claude Code reads this during GSD sessions.</div></div>
</div>
<div class="fmr"><svg class="fmic" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
<div><div class="fmp" id="fmp-k">AI_CONTEXT/05_KNOWN_BUGS.md</div><div class="fmd">Curated doc. Gets a queue reference prepended on first push.</div></div>
</div>
</div>
</div>
<div class="stsec">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:7px;border-bottom:1px solid var(--b)">
<div class="gh-hd" style="margin:0;padding:0;border:none">Commit log</div>
<button class="btn btn-sm" onclick="clearLog()">Clear</button>
</div>
<div id="commit-log"><div class="empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg><p>No commits yet</p></div></div>
</div>
</div>
<!-- GSD -->
<div class="panel" id="panel-gsd">
<div class="ph"><div class="ph-ti">GSD commands</div><div class="ph-su">Paste into Claude Code to start a debug workflow.</div></div>
<div class="notice n-i" style="margin-bottom:18px">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>
<span>Each command tells Claude Code to read <code style="font-family:var(--mono);font-size:10px">05_KNOWN_BUGS.md</code> + <code style="font-family:var(--mono);font-size:10px">05_BUG_QUEUE.md</code>, investigate, fix, and update both files.</span>
</div>
<div id="gsd-list"></div>
</div>
<!-- SETTINGS -->
<div class="panel" id="panel-settings">
<div class="ph"><div class="ph-ti">Settings</div><div class="ph-su">GitHub config is stored in your private Vibes state -- never visible to other users.</div></div>
<div class="stsec">
<div class="gh-hd">GitHub connection</div>
<div class="notice n-i">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<span>Your PAT is stored in your private Vibes state (server-side, per-user, never shared). GitHub API calls go directly from your browser to GitHub -- the PAT never passes through itjustvibes.com servers.</span>
</div>
<div class="fr">
<div class="ff"><label class="fl">GitHub username or org</label><input class="fi" type="text" id="cfg-owner" placeholder="yourname"></div>
<div class="ff"><label class="fl">Repository name</label><input class="fi" type="text" id="cfg-repo" placeholder="it-just-vibes"></div>
</div>
<div class="fr">
<div class="ff"><label class="fl">Branch</label><input class="fi" type="text" id="cfg-branch" placeholder="main"></div>
<div class="ff"><label class="fl">Queue file path</label><input class="fi" type="text" id="cfg-qpath" placeholder="AI_CONTEXT/05_BUG_QUEUE.md"></div>
</div>
<div class="ff"><label class="fl">Known bugs file path</label><input class="fi" type="text" id="cfg-kpath" placeholder="AI_CONTEXT/05_KNOWN_BUGS.md" style="max-width:340px"></div>
<div class="ff"><label class="fl">Personal Access Token</label>
<input class="fi" type="password" id="cfg-token" placeholder="ghp_••••••••••" autocomplete="off">
<div class="fh">Needs <code style="font-family:var(--mono);font-size:10.5px">Contents: Read and write</code> scope. <a href="https://github.com/settings/personal-access-tokens/new" target="_blank">Generate →</a> Leave blank to keep existing.</div>
</div>
<div class="brow">
<button class="btn btn-a" onclick="saveSettings()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>Save settings
</button>
<button class="btn" id="btn-test-s" onclick="testConn(true)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12.55a11 11 0 0 1 14.08 0M1.42 9a16 16 0 0 1 21.16 0M8.53 16.11a6 6 0 0 1 6.95 0"/><circle cx="12" cy="20" r="1"/></svg>Test connection
</button>
<span id="test-res-s" style="font-size:11.5px;font-family:var(--mono)"></span>
</div>
</div>
<hr class="dv">
<div class="stsec">
<div class="gh-hd">Danger zone</div>
<div class="brow">
<button class="btn btn-d" onclick="clearItems()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>Clear all items
</button>
<button class="btn btn-d" onclick="clearConfig()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="8" r="3"/><circle cx="6" cy="15" r="3"/><path d="M13 6H6a3 3 0 0 0-3 3v2M11 18h7a3 3 0 0 0 3-3v-2"/></svg>Clear GitHub config
</button>
</div>
<div class="fh" style="margin-top:8px">Removes data from your Vibes state only. GitHub files are never deleted.</div>
</div>
</div>
</div><!-- /app-content -->
</main>
</div>
<div id="toast"></div>
<script>
let items=[], cfg={owner:'',repo:'',branch:'main',qpath:'AI_CONTEXT/05_BUG_QUEUE.md',kpath:'AI_CONTEXT/05_KNOWN_BUGS.md',token:''};
let clog=[], fstate='all';
const CFG_KEY='gsd-cfg', ITEMS_KEY='gsd-items';
// Poll for __VIBES_STANDALONE__ (not the postMessage stub) + safety timer.
let _started = false;
function _launch(withSDK) {
if (_started) return;
_started = true;
if (!withSDK) {
G('loading').innerHTML = '<span style="font-size:12px;color:var(--t3);font-family:var(--mono)">SDK unavailable -- items won\'t persist.</span>';
setTimeout(() => { G('loading').style.display='none'; G('app-content').style.display='block'; initUI(); }, 1400);
return;
}
vibes.onReady(async () => {
try {
const [ci,cc] = await Promise.all([vibes.load(ITEMS_KEY), vibes.load(CFG_KEY)]);
if (ci) items = ci;
if (cc) cfg = {...cfg, ...cc};
} catch(e) { console.warn('vibes load err', e); }
G('loading').style.display = 'none';
G('app-content').style.display = 'block';
initUI();
});
setTimeout(() => {
if(G('app-content')&&G('app-content').style.display==='none'){G('loading').style.display='none';G('app-content').style.display='block';initUI();}
}, 5000);
}
// Poll for the STANDALONE SDK specifically -- not the postMessage stub.
// __VIBES_STANDALONE__ is set by sdk-injector.ts only when the real SDK loads.
(function waitForSDK(attempts) {
if (typeof vibes !== 'undefined' && window.__VIBES_STANDALONE__) {
_launch(true);
} else if (typeof vibes !== 'undefined' && attempts === 0) {
// vibes exists but never went standalone (submit preview with stub, or unknown env)
_launch(false);
} else if (attempts > 0) {
setTimeout(() => waitForSDK(attempts - 1), 100);
} else {
// nothing at all after 4s -- no vibes, no standalone flag
_launch(false);
}
})(40);
function initUI(){
renderChip();renderLogNotice();renderBadges();renderStats();renderQueue();renderGSD();renderGHPanel();
}
async function saveItems(){try{await vibes.save(ITEMS_KEY,items)}catch(e){console.warn(e)}}
async function saveCfg(){
// never store token separately -- include in full cfg object
try{await vibes.save(CFG_KEY,cfg)}catch(e){console.warn(e)}
}
function G(id){return document.getElementById(id)}
function V(id){return(G(id)||{}).value||''}
function esc(s){return(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
function mkid(){return Date.now().toString(36)+Math.random().toString(36).slice(2,5)}
function ts(){return new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'})}
function lim(s,n){return s&&s.length>n?s.slice(0,n)+'...':s||''}
function readForm(){
return{id:mkid(),type:V('type'),priority:V('priority'),title:V('title').trim(),
desc:V('desc').trim(),component:V('component'),viewport:V('viewport'),
filepath:V('filepath').trim(),cause:V('cause').trim(),
status:'open',created:new Date().toISOString().slice(0,10),pushed:false}
}
function clearForm(){
['title','desc','filepath','cause'].forEach(id=>{const e=G(id);if(e)e.value=''});
['type','priority','component','viewport'].forEach(id=>{const e=G(id);if(e){if(id==='type')e.value='bug';else if(id==='priority')e.value='high';else e.value=''}});
}
function saveLocal(){
const item=readForm();
if(!item.title){toast('Add a title first');return}
items.unshift(item);saveItems();clearForm();refresh();
toast('Saved ✓');
}
async function saveAndPush(){
const item=readForm();
if(!item.title){toast('Add a title first');return}
if(!cfg.owner||!cfg.repo||!cfg.token){toast('Set up GitHub in Settings first');show('settings',document.querySelectorAll('.ni')[4]);return}
setBtn('btn-push',true,'<span class="spin"></span> Pushing...');
G('push-st').textContent='';
try{
const sha=await ghPushItem(item);
item.pushed=true;logC(item.title,'ok',sha);
items.unshift(item);await saveItems();clearForm();refresh();
setSt('push-st','✓ pushed',true);toast('Saved + pushed ✓');
}catch(err){
logC(item.title,'err',null,err.message);
setSt('push-st','✗ '+lim(err.message||'push failed',40),false);
item.pushed=false;items.unshift(item);await saveItems();refresh();
toast('Push failed -- saved locally');
}
setBtn('btn-push',false,GH_BTN);
}
async function pushAll(){
const up=items.filter(i=>!i.pushed&&i.status!=='fixed');
if(!up.length){toast('Nothing to push');return}
if(!cfg.owner||!cfg.repo||!cfg.token){toast('Set up GitHub in Settings first');return}
setBtn('btn-push-all',true,`<span class="spin"></span> Pushing ${up.length}...`);
let ok=0,fail=0;
for(const item of up){
try{const sha=await ghPushItem(item);item.pushed=true;logC(item.title,'ok',sha);ok++}
catch(err){logC(item.title,'err',null,err.message);fail++}
}
await saveItems();refresh();
setBtn('btn-push-all',false,PUSH_ALL_BTN);
toast(`Pushed ${ok}${fail?` (${fail} failed)`:''}`)
}
// GitHub supports CORS with Access-Control-Allow-Origin: * for authenticated requests
// Use direct fetch() -- PAT stays in browser, never goes through vibes.fetch proxy
const BR=()=>cfg.branch||'main';
const QP=()=>cfg.qpath||'AI_CONTEXT/05_BUG_QUEUE.md';
const KP=()=>cfg.kpath||'AI_CONTEXT/05_KNOWN_BUGS.md';
const GH_HDR=()=>({Authorization:`Bearer ${cfg.token}`,Accept:'application/vnd.github.v3+json'});
async function ghGet(path){
const r=await fetch(`https://api.github.com/repos/${cfg.owner}/${cfg.repo}/contents/${encodeURIComponent(path)}?ref=${encodeURIComponent(BR())}`,{headers:GH_HDR()});
if(r.status===404)return null;
if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.message||`GitHub ${r.status}`)}
return r.json();
}
async function ghPut(path,content,sha,msg){
const body={message:msg,content:b64e(content),branch:BR()};
if(sha)body.sha=sha;
const r=await fetch(`https://api.github.com/repos/${cfg.owner}/${cfg.repo}/contents/${encodeURIComponent(path)}`,
{method:'PUT',headers:{...GH_HDR(),'Content-Type':'application/json'},body:JSON.stringify(body)});
if(!r.ok){const e=await r.json().catch(()=>({}));throw new Error(e.message||`GitHub ${r.status}`)}
const d=await r.json();return d.content?.sha||null;
}
function b64e(s){return btoa(encodeURIComponent(s).replace(/%([0-9A-F]{2})/g,(_,p)=>String.fromCharCode(parseInt(p,16))))}
function b64d(s){return decodeURIComponent(atob(s.replace(/\n/g,'')).split('').map(c=>'%'+c.charCodeAt(0).toString(16).padStart(2,'0')).join(''))}
async function ghPushItem(item){
const [qf,kf]=await Promise.all([ghGet(QP()),ghGet(KP())]);
const entry=toMd(item);
const qContent=qf?b64d(qf.content)+'\n'+entry:QHDR+entry;
const sha=await ghPut(QP(),qContent,qf?.sha,`chore(bugs): queue -- ${item.title}`);
if(kf){const kc=b64d(kf.content);if(!kc.includes('05_BUG_QUEUE.md'))await ghPut(KP(),KREF+kc,kf.sha,'chore(bugs): add queue reference')}
return sha;
}
async function pushSingle(id){
const item=items.find(i=>i.id===id);if(!item)return;
if(!cfg.owner||!cfg.repo||!cfg.token){toast('Set up GitHub in Settings first');return}
try{const sha=await ghPushItem(item);item.pushed=true;logC(item.title,'ok',sha);await saveItems();refresh();toast('Pushed ✓')}
catch(err){logC(item.title,'err',null,err.message);renderLog();toast('Push failed: '+lim(err.message||'',40))}
}
async function testConn(fromSettings){
const btnId=fromSettings?'btn-test-s':'btn-test-gh';
const resId=fromSettings?'test-res-s':'test-res-gh';
const res=G(resId);
if(!cfg.owner||!cfg.repo||!cfg.token){setSt(resId,'✗ configure first',false);return}
setBtn(btnId,true,'<span class="spin"></span> Testing...');res.textContent='';
try{
const r=await fetch(`https://api.github.com/repos/${cfg.owner}/${cfg.repo}`,{headers:GH_HDR()});
if(r.ok){const d=await r.json();setSt(resId,`✓ ${d.full_name}`,true)}
else{const e=await r.json().catch(()=>({}));setSt(resId,'✗ '+(e.message||r.status),false)}
}catch(e){setSt(resId,'✗ network error',false)}
setBtn(btnId,false,TEST_BTN);
}
const QHDR=`# Bug Queue -- auto-populated from GSD Bug Tracker
> Items logged remotely. Review and promote into \`05_KNOWN_BUGS.md\` during a GSD session.
> Run: \`/gsd:debug -- process queue in AI_CONTEXT/05_BUG_QUEUE.md\`
---
`;
const KREF=`> **Remote bug queue:** Items from the GSD Bug Tracker live in [\`AI_CONTEXT/05_BUG_QUEUE.md\`](./05_BUG_QUEUE.md). Read it alongside this file at the start of every GSD debug session.
`;
function toMd(item){
const tl={bug:'Bug',todo:'To-do',perf:'Perf issue'}[item.type]||item.type;
const lines=[`## [${item.created}] ${item.title}`,``,`**Type:** ${tl} | **Priority:** ${item.priority} | **Status:** ${item.status}`];
if(item.component)lines.push(`**Component:** ${item.component}`);
if(item.viewport)lines.push(`**Viewport:** ${item.viewport}`);
if(item.filepath)lines.push(`**File:** \`${item.filepath}\``);
if(item.desc)lines.push(``,item.desc);
if(item.cause)lines.push(``,`**Suspected cause:** ${item.cause}`);
lines.push(``,`---`,``);
return lines.join('\n');
}
function buildGSD(item){
return[`/gsd:debug`,``,`**Bug/Task:** ${item.title}`,
item.desc?`**Description:** ${item.desc}`:null,
item.component?`**Component:** ${item.component}`:null,
item.viewport?`**Viewport:** ${item.viewport}`:null,
item.filepath?`**File:** \`${item.filepath}\``:null,
item.cause?`**Suspected cause:** ${item.cause}`:null,
`**Priority:** ${item.priority}`,`**Logged:** ${item.created}`,``,
`Read CLAUDE.md, AI_CONTEXT/05_KNOWN_BUGS.md, and AI_CONTEXT/05_BUG_QUEUE.md. Investigate the root cause${item.filepath?` starting with \`${item.filepath}\``:''}. Propose a fix, update the status in 05_BUG_QUEUE.md, and promote the resolved entry into 05_KNOWN_BUGS.md.`
].filter(l=>l!==null).join('\n');
}
function copyGSD(id){const item=items.find(i=>i.id===id);if(!item)return;navigator.clipboard.writeText(buildGSD(item)).catch(()=>{});toast('GSD command copied ✓')}
async function setStatus(id,s){const item=items.find(i=>i.id===id);if(item){item.status=s;await saveItems();refresh()}}
async function deleteItem(id){items=items.filter(i=>i.id!==id);await saveItems();refresh();toast('Deleted')}
function pT(t){const m={bug:'p-bug',todo:'p-todo',perf:'p-perf'};const l={bug:'bug',todo:'to-do',perf:'perf'};return`<span class="pill ${m[t]||'p-ghost'}">${l[t]||t}</span>`}
function pP(p){return`<span class="pill p-${p}">${p}</span>`}
function pS(s){const m={open:'p-open',wip:'p-wip',fixed:'p-fixed'};const l={open:'open',wip:'in progress',fixed:'fixed'};return`<span class="pill ${m[s]||'p-open'}">${l[s]||s}</span>`}
function cardHtml(item){
return`<div class="card">
<div class="ch"><span class="ct">${esc(item.title)}</span>
<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
${item.pushed?`<span class="ptag"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>pushed</span>`:`<span style="font-size:10.5px;font-family:var(--mono);color:var(--t3)">local</span>`}
<span class="dtag">${item.created}</span>
</div>
</div>
<div class="cm">${pT(item.type)}${pP(item.priority)}${pS(item.status)}
${item.component?`<span class="pill p-ghost">${esc(item.component)}</span>`:''}
${item.viewport?`<span class="pill p-ghost">${esc(item.viewport)}</span>`:''}
</div>
${item.desc?`<div class="cd">${esc(item.desc)}</div>`:''}
${item.filepath?`<div class="cp"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>${esc(item.filepath)}</div>`:''}
${item.cause?`<div class="cc">→ ${esc(item.cause)}</div>`:''}
<div class="ca">
${!item.pushed&&cfg.token?`<button class="btn btn-sm btn-g" onclick="pushSingle('${item.id}')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>Push</button>`:''}
${item.status!=='wip'?`<button class="btn btn-sm" onclick="setStatus('${item.id}','wip')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>Start</button>`:''}
${item.status!=='fixed'?`<button class="btn btn-sm" onclick="setStatus('${item.id}','fixed')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>Fixed</button>`:''}
${item.status!=='open'?`<button class="btn btn-sm" onclick="setStatus('${item.id}','open')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-3.1"/></svg>Reopen</button>`:''}
<button class="btn btn-sm" onclick="copyGSD('${item.id}')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>Copy GSD</button>
<button class="btn btn-sm btn-d" onclick="deleteItem('${item.id}')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg></button>
</div></div>`;
}
function filteredItems(){
const f=fstate;
if(f==='all')return items;
if(['open','wip','fixed'].includes(f))return items.filter(i=>i.status===f);
if(['bug','todo','perf'].includes(f))return items.filter(i=>i.type===f);
if(f==='high')return items.filter(i=>i.priority==='high');
if(f==='unpushed')return items.filter(i=>!i.pushed&&i.status!=='fixed');
return items;
}
function renderBadges(){
const n=items.filter(i=>i.status==='open'||i.status==='wip').length;
const b=G('badge');if(n){b.textContent=n;b.style.display=''}else b.style.display='none';
const u=items.filter(i=>!i.pushed&&i.status!=='fixed').length;
const gb=G('ghbadge');if(u){gb.textContent=u;gb.style.display=''}else gb.style.display='none';
}
function renderStats(){
const o=items.filter(i=>i.status==='open').length,w=items.filter(i=>i.status==='wip').length,
f=items.filter(i=>i.status==='fixed').length,u=items.filter(i=>!i.pushed&&i.status!=='fixed').length;
G('stats').innerHTML=`<div class="sc"><div class="sc-n" style="color:var(--R)">${o}</div><div class="sc-l">open</div></div>
<div class="sc"><div class="sc-n" style="color:var(--O)">${w}</div><div class="sc-l">in progress</div></div>
<div class="sc"><div class="sc-n" style="color:var(--G)">${f}</div><div class="sc-l">fixed</div></div>
<div class="sc"><div class="sc-n" style="color:var(--A)">${u}</div><div class="sc-l">not pushed</div></div>`;
}
function renderQueue(){
const list=filteredItems(),el=G('queue-list');
if(!list.length){el.innerHTML=`<div class="empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="20 6 9 17 4 12"/></svg><p>No items match</p></div>`;return}
el.innerHTML=list.map(cardHtml).join('');
}
function renderGSD(){
const open=items.filter(i=>i.status==='open'||i.status==='wip'),el=G('gsd-list');
if(!open.length){el.innerHTML=`<div class="empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="20 6 9 17 4 12"/></svg><p>Queue is clear</p></div>`;return}
el.innerHTML=open.map(item=>`<div class="card">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px"><span style="font-size:14px;font-weight:500">${esc(item.title)}</span><div style="display:flex;gap:4px">${pT(item.type)}${pP(item.priority)}</div></div>
<div class="gsd-block">${esc(buildGSD(item))}</div>
<button class="btn btn-sm" onclick="copyGSD('${item.id}')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>Copy command</button>
</div>`).join('');
}
function renderGHPanel(){
const up=items.filter(i=>!i.pushed&&i.status!=='fixed');
G('up-count').textContent=up.length;
G('up-list').innerHTML=up.length?up.map(item=>`<div class="card" style="margin-bottom:7px">
<div class="ch"><span class="ct">${esc(item.title)}</span><div style="display:flex;gap:4px">${pT(item.type)}${pP(item.priority)}</div></div>
${item.filepath?`<div class="cp"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>${esc(item.filepath)}</div>`:''}
</div>`).join(''):`<div class="empty" style="padding:18px 0"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="20 6 9 17 4 12"/></svg><p>All pushed</p></div>`;
const conn=G('gh-conn');
if(!cfg.owner||!cfg.repo||!cfg.token){
conn.innerHTML=`<div class="notice n-w"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg><span>GitHub not configured. <button class="btn btn-sm" onclick="show('settings',document.querySelectorAll('.ni')[4])" style="margin-left:6px">Configure →</button></span></div>`;
}else{
conn.innerHTML=`<div class="notice n-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg><span>Connected to <strong>${esc(cfg.owner)}/${esc(cfg.repo)}</strong> · branch <strong>${esc(cfg.branch||'main')}</strong></span></div>`;
}
G('fmp-q').textContent=cfg.qpath||'AI_CONTEXT/05_BUG_QUEUE.md';
G('fmp-k').textContent=cfg.kpath||'AI_CONTEXT/05_KNOWN_BUGS.md';
}
function renderLogNotice(){
const el=G('log-notice');
if(!cfg.owner||!cfg.repo||!cfg.token){
el.innerHTML=`<div class="notice n-w"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg><span>GitHub not configured -- items will save to Vibes state only. <button class="btn btn-sm" onclick="show('settings',document.querySelectorAll('.ni')[4])" style="margin-left:6px">Set up →</button></span></div>`;
}else{
el.innerHTML=`<div class="notice n-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg><span>Connected to <strong>${esc(cfg.owner)}/${esc(cfg.repo)}</strong> · branch <strong>${esc(cfg.branch||'main')}</strong></span></div>`;
}
}
function renderChip(){
const dot=G('rdot'),lbl=G('rlbl'),br=G('rbr');
if(cfg.owner&&cfg.repo&&cfg.token){dot.className='rdot on';lbl.textContent=`${cfg.owner}/${cfg.repo}`;br.textContent=cfg.branch||'main'}
else{dot.className='rdot';lbl.textContent='not connected';br.textContent=''}
}
function renderLog(){
const el=G('commit-log');
if(!clog.length){el.innerHTML=`<div class="empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg><p>No commits yet</p></div>`;return}
el.innerHTML=`<div class="cl">${clog.map(c=>`<div class="cr"><span class="cdot ${c.t==='ok'?'dot-ok':'dot-err'}"></span><div style="flex:1;min-width:0"><div class="cmsg">${esc(c.msg)}</div>${c.sha?`<div class="csha">${c.sha.slice(0,12)}</div>`:''}${c.err?`<div class="cerr">✗ ${esc(c.err)}</div>`:''}</div><div class="ctime">${esc(c.time)}</div></div>`).join('')}</div>`;
}
function logC(msg,t,sha,err){clog.unshift({msg,t,sha,err,time:ts()});renderLog()}
function clearLog(){clog=[];renderLog()}
async function saveSettings(){
cfg.owner=V('cfg-owner');cfg.repo=V('cfg-repo');
cfg.branch=V('cfg-branch')||'main';
cfg.qpath=V('cfg-qpath')||'AI_CONTEXT/05_BUG_QUEUE.md';
cfg.kpath=V('cfg-kpath')||'AI_CONTEXT/05_KNOWN_BUGS.md';
const t=V('cfg-token');if(t)cfg.token=t;
await saveCfg();
G('cfg-token').value='';
renderChip();renderLogNotice();renderGHPanel();
toast('Settings saved ✓');
}
function loadSettingsForm(){
G('cfg-owner').value=cfg.owner||'';G('cfg-repo').value=cfg.repo||'';
G('cfg-branch').value=cfg.branch||'main';
G('cfg-qpath').value=cfg.qpath||'AI_CONTEXT/05_BUG_QUEUE.md';
G('cfg-kpath').value=cfg.kpath||'AI_CONTEXT/05_KNOWN_BUGS.md';
G('cfg-token').value='';
// show placeholder if token is already set
G('cfg-token').placeholder=cfg.token?'••• already set -- paste new to replace':'ghp_••••••••••';
}
async function clearItems(){
if(!confirm('Delete all items from Vibes state?'))return;
items=[];await saveItems();refresh();toast('Cleared');
}
async function clearConfig(){
if(!confirm('Clear GitHub config?'))return;
cfg={owner:'',repo:'',branch:'main',qpath:'AI_CONTEXT/05_BUG_QUEUE.md',kpath:'AI_CONTEXT/05_KNOWN_BUGS.md',token:''};
await saveCfg();loadSettingsForm();renderChip();renderLogNotice();renderGHPanel();toast('Config cleared');
}
function setF(f,btn){fstate=f;document.querySelectorAll('.fc').forEach(b=>b.classList.remove('active'));btn.classList.add('active');renderQueue()}
function show(name,btn){
document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
G('panel-'+name).classList.add('active');
document.querySelectorAll('.ni').forEach(b=>b.classList.remove('active'));
if(btn)btn.classList.add('active');
if(name==='queue'){renderStats();renderQueue()}
if(name==='gsd')renderGSD();
if(name==='github'){renderGHPanel();renderLog()}
if(name==='settings')loadSettingsForm();
}
function refresh(){renderBadges();renderStats();renderQueue();renderGSD();renderGHPanel()}
function setBtn(id,disabled,html){const b=G(id);if(!b)return;b.disabled=disabled;b.innerHTML=html}
function setSt(id,msg,ok){const e=G(id);if(!e)return;e.textContent=msg;e.style.color=ok?'var(--G)':'var(--R)'}
function toast(msg){const t=G('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2500)}
const GH_BTN=`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>Save + push to repo`;
const PUSH_ALL_BTN=`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>Push all to repo`;
const TEST_BTN=`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12.55a11 11 0 0 1 14.08 0M1.42 9a16 16 0 0 1 21.16 0M8.53 16.11a6 6 0 0 1 6.95 0"/><circle cx="12" cy="20" r="1"/></svg>Test connection`;
</script>
```
[REQUESTED CHANGES]
(no specific request — apply your best judgment)
--- HOW TO RESPOND (READ FIRST) ---
Before writing any code, follow this exact process:
1. **ANALYZE** the widget source code provided above and identify:
a. Which Vibes SDK features it already uses (vibes.save, vibes.load, vibes.shared.join, etc.)
b. Which SDK features would genuinely benefit THIS specific widget — tailored to what it does, not a dump of everything available.
2. **PRESENT A NUMBERED LIST** covering:
- SDK features currently active in this widget
- New SDK features that would concretely improve this widget (be specific: why this widget, what it enables)
3. **WAIT** — do not write any code yet. Reply with your analysis and numbered list, then stop and ask the user which numbered items they want.
4. **IMPLEMENT ONLY** the items the user confirms, plus any explicit change they requested. Do not add unrequested features.
**IMPORTANT — Shared state room names:**
If you add vibes.shared.join(), do NOT use a hardcoded string literal as the room name (e.g. vibes.shared.join("lobby")) unless the user explicitly wants ALL viewers to share one single global state. A hardcoded room name means every person who visits this widget reads and writes the same shared state — it is a global room. For per-user or per-session isolation, derive the room name from a variable (e.g. a user ID, session token, or random value). When in doubt, use vibes.save/vibes.load for per-user persistence instead.
--- VIBES SDK CONTEXT ---
## Vibes SDK Reference
You are building an HTML widget for It Just Vibes (itjustvibes.com). The Vibes SDK is auto-injected — do NOT add a script tag. Just use `window.vibes` (or just `vibes`).
### Setup
Wrap your startup code in `vibes.onReady`:
```js
vibes.onReady(async () => {
const saved = await vibes.load("myKey");
// your widget logic here
});
```
### State (Per-User Persistence)
Every user gets their own isolated state per widget. All methods return Promises.
| Method | Description |
|--------|-------------|
| `await vibes.save(key, value)` | Save JSON-serializable data |
| `await vibes.load(key)` | Load saved data (returns `null` if not found) |
| `await vibes.delete(key)` | Delete a saved key |
| `await vibes.listKeys()` | Get array of all saved key names |
**Key rules:**
- Keys are strings, max 64 characters, alphanumeric + dashes/underscores
- Values must be JSON-serializable (objects, arrays, strings, numbers, booleans)
- Max 100KB per value, 500KB per widget per user, 5MB per user total
- Max 100 keys per widget per user
### Fetch Proxy
Use direct `fetch()` for APIs with permissive CORS headers (`Access-Control-Allow-Origin: *`). Use `vibes.fetch` for APIs without CORS headers (the proxy handles cross-origin requests):
```js
const resp = await vibes.fetch("https://api.example.com/data", {
method: "GET", // GET, POST, PUT, DELETE
headers: {}, // optional headers
body: null, // optional body (string)
timeout: null // optional timeout in ms
});
const data = await resp.json(); // or resp.text()
console.log(resp.status, resp.ok);
```
### Multiplayer (Shared State)
Real-time shared state across all users viewing the same widget.
```js
// Join a room (call once at startup)
await vibes.shared.join("lobby", { persistent: true });
// Set shared state (broadcasts to all users)
await vibes.shared.set("score", { player1: 10, player2: 7 });
// Read shared state (synchronous, returns last known value)
const score = vibes.shared.get("score");
// Listen for changes to a specific key
vibes.shared.onChange("score", (newValue) => {
console.log("Score updated:", newValue);
});
// Listen for any shared state change
vibes.shared.onAny((key, value) => {
console.log(key, "changed to", value);
});
// Get number of connected users
const count = await vibes.shared.getUserCount();
// Leave room
vibes.shared.leave();
// Clear all shared state for this room
await vibes.shared.clear();
```
**Shared state options:**
- `{ persistent: true }` — state survives page reloads (stored server-side)
- Default room name is "__default__" if omitted
### Rules
1. **Do NOT use localStorage, sessionStorage, or window.storage** — they are blocked or undefined in the sandbox. Use `vibes.save`/`vibes.load` instead.
2. **Do NOT add a script tag to import the SDK** — it is auto-injected.
3. **Wrap startup code in `vibes.onReady()`** — the SDK may not be ready immediately.
4. **Await all SDK calls** — every method (except `vibes.shared.get`) returns a Promise.
5. **Do NOT import external JS libraries via script tags** — they will be blocked. Include library code inline or use a CDN link in a `<link>` tag for CSS only.
6. **Keep total code under 80KB** — that is the default widget size limit (your account limit may be higher).
### Rate Limits
| Operation | Limit |
|-----------|-------|
| Writes (save + delete combined) | 30/min |
| Reads (load) | 60/min |
| List keys | 30/min |
| Fetch proxy | Rate limited per widget |
### Error Handling
All SDK methods can reject. Wrap in try/catch:
```js
try {
await vibes.save("key", value);
} catch (err) {
console.error("Save failed:", err.message);
}
```
Fetch proxy errors include `err.code`: `"RATE_LIMITED"`, `"BLOCKED"`, `"FETCH_ERROR"`.
### Data Export
Export rows of data as CSV or Excel directly from your widget.
```js
// Register dataset for the platform's "Export data" button
// AND optionally trigger an immediate download
vibes.exportData(rows, { filename: 'results.csv' })
// rows: Array of arrays (each inner array is one row; first row = headers)
// options.filename: sets the download filename and format (csv or xlsx)
// options.directDownload: false to only register without downloading (default: true)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `filename` | string | `'data.csv'` | Download filename; extension determines format (`.csv` or `.xlsx`) |
| `directDownload` | boolean | `true` | Trigger immediate browser download in addition to registering the dataset |
**Notes:**
- First call registers the dataset so the widget chrome shows an "Export data" button
- Use `.csv` extension for CSV, `.xlsx` for Excel
- CSV injection safety is automatic (formula-starting cells prefixed with `'`)
- Data stays in the browser — no server round-trip
--- FORK & RESUBMIT INSTRUCTION ---
Return the complete, self-contained HTML file with all changes applied.
It should be ready to paste into itjustvibes.com/submit to create a new fork.
Include this comment at the top of the output:
/* Forked from: https://itjustvibes.com/Owner/gsd-bug-tracker-log-manage-issues-with-github-sync */