← Spreadsheet results

Spreadsheet

Gemma 4 12b · typescript

Initial view of Spreadsheet

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"} -->