Spreadsheet
Gemma 4 12b · 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 23,792 bytes · SHA-256 ba0eacb14df1
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SheetCalc - Spreadsheet Engine</title>
<style>
/* ── css: layout ── */
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: #f8f9fa;
}
#toolbar {
padding: 10px;
background: #fff;
border-bottom: 1px solid #ddd;
display: flex;
gap: 10px;
}
button {
padding: 6px 12px;
cursor: pointer;
background: #fff;
border: 1px solid #ccc;
border-radius: 4px;
}
button:hover {
background: #f0f0f0;
}
#formula-bar-container {
padding: 8px;
background: #fff;
border-bottom: 1px solid #ddd;
display: flex;
align-items: center;
gap: 10px;
}
#cell-ref-display {
min-width: 40px;
font-weight: bold;
color: #666;
}
#formula-input {
flex-grow: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
}
#grid-container {
overflow: auto;
height: calc(100vh - 120px);
}
table {
border-collapse: collapse;
table-layout: fixed;
background: #fff;
}
th, td {
border: 1px solid #ddd;
padding: 0;
height: 30px;
position: relative;
font-size: 14px;
overflow: hidden;
white-space: nowrap;
}
th {
background: #f1f3f4;
text-align: center;
color: #666;
position: sticky;
top: 0;
z-index: 2;
}
td[data-col] {
position: sticky;
left: 0;
z-index: 1;
background: #f1f3f4;
}
#header-row td[data-col] {
z-index: 3;
}
.cell-selected {
outline: 2px solid #1a73e8 !important;
outline-offset: -2px;
z-index: 4;
}
.cell-editing {
padding: 0 !important;
}
.cell-editing input {
width: 100%;
height: 100%;
border: none;
outline: none;
padding: 0 4px;
font-size: 14px;
}
.val-num {
text-align: right;
padding-right: 8px;
}
.val-err {
color: #d93025;
font-weight: bold;
}
#spreadsheet-body tr td {
height: 30px;
padding: 4px;
text-align: left;
}
</style>
</head>
<body>
<div id="toolbar">
<button id="btn-insert-row">Insert Row</button>
<button id="btn-delete-row">Delete Row</button>
<button id="btn-insert-col">Insert Column</button>
<button id="btn-delete-col">Delete Column</button>
<button id="btn-save">Save to LocalStorage</button>
</div>
<div id="formula-bar-container">
<div id="cell-ref-display">A1</div>
<input type="text" id="formula-input" placeholder="Enter value or formula (e.g., =SUM(A1:A10))">
</div>
<div id="grid-container">
<table id="spreadsheet">
<thead>
<tr id="header-row"></tr>
</thead>
<tbody id="spreadsheet-body"></tbody>
</table>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./engine":"src/engine.ts"}};
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 engine_1 = require("./engine");
const CONFIG = {
ROWS: 100,
COLS: 26,
};
// DOM Elements
const spreadsheetBody = document.getElementById('spreadsheet-body');
const headerRow = document.getElementById('header-row');
const formulaInput = document.getElementById('formula-input');
const cellRefDisplay = document.getElementById('cell-ref-display');
const toolbarBtnSave = document.getElementById('btn-save');
const toolbarBtnInsertRow = document.getElementById('btn-insert-row');
const toolbarBtnDeleteRow = document.getElementById('btn-delete-row');
let currentEditingCellId = null;
function renderGrid() {
headerRow.innerHTML = '';
spreadsheetBody.innerHTML = '';
// Add empty top-left corner
const cornerHeader = document.createElement('th');
headerRow.appendChild(cornerHeader);
for (let c = 0; c < CONFIG.COLS; c++) {
const th = document.createElement('th');
th.textContent = String.fromCharCode(65 + c);
headerRow.appendChild(th);
}
for (let r = 0; r < CONFIG.ROWS; r++) {
const tr = document.createElement('tr');
// Row header
const rowHeader = document.createElement('td');
rowHeader.textContent = `${r + 1}`;
rowHeader.style.background = '#f1f3f4';
rowHeader.style.fontWeight = 'bold';
tr.appendChild(rowHeader);
for (let c = 0; c < CONFIG.COLS; c++) {
const id = (0, engine_1.getCellId)(r, c);
const td = document.createElement('td');
td.id = `cell-${id}`;
td.setAttribute('data-col', c);
tr.appendChild(td);
}
spreadsheetBody.appendChild(tr);
}
// Initial populate
const cells = (0, engine_1.getCells)();
cells.forEach((data, id) => {
const td = document.getElementById(`cell-${id}`);
if (td)
renderCellContent(td, data);
});
}
function renderCellContent(td, data) {
td.innerHTML = '';
td.className = '';
const span = document.createElement('span');
if (data.error) {
span.className = 'val-err';
span.textContent = data.value;
}
else if (typeof data.value === 'number') {
span.className = 'val-num';
span.textContent = data.value.toString();
}
else {
span.textContent = data.value || '';
}
td.appendChild(span);
}
function updateAllCells() {
const cells = (0, engine_1.getCells)();
cells.forEach((data, id) => {
const td = document.getElementById(`cell-${id}`);
if (td)
renderCellContent(td, data);
});
}
function handleCellSelect(id) {
const match = id.match(/([A-Z])(\d+)/);
if (!match)
return;
const colStr = match[1];
const rowStr = parseInt(match[2], 10);
// Using direct numbers to bypass compiler's inference issues with cast expressions
(0, engine_1.setSelection)(rowStr - 1, colStr.charCodeAt(0) - 65);
document.querySelectorAll('td').forEach(td => td.classList.remove('cell-selected'));
const targetTd = document.getElementById(`cell-${id}`);
if (targetTd) {
targetTd.classList.add('cell-selected');
const data = (0, engine_1.getCells)().get(id);
formulaInput.value = data?.raw || '';
cellRefDisplay.textContent = id;
}
}
function handleCellDoubleClick(id) {
const td = document.getElementById(`cell-${id}`);
if (!td)
return;
const existingInput = td.querySelector('input');
if (existingInput)
existingInput.remove();
const newInput = document.createElement('input');
newInput.type = 'text';
newInput.className = 'cell-editing input';
td.appendChild(newInput);
newInput.focus();
currentEditingCellId = id;
formulaInput.value = (0, engine_1.getCells)().get(id)?.raw || '';
newInput.addEventListener('input', (e) => {
const val = e.target.value;
(0, engine_1.updateCell)(id, val);
renderCellContent(td, (0, engine_1.getCells)().get(id));
formulaInput.value = val;
});
newInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === 'Tab') {
e.preventDefault();
const val = newInput.value;
(0, engine_1.updateCell)(id, val);
renderCellContent(td, (0, engine_1.getCells)().get(id));
currentEditingCellId = null;
newInput.remove();
const s = (0, engine_1.getSelection)();
if (s) {
if (e.key === 'Enter') {
(0, engine_1.setSelection)(Math.min(s.r + 1, CONFIG.ROWS - 1), s.c);
}
else {
(0, engine_1.setSelection)(s.r, Math.min(s.c + 1, CONFIG.COLS - 1));
}
const nextId = (0, engine_1.getCellId)((0, engine_1.getSelection)().r, (0, engine_1.getSelection)().c);
handleCellSelect(nextId);
}
}
});
}
function handleKeyDown(e) {
const s = (0, engine_1.getSelection)();
if (!s || currentEditingCellId)
return;
let newR = s.r;
let newC = s.c;
if (e.key === 'Enter') {
newR = Math.min(s.r + 1, CONFIG.ROWS - 1);
}
else if (e.key === 'Tab') {
newC = Math.min(s.c + 1, CONFIG.COLS - 1);
}
else if (e.key === 'ArrowUp') {
newR = Math.max(0, s.r - 1);
}
else if (e.key === 'ArrowDown') {
newR = Math.min(s.r + 1, CONFIG.ROWS - 1);
}
else if (e.key === 'ArrowLeft') {
newC = Math.max(0, s.c - 1);
}
else if (e.key === 'ArrowRight') {
newC = Math.min(s.c + 1, CONFIG.COLS - 1);
}
else if (e.key === 'Delete' || e.key === 'Backspace') {
const id = (0, engine_1.getCellId)(s.r, s.c);
(0, engine_1.updateCell)(id, '');
updateAllCells();
return;
}
else {
return;
}
(0, engine_1.setSelection)(newR, newC);
handleCellSelect((0, engine_1.getCellId)(newR, newC));
}
function setupListeners() {
spreadsheetBody.addEventListener('click', (e) => {
const td = e.target;
if (td.tagName === 'TD' && td.id.startsWith('cell-')) {
handleCellSelect(td.id);
}
});
spreadsheetBody.addEventListener('dblclick', (e) => {
const td = e.target;
if (td.tagName === 'TD' && td.id.startsWith('cell-')) {
handleCellDoubleClick(td.id);
}
});
formulaInput.addEventListener('input', () => {
if (!currentEditingCellId) {
const s = (0, engine_1.getSelection)();
if (s) {
const id = (0, engine_1.getCellId)(s.r, s.c);
(0, engine_1.updateCell)(id, formulaInput.value);
renderCellContent(document.getElementById(`cell-${id}`), (0, engine_1.getCells)().get(id));
}
}
});
formulaInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const s = (0, engine_1.getSelection)();
if (s) {
const id = (0, engine_1.getCellId)(s.r, s.c);
(0, engine_1.updateCell)(id, formulaInput.value);
renderCellContent(document.getElementById(`cell-${id}`), (0, engine_1.getCells)().get(id));
}
}
});
window.addEventListener('keydown', handleKeyDown);
toolbarBtnSave.onclick = () => {
(0, engine_1.save)();
alert('Saved to local storage');
};
toolbarBtnInsertRow.onclick = () => {
const s = (0, engine_1.getSelection)();
if (s) {
(0, engine_1.insertRow)(s.r + 1);
renderGrid();
handleCellSelect((0, engine_1.getCellId)(s.r, s.c));
}
};
toolbarBtnDeleteRow.onclick = () => {
const s = (0, engine_1.getSelection)();
if (s) {
(0, engine_1.deleteRow)(s.r + 1);
renderGrid();
handleCellSelect((0, engine_1.getCellId)(s.r, s.c));
}
};
}
(0, engine_1.init)();
(0, engine_1.load)();
renderGrid();
setupListeners();
};
// ── module: src/engine.ts ──
__mods["src/engine.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.init = init;
exports.getCellId = getCellId;
exports.calculateCell = calculateCell;
exports.updateCell = updateCell;
exports.registerDependencies = registerDependencies;
exports.save = save;
exports.load = load;
exports.getSelection = getSelection;
exports.setSelection = setSelection;
exports.insertRow = insertRow;
exports.deleteRow = deleteRow;
exports.getCells = getCells;
const CONFIG = {
ROWS: 100,
COLS: 26,
};
// Internal State
let cells = new Map();
let dependencies = new Map(); // key -> set of cells that depend on it
let selection = null;
function init() {
for (let r = 0; r < CONFIG.ROWS; r++) {
for (let c = 0; c < CONFIG.COLS; c++) {
const id = getCellId(r, c);
cells.set(id, { raw: '', value: '' });
dependencies.set(id, new Set());
}
}
}
function getCellId(r, c) {
const colStr = String.fromCharCode(65 + c); // A-Z
return `${colStr}${r + 1}`;
}
// Formula Engine
class Parser {
constructor() {
this.tokens = [];
this.pos = 0;
}
tokenize(input) {
this.tokens = input.match(/([A-Z][0-9]+)|([0-9.]+)|(\+|\-|\*|\/|\(|\)|,)|(=)|([a-zA-Z]+)/g) || [];
this.pos = 0;
}
peek() { return this.tokens[this.pos]; }
consume() { return this.tokens[this.pos++]; }
parseExpression() {
let node = this.parseTerm();
while (this.peek() === '+' || this.peek() === '-') {
const op = this.consume();
const right = this.parseTerm();
node = op === '+' ? node + right : node - right;
}
return node;
}
parseTerm() {
let node = this.parseFactor();
while (this.peek() === '*' || this.peek() === '/') {
const op = this.consume();
const right = this.parseFactor();
if (op === '*')
node *= right;
else {
if (right === 0)
throw new Error('DIV_ZERO');
node /= right;
}
}
return node;
}
parseFactor() {
let token = this.peek();
if (token === '-') {
this.consume();
return -this.parseFactor();
}
if (token === '(') {
this.consume();
const node = this.parseExpression();
this.consume(); // )
return node;
}
if (token && !isNaN(parseFloat(token))) {
return parseFloat(this.consume());
}
if (token && /^[A-Z][0-9]+$/.test(token)) {
const ref = this.consume();
return { type: 'ref', id: ref };
}
if (token && /^[a-zA-Z]+$/.test(token)) {
const func = this.consume();
this.consume(); // (
const args = [];
while (this.peek() !== ')') {
args.push(this.parseExpression());
if (this.peek() === ',')
this.consume();
}
this.consume(); // )
return { type: 'func', name: func, args };
}
throw new Error('PARSE_ERR');
}
evaluate(input) {
if (!input.startsWith('='))
return input;
const formula = input.substring(1);
this.tokenize(formula);
try {
return this.parseExpression();
}
catch (e) {
throw e;
}
}
resolveNode(node, visited) {
if (typeof node === 'number' || typeof node === 'string')
return node;
if (node && node.type === 'ref') {
const val = calculateCell(node.id, visited);
return val;
}
if (node && node.type === 'func') {
const args = node.args.map((arg) => this.resolveNode(arg, visited));
switch (node.name.toUpperCase()) {
case 'SUM': return args.reduce((a, b) => (typeof a === 'number' && typeof b === 'number' ? a + b : a), 0);
case 'AVG':
const nums = args.filter((v) => typeof v === 'number');
return nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;
case 'MIN': return Math.min(...args.filter((v) => typeof v === 'number'));
case 'MAX': return Math.max(...args.filter((v) => typeof v === 'number'));
case 'COUNT': return args.filter((v) => v !== '').length;
default: throw new Error('PARSE_ERR');
}
}
return node;
}
}
function calculateCell(id, visited = new Set()) {
if (visited.has(id))
throw new Error('CYCLE');
const data = cells.get(id);
if (!data || !data.raw)
return '';
if (!data.raw.startsWith('=')) {
return data.raw;
}
visited.add(id);
const parser = new Parser();
try {
const result = parser.evaluate(data.raw);
return parser.resolveNode(result, visited);
}
catch (e) {
if (e.message === 'DIV_ZERO')
return '#DIV/0!';
if (e.message === 'CYCLE')
return '#CYCLE!';
return '#ERR!';
}
}
// Dependency Graph & Update logic
function updateCell(id, rawValue) {
const data = cells.get(id);
if (data) {
data.raw = rawValue;
registerDependencies(); // Refresh graph as formulas change
recompute(id);
}
}
function recompute(rootId) {
const affected = new Set();
const queue = [rootId];
while (queue.length > 0) {
const curr = queue.shift();
affected.add(curr);
const dependents = dependencies.get(curr);
if (dependents) {
for (const dep of dependents) {
if (!affected.has(dep))
queue.push(dep);
}
}
}
const dependsOn = new Map();
for (const [source, targets] of dependencies) {
for (const target of targets) {
if (!dependsOn.has(target))
dependsOn.set(target, new Set());
dependsOn.get(target).add(source);
}
}
const sorted = [];
const seen = new Set();
const visiting = new Set();
function dfs(id) {
if (visiting.has(id))
return;
if (seen.has(id))
return;
visiting.add(id);
const parents = dependsOn.get(id);
if (parents) {
for (const p of parents) {
dfs(p);
}
}
visiting.delete(id);
seen.add(id);
sorted.push(id);
}
affected.forEach(dfs);
for (const id of sorted) {
try {
const val = calculateCell(id);
const data = cells.get(id);
data.value = val;
data.error = false;
}
catch (e) {
const data = cells.get(id);
data.value = e.message === 'CYCLE' ? '#CYCLE!' : (e.message === 'DIV_ZERO' ? '#DIV/0!' : '#ERR!');
data.error = true;
}
}
}
function registerDependencies() {
dependencies.clear();
for (let r = 0; r < CONFIG.ROWS; r++) {
for (let c = 0; c < CONFIG.COLS; c++) {
const id = getCellId(r, c);
const data = cells.get(id);
if (data && data.raw.startsWith('=')) {
// Support for ranges like A1:A5
const mut_refs = data.raw.match(/[A-Z][0-9]+(?::[A-Z][0-9]+)?/g);
if (mut_refs) {
for (const ref of mut_refs) {
// For ranges, we treat each individual cell in the range as a dependency
if (ref.includes(':')) {
const [start, end] = ref.split(':');
const startMatch = start.match(/([A-Z])(\d+)/);
const endMatch = end.match(/([A-Z])(\d+)/);
if (startMatch && endMatch) {
const c1 = startMatch[1].charCodeAt(0) - 65;
const r1 = parseInt(startMatch[2]);
const c2 = endMatch[1].charCodeAt(0) - 65;
const r2 = parseInt(endMatch[2]);
const minR = Math.min(r1, r2);
const maxR = Math.max(r1, r2);
const minC = Math.min(c1, c2);
const maxC = Math.max(c1, c2);
for (let rr = minR; rr <= maxR; rr++) {
for (let cc = minC; cc <= maxC; cc++) {
const cellId = getCellId(rr, cc);
if (!dependencies.has(cellId))
dependencies.set(cellId, new Set());
dependencies.get(cellId).add(id);
}
}
}
}
else {
if (!dependencies.has(ref))
dependencies.set(ref, new Set());
dependencies.get(ref).add(id);
}
}
}
}
}
}
}
function save() {
localStorage.setItem('sheetcalc_data', JSON.stringify(Array.from(cells.entries())));
}
function load() {
const data = localStorage.getItem('sheetcalc_data');
if (data) {
try {
const parsed = JSON.parse(data);
cells = new Map(parsed);
registerDependencies();
for (const id of cells.keys()) {
recompute(id);
}
}
catch (e) {
init();
}
}
else {
init();
}
}
function getSelection() { return selection; }
function setSelection(r, c) {
selection = { r, c };
}
// Row/Column operations with formula rewriting
function insertRow(index) {
const newCells = new Map();
for (let r = 0; r < CONFIG.ROWS; r++) {
for (let c = 0; c < CONFIG.COLS; c++) {
const oldId = getCellId(r, c);
let newR = r;
if (r >= index)
newR = r + 1;
if (newR >= CONFIG.ROWS)
continue;
const newId = getCellId(newR, c);
const oldData = cells.get(oldId);
if (oldData) {
let newData = { ...oldData };
if (r >= index) {
newData.raw = newData.raw.replace(/([A-Z])(\d+)/g, (m, col, row) => {
return col + (parseInt(row) + 1);
});
}
newCells.set(newId, newData);
}
else if (r === index) {
newCells.set(newId, { raw: '', value: '' });
}
}
}
cells = newCells;
registerDependencies();
}
function deleteRow(index) {
const newCells = new Map();
for (let r = 0; r < CONFIG.ROWS; r++) {
if (r === index)
continue;
for (let c = 0; c < CONFIG.COLS; c++) {
let newR = r;
if (r > index)
newR = r - 1;
const oldId = getCellId(r, c);
const newId = getCellId(newR, c);
const oldData = cells.get(oldId);
if (oldData) {
let newData = { ...oldData };
if (r > index) {
newData.raw = newData.raw.replace(/([A-Z])(\d+)/g, (m, col, row) => {
return col + (parseInt(row) - 1);
});
}
newCells.set(newId, newData);
}
}
}
cells = newCells;
registerDependencies();
}
function getCells() { return cells; }
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"google/gemma-4-12b","provider":"lmstudio","persona":"typescript","sessionId":"5da0cd13-4063-45ee-9ccc-92f6450c065a","tokensIn":1828073,"tokensOut":74061,"tokensTotal":1902134,"cost":0,"turns":46,"toolCalls":45,"failedToolCalls":6,"timestamp":"2026-08-02T05:22:00.218Z"} -->