Spreadsheet
GPT 5.6 Luna Pro · typescript

Browser check: Artifact runtime error. This is a technical check, not a quality review.
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 19,691 bytes · SHA-256 d03a436e4406
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Gridline — Spreadsheet</title>
<style>
/* ── css: base ── */
*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;color:#1f2937;background:#f8fafc;overflow:hidden}button,input{font:inherit}.app-shell{height:100vh;display:flex;flex-direction:column}.topbar{height:58px;display:flex;align-items:center;padding:0 22px;background:#fff;border-bottom:1px solid #e5e7eb;gap:26px}.brand{display:flex;align-items:center;gap:9px;font-weight:750;font-size:18px;letter-spacing:-.02em;color:#173b70;white-space:nowrap}.brand-mark{font-size:26px;color:#2463d4;line-height:1}.brand-sub{font-size:11px;font-weight:600;color:#9aa5b5;text-transform:uppercase;letter-spacing:.12em;margin-left:2px}.toolbar{display:flex;gap:7px;flex:1}.toolbar button{border:1px solid #dbe2ec;background:#fff;color:#34435a;border-radius:6px;padding:7px 11px;font-size:12px;cursor:pointer}.toolbar button:hover{border-color:#8eace0;background:#f5f8ff}.toolbar .ghost{margin-left:8px;color:#64748b}.save-state{font-size:11px;color:#7b8798;white-space:nowrap}.formula-area{height:48px;display:flex;align-items:center;gap:10px;padding:7px 22px;background:#f8fafc;border-bottom:1px solid #e5e7eb}.name-box{width:66px;height:32px;border:1px solid #cfd8e5;background:#fff;border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:650;color:#29466f}.fx{font-family:Georgia,serif;font-size:19px;font-style:italic;color:#8190a4;width:20px;text-align:center}.formula-area input{height:32px;flex:1;border:1px solid #cfd8e5;background:#fff;border-radius:5px;padding:0 11px;outline:none;color:#1f2937;font-size:13px}.formula-area input:focus{border-color:#4d83e8;box-shadow:0 0 0 2px #dce8ff}.sheet-wrap{flex:1;overflow:auto;background:#fff}.grid{display:grid;grid-template-columns:46px repeat(26,100px);grid-auto-rows:28px;min-width:2646px;min-height:2828px;position:relative}.cell,.col-head,.row-head,.corner{border-right:1px solid #e2e7ee;border-bottom:1px solid #e2e7ee;display:flex;align-items:center;overflow:hidden}.corner,.col-head,.row-head{background:#f4f6f9;color:#607087;font-size:11px;font-weight:650;justify-content:center;position:sticky;z-index:4}.corner{left:0;top:0}.col-head{top:0;height:28px}.row-head{left:0;justify-content:center}.cell{background:#fff;padding:0 8px;font-size:12px;white-space:nowrap;cursor:cell;position:relative}.cell.selected{outline:2px solid #3274dc;outline-offset:-2px;z-index:2}.cell.active{box-shadow:inset 0 0 0 1px #3274dc}.cell.editing{padding:0}.cell-input{width:100%;height:100%;border:0;outline:0;padding:0 7px;font-size:12px;background:#fff}.cell.numeric{text-align:right;justify-content:flex-end}.cell.error{color:#d14343;font-weight:650}.cell.formula{color:#344d76}.context-menu{display:none;position:fixed;z-index:20;background:#fff;border:1px solid #d8e0eb;border-radius:7px;box-shadow:0 8px 24px #1c355122;padding:5px;width:165px}.context-menu.open{display:flex;flex-direction:column}.context-menu button{border:0;background:transparent;text-align:left;padding:8px 9px;font-size:12px;color:#35445a;border-radius:4px;cursor:pointer}.context-menu button:hover{background:#edf3ff;color:#1f5dbe}.statusbar{height:30px;border-top:1px solid #e5e7eb;background:#fff;display:flex;align-items:center;padding:0 22px;font-size:11px;color:#64748b;gap:24px}.status-hint{flex:1;color:#9aa5b5}@media(max-width:700px){.topbar{padding:0 10px;gap:10px}.brand-sub,.save-state,.toolbar button:nth-child(n+3){display:none}.formula-area{padding:7px 10px}.statusbar{padding:0 10px}.status-hint{display:none}}
/* ── css: selection ── */
.cell:focus{outline:none}.cell.flash{background:#edf5ff}.cell[title]{cursor:cell}
</style>
</head>
<body>
<div class="app-shell"><header class="topbar"><div class="brand"><span class="brand-mark">▦</span><span>Gridline</span><span class="brand-sub">Spreadsheet</span></div><div class="toolbar"><button id="add-row">+ Row</button><button id="del-row">- Row</button><button id="add-col">+ Column</button><button id="del-col">- Column</button><button id="clear-sheet" class="ghost">Clear sheet</button></div><div id="save-state" class="save-state">Saved locally</div></header><section class="formula-area"><div id="name-box" class="name-box">A1</div><div class="fx">ƒx</div><input id="formula-bar" aria-label="Formula bar" spellcheck="false" placeholder="Enter value or formula"></section><main class="sheet-wrap"><div id="grid" class="grid" role="grid"></div></main><footer class="statusbar"><span id="status-selection">A1</span><span class="status-hint">Enter to commit · Tab across · Arrow keys navigate · Double-click to edit</span><span id="status-count">26 columns × 100 rows</span></footer><div id="context-menu" class="context-menu"><button data-action="insert-row">Insert row above</button><button data-action="delete-row">Delete row</button><button data-action="insert-col">Insert column left</button><button data-action="delete-col">Delete column</button></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 });
exports.start = start;
// Gridline spreadsheet: state, parser, recalculation, rendering, and interaction.
const ROWS = 100, COLS = 26, KEY = 'gridline-sheet-v1';
const cells = Array.from({ length: ROWS }, () => Array.from({ length: COLS }, () => ({ raw: '', value: '' })));
let selected = { r: 0, c: 0 };
let editing = false;
let editInput = null;
const grid = document.querySelector('#grid');
const bar = document.querySelector('#formula-bar');
const nameBox = document.querySelector('#name-box');
const menu = document.querySelector('#context-menu');
const colName = (c) => String.fromCharCode(65 + c);
const addr = (r, c) => colName(c) + (r + 1);
function parseAddr(s) { const m = /^([A-Z]+)(\d+)$/.exec(s.toUpperCase()); if (!m)
return null; let c = 0; for (const ch of m[1])
c = c * 26 + ch.charCodeAt(0) - 64; c--; const r = +m[2] - 1; return r >= 0 && r < ROWS && c >= 0 && c < COLS ? { r, c } : null; }
function tokenize(s) { const out = []; let i = 0; while (i < s.length) {
const ch = s[i];
if (/\s/.test(ch)) {
i++;
continue;
}
if (/[0-9.]/.test(ch)) {
let j = i + 1;
while (j < s.length && /[0-9.]/.test(s[j]))
j++;
out.push({ kind: 'num', text: s.slice(i, j) });
i = j;
continue;
}
if (/[A-Za-z]/.test(ch)) {
let j = i + 1;
while (j < s.length && /[A-Za-z0-9]/.test(s[j]))
j++;
const t = s.slice(i, j).toUpperCase();
out.push({ kind: /^[A-Z]+\d+$/.test(t) ? 'ref' : 'word', text: t });
i = j;
continue;
}
if ('+-*/'.includes(ch))
out.push({ kind: 'op', text: ch });
else if (ch === '(')
out.push({ kind: 'lpar', text: ch });
else if (ch === ')')
out.push({ kind: 'rpar', text: ch });
else if (ch === ',')
out.push({ kind: 'comma', text: ch });
else if (ch === ':')
out.push({ kind: 'colon', text: ch });
else
throw Error('token');
i++;
} return out; }
class Parser {
get cur() { return this.t[this.i]; }
;
constructor(s) {
this.i = 0;
this.t = tokenize(s.slice(1));
}
eat(k, x) { const z = this.cur; if (!z || (k && z.kind !== k) || (x && z.text !== x))
throw Error('parse'); this.i++; return z; }
expr() { let v = this.term(); while (this.cur?.kind === 'op' && /[+-]/.test(this.cur.text)) {
const o = this.eat('op').text, n = this.term();
v = o === '+' ? v + n : v - n;
} return v; }
term() { let v = this.factor(); while (this.cur?.kind === 'op' && /[*/]/.test(this.cur.text)) {
const o = this.eat('op').text, n = this.factor();
if (o === '/' && n === 0)
throw Error('#DIV/0!');
v = o === '*' ? v * n : v / n;
} return v; }
factor() { if (this.cur?.kind === 'op' && this.cur.text === '-') {
this.eat();
return -this.factor();
} if (this.cur?.kind === 'num')
return +this.eat().text; if (this.cur?.kind === 'lpar') {
this.eat();
const v = this.expr();
this.eat('rpar');
return v;
} if (this.cur?.kind === 'ref') {
const ref = this.eat('ref').text;
const a = parseAddr(ref);
if (!a)
throw Error('#REF!');
let list = [a];
if (this.cur?.kind === 'colon') {
this.eat();
const b = parseAddr(this.eat('ref').text);
if (!b)
throw Error('#REF!');
list = [];
for (let r = Math.min(a.r, b.r); r <= Math.max(a.r, b.r); r++)
for (let c = Math.min(a.c, b.c); c <= Math.max(a.c, b.c); c++)
list.push({ r, c });
}
if (list.length > 1)
return list.reduce((s, p) => s + num(cells[p.r][p.c]), 0);
return num(cells[a.r][a.c]);
} if (this.cur?.kind === 'word') {
const fn = this.eat('word').text;
this.eat('lpar');
const vals = [];
while (this.cur && this.cur.kind !== 'rpar') {
if (this.cur.kind === 'ref') {
const a = parseAddr(this.eat('ref').text);
if (!a)
throw Error('#REF!');
if (this.cur?.kind === 'colon') {
this.eat();
const b = parseAddr(this.eat('ref').text);
if (!b)
throw Error('#REF!');
for (let r = Math.min(a.r, b.r); r <= Math.max(a.r, b.r); r++)
for (let c = Math.min(a.c, b.c); c <= Math.max(a.c, b.c); c++)
vals.push(num(cells[r][c]));
}
else
vals.push(num(cells[a.r][a.c]));
}
else
vals.push(this.expr());
if (this.cur?.kind === 'comma')
this.eat('comma');
else
break;
}
this.eat('rpar');
if (!['SUM', 'AVG', 'AVERAGE', 'MIN', 'MAX', 'COUNT'].includes(fn))
throw Error('#ERR!');
if (fn === 'SUM')
return vals.reduce((a, b) => a + b, 0);
if (fn === 'COUNT')
return vals.filter(v => !Number.isNaN(v)).length;
if (fn === 'MIN')
return vals.length ? Math.min(...vals) : 0;
if (fn === 'MAX')
return vals.length ? Math.max(...vals) : 0;
return vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
} throw Error('parse'); }
}
function num(c) { if (c.error)
throw Error(c.error); const n = typeof c.value === 'number' ? c.value : Number(c.value); return Number.isFinite(n) ? n : 0; }
function refs(raw) { const a = []; const re = /\b([A-Z]+\d+)(?::([A-Z]+\d+))?\b/gi; let m; while ((m = re.exec(raw.slice(1)))) {
const x = parseAddr(m[1]);
const y = m[2] && parseAddr(m[2]);
if (x && y)
for (let r = Math.min(x.r, y.r); r <= Math.max(x.r, y.r); r++)
for (let c = Math.min(x.c, y.c); c <= Math.max(x.c, y.c); c++)
a.push(addr(r, c));
else
a.push(m[1].toUpperCase());
} return a; }
function recalc() {
for (const row of cells)
for (const c of row) {
c.error = undefined;
c.value = c.raw.startsWith('=') ? '' : c.raw;
}
const visiting = new Set(), done = new Set();
function calc(r, c) { const key = addr(r, c), cell = cells[r][c]; if (done.has(key))
return; if (visiting.has(key)) {
cell.error = '#CYCLE!';
cell.value = '';
return;
} if (!cell.raw.startsWith('=')) {
done.add(key);
return;
} visiting.add(key); try {
for (const x of refs(cell.raw)) {
const p = parseAddr(x);
if (!p)
throw Error('#REF!');
calc(p.r, p.c);
if (cells[p.r][p.c].error)
throw Error(cells[p.r][p.c].error);
}
cell.value = new Parser(cell.raw).expr();
if (!Number.isFinite(cell.value))
cell.error = '#ERR!';
}
catch (e) {
cell.error = e instanceof Error && e.message.startsWith('#') ? e.message : '#ERR!';
cell.value = '';
} visiting.delete(key); done.add(key); }
for (let r = 0; r < ROWS; r++)
for (let c = 0; c < COLS; c++)
calc(r, c);
render();
save();
}
function display(c) { return c.error || c.value; }
function render() { grid.innerHTML = ''; const corner = document.createElement('div'); corner.className = 'corner'; corner.textContent = ''; grid.append(corner); for (let c = 0; c < COLS; c++) {
const h = document.createElement('div');
h.className = 'col-head';
h.textContent = colName(c);
h.dataset.c = String(c);
h.oncontextmenu = e => openMenu(e, c, 'col');
grid.append(h);
} for (let r = 0; r < ROWS; r++) {
const h = document.createElement('div');
h.className = 'row-head';
h.textContent = String(r + 1);
h.dataset.r = String(r);
h.oncontextmenu = e => openMenu(e, r, 'row');
grid.append(h);
for (let c = 0; c < COLS; c++) {
const el = document.createElement('div');
const cell = cells[r][c];
el.className = 'cell' + (r === selected.r && c === selected.c ? ' selected active ' : '') + (cell.error ? ' error ' : '') + (typeof cell.value === 'number' ? ' numeric ' : '') + (cell.raw.startsWith('=') ? ' formula' : '');
el.textContent = String(display(cell));
el.title = cell.raw;
el.dataset.r = String(r);
el.dataset.c = String(c);
el.onclick = () => select(r, c);
el.ondblclick = () => beginEdit();
grid.append(el);
}
} nameBox.textContent = addr(selected.r, selected.c); bar.value = cells[selected.r][selected.c].raw; document.querySelector('#status-selection').textContent = addr(selected.r, selected.c); }
function select(r, c) { if (editing)
commit(); selected = { r: Math.max(0, Math.min(ROWS - 1, r)), c: Math.max(0, Math.min(COLS - 1, c)) }; render(); document.querySelector('.sheet-wrap').scrollIntoView({ block: 'nearest' }); }
function beginEdit() { if (editing)
return; editing = true; const el = grid.querySelector(`.cell[data-r="${selected.r}"][data-c="${selected.c}"]`); el.innerHTML = ''; el.classList.add('editing'); editInput = document.createElement('input'); editInput.className = 'cell-input'; editInput.value = cells[selected.r][selected.c].raw; el.append(editInput); editInput.focus(); editInput.select(); editInput.onkeydown = e => { if (e.key === 'Enter' || e.key === 'Tab') {
e.preventDefault();
const move = e.key === 'Enter' ? { r: 1, c: 0 } : { r: 0, c: 1 };
commit();
select(selected.r + move.r, selected.c + move.c);
} if (e.key === 'Escape') {
editing = false;
render();
} }; }
function commit(raw) { if (!editing && raw === undefined)
return; const v = raw ?? editInput?.value ?? bar.value; cells[selected.r][selected.c].raw = v; editing = false; editInput = null; recalc(); }
bar.onfocus = () => { if (!editing)
bar.select(); };
bar.onkeydown = e => { if (e.key === 'Enter') {
e.preventDefault();
commit(bar.value);
select(selected.r + 1, selected.c);
} };
bar.oninput = () => { if (!editing)
cells[selected.r][selected.c].raw = bar.value; };
function move(dr, dc) { select(selected.r + dr, selected.c + dc); }
document.addEventListener('keydown', e => { if (editing)
return; if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
e.preventDefault();
move(e.key === 'ArrowUp' ? -1 : e.key === 'ArrowDown' ? 1 : 0, e.key === 'ArrowLeft' ? -1 : e.key === 'ArrowRight' ? 1 : 0);
}
else if (e.key === 'Enter') {
beginEdit();
}
else if (e.key === 'Delete' || e.key === 'Backspace') {
cells[selected.r][selected.c].raw = '';
recalc();
}
else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
beginEdit();
if (editInput) {
editInput.value = e.key;
}
} });
function save() { localStorage.setItem(KEY, JSON.stringify(cells.map(row => row.map(c => c.raw)))); document.querySelector('#save-state').textContent = 'Saved locally'; }
function load() { try {
const data = JSON.parse(localStorage.getItem(KEY) || '[]');
data.forEach((row, r) => row?.forEach((v, c) => { if (cells[r]?.[c])
cells[r][c].raw = v || ''; }));
}
catch { } recalc(); }
function rewrite(axis, at, delta) { for (const row of cells)
for (const cell of row)
if (cell.raw.startsWith('=')) {
cell.raw = cell.raw.replace(/\b([A-Z]+)(\d+)\b/gi, (_, letters, n) => { let a = parseAddr(letters + n); let ix = axis === 'row' ? a.r : a.c; if (delta < 0 && ix === at)
return '#REF!'; if (ix >= at)
ix += delta; return axis === 'row' ? letters + (ix + 1) : colName(ix); });
} }
function op(axis, delta, index) { if (axis === 'row' && delta > 0) {
cells.splice(index, 0, Array.from({ length: COLS }, () => ({ raw: '', value: '' })));
cells.pop();
} if (axis === 'row' && delta < 0) {
cells.splice(index, 1);
cells.push(Array.from({ length: COLS }, () => ({ raw: '', value: '' })));
} if (axis === 'col' && delta > 0)
for (const row of cells) {
row.splice(index, 0, { raw: '', value: '' });
row.pop();
} if (axis === 'col' && delta < 0)
for (const row of cells) {
row.splice(index, 1);
row.push({ raw: '', value: '' });
} rewrite(axis, index, delta); recalc(); }
function openMenu(e, index, axis) { e.preventDefault(); menu.style.left = e.clientX + 'px'; menu.style.top = e.clientY + 'px'; menu.classList.add('open'); menu.dataset.index = String(index); menu.dataset.axis = axis; }
menu.onclick = e => { const a = e.target.dataset.action; if (!a)
return; const axis = menu.dataset.axis, i = +(menu.dataset.index || 0); op(axis, a.startsWith('insert') ? 1 : -1, i); menu.classList.remove('open'); };
document.addEventListener('click', () => menu.classList.remove('open'));
document.querySelector('#add-row').onclick = () => op('row', 1, selected.r);
document.querySelector('#del-row').onclick = () => op('row', -1, selected.r);
document.querySelector('#add-col').onclick = () => op('col', 1, selected.c);
document.querySelector('#del-col').onclick = () => op('col', -1, selected.c);
document.querySelector('#clear-sheet').onclick = () => { for (const row of cells)
for (const c of row)
c.raw = ''; recalc(); };
load();
function start() { }
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-5.6-luna-pro","provider":"openrouter","persona":"typescript","sessionId":"def65c61-2177-41e5-84ad-36fe45e5bd08","tokensIn":785708,"tokensOut":28752,"tokensTotal":814460,"turns":21,"toolCalls":25,"failedToolCalls":2,"timestamp":"2026-07-31T02:44:45.725Z"} -->