← Git Explorer results

Git Explorer

GPT 5.6 terra · typescript

Initial view of Git Explorer

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 24,121 bytes · SHA-256 d1c290f63536
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Git &amp; Structure Visualizer</title>
<style>
/* ── css: app ── */
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@500;600;700;800&display=swap');
:root{--bg:#f5f6f7;--panel:#fff;--ink:#17212b;--muted:#74808c;--line:#e5e8eb;--navy:#183a52;--blue:#3182ce;--teal:#1e9b8a;--orange:#e4853b;--pink:#d95878;--shadow:0 9px 26px rgba(28,42,53,.07)}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:Manrope,Arial,sans-serif;font-size:13px}.shell{min-height:100vh}.topbar{height:72px;background:#fff;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 28px}.brand{display:flex;gap:12px;align-items:center}.gitmark{width:34px;height:34px;background:var(--navy);color:white;border-radius:10px;display:grid;place-items:center;font-size:23px;font-weight:800}.brand h1,.view-head h2,.flow h2{font-size:16px;margin:0;font-weight:800;letter-spacing:-.35px}.brand p{margin:2px 0 0;color:var(--muted);font-size:10px}.header-actions{display:flex;align-items:center;gap:9px}.save-status{font:10px 'DM Mono';color:var(--teal);margin-right:8px}.btn,.wide-btn,.file-label,.tiny-btn,.icon-btn{border:1px solid var(--line);background:#fff;color:#30404d;border-radius:7px;cursor:pointer;font:600 11px Manrope;padding:8px 11px}.btn:hover,.wide-btn:hover,.file-label:hover,.tiny-btn:hover,.icon-btn:hover{border-color:#aeb7bf;background:#fafbfb}.btn.primary{background:var(--navy);color:#fff;border-color:var(--navy);padding:10px 14px}.icon-btn{padding:6px 9px;font-size:16px;line-height:16px}.workspace{display:grid;grid-template-columns:220px minmax(500px,1fr) 270px;min-height:500px;border-bottom:1px solid var(--line)}.sidebar,.inspector{background:#fbfcfc;padding:24px 20px}.sidebar{border-right:1px solid var(--line)}.inspector{border-left:1px solid var(--line)}h2{font-size:12px;margin:0;font-weight:800}.section-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:13px}.tiny-btn{font-size:10px;padding:5px 8px}.branch-list{display:flex;flex-direction:column;gap:7px}.branch{display:flex;align-items:center;justify-content:space-between;border:1px solid transparent;padding:7px 5px;border-radius:6px;cursor:pointer}.branch:hover{background:#fff;border-color:var(--line)}.branch-left{display:flex;align-items:center;gap:7px;font:11px 'DM Mono'}.branch-swatch{width:8px;height:8px;border-radius:50%}.branch-actions button{border:0;background:none;color:#9aa4ad;cursor:pointer;font-size:14px}.legend,.data-tools{margin-top:31px;border-top:1px solid var(--line);padding-top:19px}.legend h2,.data-tools h2{margin-bottom:12px}.legend div{color:var(--muted);font-size:11px;margin:10px 0;display:flex;align-items:center;gap:8px}.dot{width:9px;height:9px;border-radius:50%;display:inline-block}.commit-dot{background:var(--blue)}.event-dot{background:var(--orange);box-shadow:0 0 0 3px #fbe5d5}.line{height:2px;width:13px;background:#9ba7af;display:inline-block}.wide-btn,.file-label{width:100%;text-align:left;display:block;margin-bottom:8px}.file-label input{display:none}.center{background:#fff;min-width:0}.view-head{height:80px;padding:20px 24px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between}.eyebrow{font:9px 'DM Mono';color:var(--teal);letter-spacing:.9px}.view-head h2{margin-top:2px}.view-controls{display:flex;gap:5px}.graph-wrap{height:420px;position:relative;overflow:hidden;background:radial-gradient(#dfe5e8 1px,transparent 1px);background-size:18px 18px}#graph{width:100%;height:100%;display:block}.graph-footer{height:34px;border-top:1px solid var(--line);padding:9px 20px;display:flex;justify-content:space-between;color:#8a959e;font:9px 'DM Mono'}.empty-state{position:absolute;inset:0;display:none;align-items:center;justify-content:center;flex-direction:column;color:var(--muted);gap:5px}.empty-state strong{color:#42525e}.tooltip{position:absolute;z-index:4;background:#17212b;color:white;padding:9px 11px;border-radius:7px;font-size:10px;box-shadow:var(--shadow);pointer-events:none;max-width:210px}.tooltip b{display:block;margin-bottom:3px}.inspector-empty{text-align:center;margin-top:115px;color:var(--muted)}.empty-icon{font-size:34px;color:#c9d0d5}.inspector-empty h2{margin:10px}.inspector-empty p{font-size:11px;line-height:1.7}.inspect-title{font-size:15px;margin:5px 0 17px}.inspect-meta{font:10px 'DM Mono';color:var(--muted);margin:7px 0}.inspect-meta b{color:#40505c}.inspect-actions{display:flex;gap:7px;margin-top:21px}.inspect-actions button{flex:1}.flow{padding:25px 28px 33px;background:#f8f9fa}.flow-head{display:flex;justify-content:space-between;align-items:end;margin-bottom:17px}.flow h2{font-size:16px;margin:3px 0}.flow p{margin:0;color:var(--muted);font-size:11px}.event-flow{display:flex;gap:12px;overflow-x:auto;padding:3px 1px 8px}.event-card{min-width:214px;max-width:250px;background:#fff;border:1px solid var(--line);border-left:4px solid var(--orange);border-radius:8px;padding:13px;cursor:pointer;box-shadow:0 3px 8px rgba(30,43,52,.03)}.event-card:hover,.event-card.selected{border-color:#efb178;box-shadow:var(--shadow)}.event-card .event-ref{font:9px 'DM Mono';color:var(--orange)}.event-card h3{margin:7px 0 5px;font-size:12px}.event-card p{font-size:10px;line-height:1.45}.modal-backdrop{position:fixed;inset:0;background:rgba(15,27,36,.42);z-index:10;display:grid;place-items:center}.modal{width:min(440px,calc(100vw - 32px));background:#fff;border-radius:12px;padding:22px;box-shadow:0 20px 60px rgba(0,0,0,.2)}.modal h2{font-size:16px;margin-bottom:17px}.field{display:block;margin:11px 0}.field span{display:block;font-size:10px;font-weight:800;margin-bottom:5px;color:#53616c}.field input,.field select,.field textarea{width:100%;border:1px solid #dce1e5;border-radius:6px;padding:9px;font:12px Manrope;color:var(--ink)}.field textarea{resize:vertical;min-height:65px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:19px}.danger{color:#c8465f!important}@media(max-width:900px){.workspace{grid-template-columns:190px 1fr}.inspector{display:none}}@media(max-width:620px){.topbar{padding:0 14px}.header-actions .secondary,.save-status{display:none}.workspace{display:block}.sidebar{display:none}.graph-wrap{height:380px}.flow{padding:20px}.graph-footer span:last-child{display:none}}
</style>
</head>
<body>
<div id="app" class="shell"><header class="topbar"><div class="brand"><div class="gitmark">⌘</div><div><h1>Git Structure</h1><p>Topology &amp; contextual markers</p></div></div><div class="header-actions"><span id="save-status" class="save-status">● Saved locally</span><button id="import-btn" class="btn secondary">Import JSON</button><button id="export-btn" class="btn secondary">Export</button><button id="reset-btn" class="icon-btn" title="Reset workspace">↻</button></div></header><main class="workspace"><aside class="sidebar"><section><div class="section-head"><h2>Repository</h2><button id="add-branch" class="tiny-btn">+ Branch</button></div><div id="branch-list" class="branch-list"></div></section><section class="legend"><h2>Graph guide</h2><div><i class="dot commit-dot"></i> Commit</div><div><i class="dot event-dot"></i> Context event</div><div><i class="line"></i> Parent relationship</div></section><section class="data-tools"><h2>Workspace</h2><button id="load-sample" class="wide-btn">Load sample topology</button><label class="file-label" for="import-file">Choose JSON file<input id="import-file" type="file" accept="application/json"></label></section></aside><section class="center"><div class="view-head"><div><span class="eyebrow">PRIMARY VIEW</span><h2>Commit topology</h2></div><div class="view-controls"><button id="add-commit" class="btn secondary">+ Commit</button><button id="zoom-out" class="icon-btn">−</button><button id="zoom-reset" class="icon-btn">⊙</button><button id="zoom-in" class="icon-btn">+</button></div></div><div id="graph-wrap" class="graph-wrap"><svg id="graph" aria-label="Interactive git commit graph"></svg><div id="empty-state" class="empty-state"><strong>Build your history</strong><span>Add a root commit to start mapping your repository.</span></div><div id="tooltip" class="tooltip" hidden=""></div></div><div class="graph-footer"><span id="commit-count">0 commits</span><span>Scroll to zoom · Drag canvas to pan · Select a node to inspect</span></div></section><aside class="inspector"><div id="inspector-empty" class="inspector-empty"><div class="empty-icon">◎</div><h2>Inspector</h2><p>Select a commit or event marker to view its structural context.</p></div><div id="inspector-content" hidden=""></div></aside></main><section class="flow"><div class="flow-head"><div><span class="eyebrow">SECONDARY VIEW</span><h2>Contextual flow</h2><p>External milestones anchored directly in development history.</p></div><button id="add-event" class="btn primary">+ Add context event</button></div><div id="event-flow" class="event-flow"></div></section><div id="modal-root"></div></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const KEY = 'git-structure-visualizer-v1';
const colors = ['#3182ce', '#1e9b8a', '#d95878', '#8b6fc7', '#e4853b'];
const sample = () => ({ commits: [{ id: 'a1b2c3d', message: 'Initialize repository', parents: [], branch: 'main' }, { id: 'd4e5f6a', message: 'Set up application shell', parents: ['a1b2c3d'], branch: 'main' }, { id: '98b7c6d', message: 'Add authentication flow', parents: ['d4e5f6a'], branch: 'feature/auth' }, { id: 'e8f9a0b', message: 'Refine login validation', parents: ['98b7c6d'], branch: 'feature/auth' }, { id: '1c2d3e4', message: 'Create dashboard layout', parents: ['d4e5f6a'], branch: 'feature/dashboard' }, { id: '5f6a7b8', message: 'Merge auth feature', parents: ['d4e5f6a', 'e8f9a0b'], branch: 'main', tag: 'v0.8.0' }, { id: '9c0d1e2', message: 'Merge dashboard feature', parents: ['5f6a7b8', '1c2d3e4'], branch: 'main', tag: 'v1.0.0' }], branches: [{ name: 'main', head: '9c0d1e2', color: '#3182ce' }, { name: 'feature/auth', head: 'e8f9a0b', color: '#d95878' }, { name: 'feature/dashboard', head: '1c2d3e4', color: '#1e9b8a' }], events: [{ id: 'evt-1', title: 'Architecture review', description: 'Core application shell approved for feature work.', ref: 'ADR-004', associationId: 'd4e5f6a' }, { id: 'evt-2', title: 'Feature completion', description: 'Authentication experience signed off by product.', ref: 'MILESTONE', associationId: 'e8f9a0b' }, { id: 'evt-3', title: 'Code freeze', description: 'Release candidate frozen for quality validation.', ref: 'RELEASE-1.0', associationId: '9c0d1e2' }] });
let state;
try {
    state = JSON.parse(localStorage.getItem(KEY) || 'null') || sample();
}
catch {
    state = sample();
}
let selected = null;
let scale = 1;
const $ = (s) => document.querySelector(s);
const esc = (v) => v.replace(/[&<>"']/g, m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[m]));
function save() { localStorage.setItem(KEY, JSON.stringify(state)); $('#save-status').textContent = '● Saved locally'; setTimeout(() => $('#save-status').textContent = '● Saved locally', 400); }
function layout() { const byId = new Map(state.commits.map(c => [c.id, c])); const depth = (c) => c.parents.length ? Math.max(...c.parents.map(p => depth(byId.get(p)))) + 1 : 0; const lanes = [...new Set(state.commits.map(c => c.branch))]; state.commits.forEach(c => { c.x = 86 + depth(c) * 145; c.y = 80 + lanes.indexOf(c.branch) * 100; }); }
function renderBranches() { const el = $('#branch-list'); el.innerHTML = state.branches.map(b => `<div class="branch" data-branch="${esc(b.name)}"><div class="branch-left"><i class="branch-swatch" style="background:${b.color}"></i>${esc(b.name)}</div><div class="branch-actions"><button data-edit-branch="${esc(b.name)}">✎</button><button data-delete-branch="${esc(b.name)}">×</button></div></div>`).join('') || '<small>No branches yet</small>'; el.querySelectorAll('[data-edit-branch]').forEach(x => x.addEventListener('click', e => { e.stopPropagation(); openModal('branch', x.dataset.editBranch); })); el.querySelectorAll('[data-delete-branch]').forEach(x => x.addEventListener('click', e => { e.stopPropagation(); remove('branch', x.dataset.deleteBranch); })); }
function renderGraph() { layout(); const svg = $('#graph'); const empty = $('#empty-state'); empty.style.display = state.commits.length ? 'none' : 'flex'; const maxX = Math.max(500, ...state.commits.map(c => c.x + 100)); svg.setAttribute('viewBox', `0 0 ${maxX} 420`); const eventsBy = new Map(); state.events.forEach(e => eventsBy.set(e.associationId, [...(eventsBy.get(e.associationId) || []), e])); const links = state.commits.flatMap(c => c.parents.map(p => { const q = state.commits.find(x => x.id === p); return q ? `<path d="M ${q.x} ${q.y} C ${q.x + 55} ${q.y}, ${c.x - 55} ${c.y}, ${c.x} ${c.y}" fill="none" stroke="#9eabb4" stroke-width="2"/>` : ''; })).join(''); const nodes = state.commits.map(c => { const ev = eventsBy.get(c.id) || []; const active = selected?.id === c.id || ev.some(e => selected?.id === e.id); return `<g class="commit-node" data-commit="${c.id}" style="cursor:pointer"><circle cx="${c.x}" cy="${c.y}" r="${active ? 15 : 12}" fill="#fff" stroke="${active ? '#183a52' : '#3182ce'}" stroke-width="3"/><circle cx="${c.x}" cy="${c.y}" r="5" fill="#3182ce"/><text x="${c.x + 19}" y="${c.y - 5}" font-family="DM Mono" font-size="10" fill="#273843">${esc(c.id)}</text><text x="${c.x + 19}" y="${c.y + 11}" font-family="Manrope" font-size="10" fill="#74808c">${esc(c.message.slice(0, 24))}</text>${c.tag ? `<rect x="${c.x - 2}" y="${c.y - 41}" width="${c.tag.length * 7 + 16}" height="18" rx="4" fill="#e7f4f2"/><text x="${c.x + 6}" y="${c.y - 29}" font-family="DM Mono" font-size="9" fill="#167467">${esc(c.tag)}</text>` : ''}${ev.map((e, i) => `<g class="event-anchor" data-event="${e.id}" style="cursor:pointer"><line x1="${c.x}" y1="${c.y - 12}" x2="${c.x}" y2="${c.y - 31 - i * 23}" stroke="#e4853b" stroke-width="2"/><circle cx="${c.x}" cy="${c.y - 37 - i * 23}" r="8" fill="#e4853b" stroke="white" stroke-width="3"/><text x="${c.x + 12}" y="${c.y - 34 - i * 23}" font-family="DM Mono" font-size="9" fill="#c86b24">${esc(e.ref)}</text></g>`).join('')}</g>`; }).join(''); svg.innerHTML = `<g transform="scale(${scale})">${links}${nodes}</g>`; $('#commit-count').textContent = `${state.commits.length} commit${state.commits.length === 1 ? '' : 's'}`; svg.querySelectorAll('[data-commit]').forEach(n => n.addEventListener('click', () => select('commit', n.dataset.commit))); svg.querySelectorAll('[data-event]').forEach(n => { n.addEventListener('click', e => { e.stopPropagation(); select('event', n.dataset.event); }); n.addEventListener('mouseenter', e => showTip(e, n)); n.addEventListener('mouseleave', hideTip); }); }
function renderEvents() { const el = $('#event-flow'); el.innerHTML = state.events.map(e => `<article class="event-card ${selected?.id === e.id ? 'selected' : ''}" data-event-card="${e.id}"><div class="event-ref">${esc(e.ref)} · ${esc(e.associationId)}</div><h3>${esc(e.title)}</h3><p>${esc(e.description || 'No description')}</p></article>`).join('') || '<div class="event-card"><h3>No contextual events</h3><p>Add an event to connect external milestones to your code history.</p></div>'; el.querySelectorAll('[data-event-card]').forEach(x => x.addEventListener('click', () => select('event', x.dataset.eventCard))); }
function select(type, id) { selected = { type, id }; renderGraph(); renderEvents(); const c = type === 'commit' ? state.commits.find(x => x.id === id) : state.commits.find(x => x.id === state.events.find(e => e.id === id)?.associationId); if (!c)
    return; $('#inspector-empty').hidden = true; const box = $('#inspector-content'); box.hidden = false; const ev = state.events.filter(e => e.associationId === c.id); box.innerHTML = `<span class="eyebrow">${type === 'event' ? 'CONTEXT EVENT' : 'COMMIT'}</span><h2 class="inspect-title">${esc(type === 'event' ? state.events.find(e => e.id === id).title : c.message)}</h2><div class="inspect-meta"><b>COMMIT ID</b><br>${esc(c.id)}</div><div class="inspect-meta"><b>BRANCH</b><br>${esc(c.branch)}</div><div class="inspect-meta"><b>PARENTS</b><br>${c.parents.length ? c.parents.join(', ') : 'root commit'}</div>${ev.length ? `<div class="inspect-meta"><b>ANCHORED EVENTS</b><br>${ev.map(e => esc(e.title)).join('<br>')}</div>` : ''}<div class="inspect-actions"><button class="btn secondary" id="edit-selected">Edit</button><button class="btn secondary danger" id="delete-selected">Delete</button></div>`; $('#edit-selected').onclick = () => openModal(type === 'event' ? 'event' : 'commit', type === 'event' ? id : c.id); $('#delete-selected').onclick = () => remove(type === 'event' ? 'event' : 'commit', type === 'event' ? id : c.id); }
function showTip(e, el) { const item = state.events.find(x => x.id === el.dataset.event); const t = $('#tooltip'); t.hidden = false; t.innerHTML = `<b>${esc(item.title)}</b>${esc(item.ref)} · linked to ${esc(item.associationId)}`; const r = e; t.style.left = r.offsetX + 15 + 'px'; t.style.top = r.offsetY + 15 + 'px'; }
function hideTip() { $('#tooltip').hidden = true; }
function modal(title, body, onSave) { $('#modal-root').innerHTML = `<div class="modal-backdrop"><form class="modal"><h2>${title}</h2>${body}<div class="modal-actions"><button type="button" class="btn secondary" id="cancel">Cancel</button><button class="btn primary">Save</button></div></form></div>`; $('#cancel').onclick = () => $('#modal-root').innerHTML = ''; $('#modal-root form').onsubmit = e => { e.preventDefault(); onSave(); $('#modal-root').innerHTML = ''; save(); render(); }; }
function openModal(kind, id) { if (kind === 'branch') {
    const old = state.branches.find(b => b.name === id);
    modal(old ? 'Edit branch' : 'Create branch', `<label class="field"><span>BRANCH NAME</span><input id="f-name" required value="${old ? esc(old.name) : ''}"></label><label class="field"><span>HEAD COMMIT</span><select id="f-head">${state.commits.map(c => `<option ${old?.head === c.id ? 'selected' : ''}>${c.id}</option>`).join('')}</select></label>`, () => { const name = $('#f-name').value.trim(); if (!name)
        return; if (old) {
        const prior = old.name;
        old.name = name;
        state.commits.forEach(c => { if (c.branch === prior)
            c.branch = name; });
    }
    else
        state.branches.push({ name, head: $('#f-head').value, color: colors[state.branches.length % colors.length] }); });
    return;
} if (kind === 'commit') {
    const old = state.commits.find(c => c.id === id);
    modal(old ? 'Edit commit' : 'Add commit', `<label class="field"><span>COMMIT ID</span><input id="f-id" required value="${old ? esc(old.id) : Math.random().toString(16).slice(2, 9)}"></label><label class="field"><span>MESSAGE</span><input id="f-message" required value="${old ? esc(old.message) : ''}"></label><label class="field"><span>BRANCH</span><select id="f-branch">${state.branches.map(b => `<option ${old?.branch === b.name ? 'selected' : ''}>${esc(b.name)}</option>`).join('')}</select></label><label class="field"><span>PARENT IDS (comma separated)</span><input id="f-parents" value="${old ? old.parents.join(', ') : ''}"></label><label class="field"><span>TAG (optional)</span><input id="f-tag" value="${old?.tag || ''}"></label>`, () => { const val = (x) => document.querySelector(x).value.trim(); const item = { id: val('#f-id'), message: val('#f-message'), branch: document.querySelector('#f-branch').value, parents: val('#f-parents').split(',').map(x => x.trim()).filter(Boolean), tag: val('#f-tag') || undefined }; if (old)
        Object.assign(old, item);
    else {
        state.commits.push(item);
        const b = state.branches.find(x => x.name === item.branch);
        if (b)
            b.head = item.id;
    } });
    return;
} const old = state.events.find(e => e.id === id); modal(old ? 'Edit context event' : 'Add context event', `<label class="field"><span>TITLE</span><input id="f-title" required value="${old ? esc(old.title) : ''}"></label><label class="field"><span>REFERENCE</span><input id="f-ref" required value="${old ? esc(old.ref) : 'MILESTONE'}"></label><label class="field"><span>ASSOCIATED COMMIT</span><select id="f-assoc" required>${state.commits.map(c => `<option ${old?.associationId === c.id ? 'selected' : ''}>${c.id} — ${esc(c.message)}</option>`).join('')}</select></label><label class="field"><span>DESCRIPTION</span><textarea id="f-desc">${old ? esc(old.description) : ''}</textarea></label>`, () => { const item = { id: old?.id || 'evt-' + Date.now(), title: document.querySelector('#f-title').value.trim(), ref: document.querySelector('#f-ref').value.trim(), associationId: document.querySelector('#f-assoc').value.split(' — ')[0], description: document.querySelector('#f-desc').value.trim() }; if (old)
    Object.assign(old, item);
else
    state.events.push(item); }); }
function remove(kind, id) { if (!confirm(`Delete this ${kind}?`))
    return; if (kind === 'event')
    state.events = state.events.filter(e => e.id !== id);
else if (kind === 'commit') {
    state.commits = state.commits.filter(c => c.id !== id);
    state.commits.forEach(c => c.parents = c.parents.filter(p => p !== id));
    state.events = state.events.filter(e => e.associationId !== id);
    state.branches.forEach(b => { if (b.head === id)
        b.head = ''; });
}
else
    state.branches = state.branches.filter(b => b.name !== id); selected = null; save(); render(); }
function render() { renderBranches(); renderGraph(); renderEvents(); }
render();
$('#add-commit').onclick = () => openModal('commit');
$('#add-branch').onclick = () => openModal('branch');
$('#add-event').onclick = () => openModal('event');
$('#load-sample').onclick = () => { state = sample(); selected = null; save(); render(); };
$('#export-btn').onclick = () => { const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })); a.download = 'git-structure.json'; a.click(); URL.revokeObjectURL(a.href); };
$('#import-btn').onclick = () => $('#import-file').click();
$('#import-file').onchange = e => { const f = e.target.files?.[0]; if (!f)
    return; f.text().then(t => { try {
    const n = JSON.parse(t);
    if (!Array.isArray(n.commits) || !Array.isArray(n.branches) || !Array.isArray(n.events))
        throw Error();
    state = n;
    selected = null;
    save();
    render();
}
catch {
    alert('Invalid Git structure JSON. Expected commits, branches, and events arrays.');
} }); };
$('#reset-btn').onclick = () => { if (confirm('Reset to the sample topology?')) {
    state = sample();
    selected = null;
    save();
    render();
} };
$('#zoom-in').onclick = () => { scale = Math.min(1.5, scale + .1); renderGraph(); };
$('#zoom-out').onclick = () => { scale = Math.max(.65, scale - .1); renderGraph(); };
$('#zoom-reset').onclick = () => { scale = 1; renderGraph(); };
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-terra","provider":"openrouter","persona":"typescript","sessionId":"c075fb60-0e0d-4174-aa4e-39543b71e81a","tokensIn":231815,"tokensOut":8303,"tokensTotal":240118,"turns":18,"toolCalls":18,"failedToolCalls":1,"timestamp":"2026-07-31T18:07:30.129Z"} -->