← Spreadsheet results

Spreadsheet

ThinkingCap Qwen3.6 27B GGUF · 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 45,586 bytes · SHA-256 fbffd1d317c8
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Spreadsheet</title>
<style>
/* ── css: spreadsheet ── */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  font-size: 13px; color: #202124; overflow: hidden;
  height: 100vh; display: flex; flex-direction: column;
}
#toolbar {
  display: flex; align-items: center; gap: 4px;
  padding: 4px 8px; background: #f8f9fa;
  border-bottom: 1px solid #d4d4d4;
  min-height: 32px; flex-shrink: 0;
}
#toolbar button {
  background: #fff; border: 1px solid #d4d4d4;
  border-radius: 3px; padding: 3px 10px;
  font-size: 12px; cursor: pointer; color: #444;
}
#toolbar button:hover { background: #e8eaed; }
#formula-bar {
  display: flex; align-items: center;
  padding: 4px 8px; background: #f8f9fa;
  border-bottom: 1px solid #d4d4d4; flex-shrink: 0;
}
#cell-ref {
  min-width: 50px; text-align: center; font-weight: 600;
  color: #666; border-right: 1px solid #d4d4d4;
  padding-right: 8px; flex-shrink: 0;
}
#formula-input {
  flex: 1; border: 1px solid #d4d4d4;
  padding: 4px 8px; font-size: 13px;
  font-family: 'Segoe UI', system-ui, sans-serif;
  border-radius: 2px; outline: none;
}
#formula-input:focus {
  border-color: #1a73e8; box-shadow: 0 0 0 1px #1a73e8;
}
#grid-container { flex: 1; overflow: auto; }
#grid { border-collapse: collapse; table-layout: fixed; }
#grid th, #grid td {
  border: 1px solid #d4d4d4; padding: 0;
  font-size: 13px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.col-header {
  background: #f8f9fa; color: #666; font-weight: 500;
  text-align: center; position: sticky; top: 0;
  z-index: 2; height: 25px; min-width: 40px; user-select: none;
}
.col-header-active { background: #d2e3fc; color: #1a73e8; font-weight: 600; }
.row-header {
  background: #f8f9fa; color: #666; font-weight: 500;
  text-align: center; position: sticky; left: 0;
  z-index: 1; min-width: 40px; width: 40px; user-select: none;
}
.row-header-active { background: #d2e3fc; color: #1a73e8; font-weight: 600; }
.corner {
  position: sticky; top: 0; left: 0; z-index: 3;
  background: #f8f9fa; width: 40px; min-width: 40px;
}
.cell {
  padding: 2px 4px; height: 25px;
  min-height: 25px; cursor: cell;
}
.cell-number { text-align: right; }
.cell-error { color: #c0392b; font-weight: 500; }
.cell-selected {
  outline: 2px solid #1a73e8;
  outline-offset: -1px; position: relative; z-index: 1;
}
.cell-input {
  width: 100%; height: 100%; border: none; outline: none;
  font-size: 13px; font-family: 'Segoe UI', system-ui, sans-serif;
  padding: 2px 4px;
}
.context-menu {
  position: fixed; background: #fff;
  border: 1px solid #d4d4d4; border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
  z-index: 100; min-width: 160px; padding: 4px 0;
}
.context-menu-item {
  padding: 6px 16px; cursor: pointer;
  font-size: 13px; color: #333;
}
.context-menu-item:hover { background: #e8eaed; }
</style>
</head>
<body>
<div id="toolbar"></div>
<div id="formula-bar">
  <span id="cell-ref"></span>
  <input id="formula-input" type="text" spellcheck="false" autocomplete="off">
</div>
<div id="grid-container">
  <table id="grid"></table>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./config":"src/config.ts","./state":"src/state.ts","./formula":"src/formula.ts","./renderer":"src/renderer.ts"},"src/state.ts":{"./config":"src/config.ts"},"src/formula.ts":{"./config":"src/config.ts","./state":"src/state.ts"},"src/renderer.ts":{"./config":"src/config.ts","./state":"src/state.ts","./formula":"src/formula.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 config_1 = require("./config");
const state_1 = require("./state");
const formula_1 = require("./formula");
const renderer_1 = require("./renderer");
// ---- Init ----
const sheet = new state_1.Sheet(config_1.CONFIG.COLS, config_1.CONFIG.ROWS);
const depGraph = new formula_1.DepGraph();
(0, renderer_1.setSheet)(sheet, depGraph);
(0, renderer_1.setSheetSize)(sheet.cols, sheet.rows);
sheet.load();
(0, formula_1.fullRecalc)(sheet, depGraph);
(0, renderer_1.buildGrid)(sheet);
(0, renderer_1.renderAll)(sheet);
// Expose for testing
window.__sheet = sheet;
window.__depGraph = depGraph;
window.__finishEdit = renderer_1.finishEdit;
window.__renderAll = renderer_1.renderAll;
window.__recalcAndCascade = formula_1.recalcAndCascade;
window.__rebuildDeps = formula_1.rebuildDeps;
window.__cellKey = state_1.cellKey;
window.__fullRecalc = formula_1.fullRecalc;
// Select A1 initially
(0, renderer_1.setSelected)(0, 0);
(0, renderer_1.updateCellRefDisplay)();
// ---- Toolbar buttons ----
const toolbar = document.getElementById('toolbar');
function makeToolbarButton(label, action) {
    const btn = document.createElement('button');
    btn.textContent = label;
    btn.addEventListener('click', () => { action(); });
    return btn;
}
toolbar.appendChild(makeToolbarButton('Insert Row', () => insertRowAt((0, renderer_1.getSelected)()?.row ?? 0)));
toolbar.appendChild(makeToolbarButton('Delete Row', () => deleteRowAt((0, renderer_1.getSelected)()?.row ?? 0)));
toolbar.appendChild(makeToolbarButton('Insert Col', () => insertColAt((0, renderer_1.getSelected)()?.col ?? 0)));
toolbar.appendChild(makeToolbarButton('Delete Col', () => deleteColAt((0, renderer_1.getSelected)()?.col ?? 0)));
// ---- Grid events ----
const grid = document.getElementById('grid');
grid.addEventListener('click', (e) => {
    const td = e.target.closest('td.cell');
    if (!td)
        return;
    const c = parseInt(td.dataset.col || '0', 10);
    const r = parseInt(td.dataset.row || '0', 10);
    (0, renderer_1.setSelected)(c, r);
    (0, renderer_1.updateCellRefDisplay)();
    const cellData = sheet.get(c, r);
    const formulaInput = document.getElementById('formula-input');
    if (formulaInput)
        formulaInput.value = cellData.raw;
});
grid.addEventListener('dblclick', (e) => {
    const td = e.target.closest('td.cell');
    if (!td)
        return;
    const c = parseInt(td.dataset.col || '0', 10);
    const r = parseInt(td.dataset.row || '0', 10);
    (0, renderer_1.setSelected)(c, r);
    (0, renderer_1.updateCellRefDisplay)();
    (0, renderer_1.startEdit)(sheet);
});
// Header right-click → context menu
grid.addEventListener('contextmenu', (e) => {
    e.preventDefault();
    const th = e.target.closest('th.col-header');
    const rowHeader = e.target.closest('td.row-header');
    if (th) {
        const col = parseInt(th.dataset.col || '0', 10);
        showContextMenu(e.clientX, e.clientY, [
            { label: 'Insert Column', action: () => insertColAt(col) },
            { label: 'Delete Column', action: () => deleteColAt(col) },
        ]);
    }
    else if (rowHeader) {
        const row = parseInt(rowHeader.dataset.row || '0', 10);
        showContextMenu(e.clientX, e.clientY, [
            { label: 'Insert Row', action: () => insertRowAt(row) },
            { label: 'Delete Row', action: () => deleteRowAt(row) },
        ]);
    }
});
function showContextMenu(x, y, items) {
    removeContextMenu();
    const menu = document.createElement('div');
    menu.className = 'context-menu';
    menu.style.left = x + 'px';
    menu.style.top = y + 'px';
    for (const item of items) {
        const div = document.createElement('div');
        div.className = 'context-menu-item';
        div.textContent = item.label;
        div.addEventListener('click', () => { item.action(); removeContextMenu(); });
        menu.appendChild(div);
    }
    document.body.appendChild(menu);
}
function removeContextMenu() {
    const existing = document.querySelector('.context-menu');
    if (existing)
        existing.remove();
}
document.addEventListener('click', (e) => {
    if (!e.target.closest('.context-menu'))
        removeContextMenu();
});
// ---- Formula bar ----
const formulaInputEl = document.getElementById('formula-input');
formulaInputEl.addEventListener('keydown', (e) => {
    const sel = (0, renderer_1.getSelected)();
    if (!sel)
        return;
    if (e.key === 'Enter') {
        e.preventDefault();
        const { col: c, row: r } = sel;
        const raw = formulaInputEl.value;
        (0, renderer_1.finishEdit)(sheet, c, r, raw, depGraph);
        (0, renderer_1.moveSelection)(0, 1);
    }
});
formulaInputEl.addEventListener('input', () => {
    const input = grid.querySelector('.cell-input');
    if (input)
        input.value = formulaInputEl.value;
});
// ---- Keyboard shortcuts ----
document.addEventListener('keydown', (e) => {
    if (document.activeElement === formulaInputEl)
        return;
    if (document.activeElement?.classList.contains('cell-input'))
        return;
    const sel = (0, renderer_1.getSelected)();
    if (!sel)
        return;
    if (e.key === 'ArrowDown') {
        e.preventDefault();
        (0, renderer_1.moveSelection)(0, 1);
    }
    else if (e.key === 'ArrowUp') {
        e.preventDefault();
        (0, renderer_1.moveSelection)(0, -1);
    }
    else if (e.key === 'ArrowRight') {
        e.preventDefault();
        (0, renderer_1.moveSelection)(1, 0);
    }
    else if (e.key === 'ArrowLeft') {
        e.preventDefault();
        (0, renderer_1.moveSelection)(-1, 0);
    }
    else if (e.key === 'Tab') {
        e.preventDefault();
        (0, renderer_1.moveSelection)(e.shiftKey ? -1 : 1, 0);
    }
    else if (e.key === 'Enter') {
        e.preventDefault();
        if (grid.querySelector('.cell-input')) {
            (0, renderer_1.moveSelection)(0, 1);
        }
        else {
            (0, renderer_1.startEdit)(sheet);
        }
    }
    else if (e.key === 'Delete' || e.key === 'Backspace') {
        e.preventDefault();
        clearCell(sel.col, sel.row);
    }
    else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
        (0, renderer_1.startEdit)(sheet);
        const input = grid.querySelector('.cell-input');
        if (input) {
            input.value = e.key;
            formulaInputEl.value = e.key;
        }
    }
});
function clearCell(c, r) {
    sheet.clear(c, r);
    (0, formula_1.fullRecalc)(sheet, depGraph);
    (0, renderer_1.renderAll)(sheet);
    sheet.save();
    (0, renderer_1.updateCellRefDisplay)();
    formulaInputEl.value = '';
}
// ---- Formula rewriting for row/col shifts ----
function rewriteRaw(raw, colShift, rowShift) {
    if (!raw.startsWith('='))
        return raw;
    return raw.replace(/[A-Z]\d+/g, (match) => {
        const parsed = (0, state_1.parseKey)(match);
        if (!parsed)
            return match;
        let nc = parsed[0] + colShift;
        let nr = parsed[1] + rowShift;
        if (nc < 0 || nc >= sheet.cols || nr < 0 || nr >= sheet.rows) {
            return '#REF!';
        }
        return (0, state_1.cellKey)(nc, nr);
    });
}
function rebuildGrid() {
    (0, renderer_1.buildGrid)(sheet);
    (0, renderer_1.renderAll)(sheet);
}
// ---- Row/Column insert/delete ----
function insertRowAt(rowIdx) {
    const entries = new Map();
    sheet.forEach((c, r, data) => {
        if (r < rowIdx) {
            entries.set((0, state_1.cellKey)(c, r), data);
        }
        else {
            const newKey = (0, state_1.cellKey)(c, r + 1);
            const rewritten = rewriteRaw(data.raw, 0, 1);
            entries.set(newKey, { ...data, raw: rewritten });
        }
    });
    sheet.rows++;
    (0, renderer_1.setSheetSize)(sheet.cols, sheet.rows);
    sheet.grid = entries;
    (0, formula_1.fullRecalc)(sheet, depGraph);
    rebuildGrid();
    sheet.save();
}
function deleteRowAt(rowIdx) {
    if (sheet.rows <= 1)
        return;
    const entries = new Map();
    sheet.forEach((c, r, data) => {
        if (r < rowIdx) {
            entries.set((0, state_1.cellKey)(c, r), data);
        }
        else if (r > rowIdx) {
            const newKey = (0, state_1.cellKey)(c, r - 1);
            const rewritten = rewriteRaw(data.raw, 0, -1);
            entries.set(newKey, { ...data, raw: rewritten });
        }
        // skip r === rowIdx
    });
    sheet.rows--;
    (0, renderer_1.setSheetSize)(sheet.cols, sheet.rows);
    sheet.grid = entries;
    (0, formula_1.fullRecalc)(sheet, depGraph);
    rebuildGrid();
    sheet.save();
}
function insertColAt(colIdx) {
    const entries = new Map();
    sheet.forEach((c, r, data) => {
        if (c < colIdx) {
            entries.set((0, state_1.cellKey)(c, r), data);
        }
        else {
            const newKey = (0, state_1.cellKey)(c + 1, r);
            const rewritten = rewriteRaw(data.raw, 1, 0);
            entries.set(newKey, { ...data, raw: rewritten });
        }
    });
    sheet.cols++;
    (0, renderer_1.setSheetSize)(sheet.cols, sheet.rows);
    sheet.grid = entries;
    (0, formula_1.fullRecalc)(sheet, depGraph);
    rebuildGrid();
    sheet.save();
}
function deleteColAt(colIdx) {
    if (sheet.cols <= 1)
        return;
    const entries = new Map();
    sheet.forEach((c, r, data) => {
        if (c < colIdx) {
            entries.set((0, state_1.cellKey)(c, r), data);
        }
        else if (c > colIdx) {
            const newKey = (0, state_1.cellKey)(c - 1, r);
            const rewritten = rewriteRaw(data.raw, -1, 0);
            entries.set(newKey, { ...data, raw: rewritten });
        }
        // skip c === colIdx
    });
    sheet.cols--;
    (0, renderer_1.setSheetSize)(sheet.cols, sheet.rows);
    sheet.grid = entries;
    (0, formula_1.fullRecalc)(sheet, depGraph);
    rebuildGrid();
    sheet.save();
}
};

// ── module: src/config.ts ──
__mods["src/config.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERROR_MSGS = exports.CONFIG = void 0;
// Tunable constants
exports.CONFIG = {
    // Grid dimensions
    COLS: 26,
    ROWS: 100,
    // Cell dimensions
    CELL_WIDTH: 100,
    CELL_HEIGHT: 25,
    HEADER_WIDTH: 40,
    HEADER_HEIGHT: 25,
    // Colors
    COLOR_GRID_LINE: '#d4d4d4',
    COLOR_HEADER_BG: '#f8f9fa',
    COLOR_HEADER_TEXT: '#666',
    COLOR_SELECTION_BORDER: '#1a73e8',
    COLOR_TEXT: '#202124',
    COLOR_ERROR: '#c0392b',
    COLOR_NUMBER: '#202124',
    COLOR_ACTIVE_HEADER: '#e8eaed',
    COLOR_ROW_HEADER: '#f1f3f4',
    COLOR_COL_HEADER: '#f1f3f4',
};
exports.ERROR_MSGS = {
    CYCLE: '#CYCLE!',
    DIV0: '#DIV/0!',
    REF: '#REF!',
    ERR: '#ERR!',
};
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sheet = void 0;
exports.colName = colName;
exports.colIndex = colIndex;
exports.cellKey = cellKey;
exports.parseKey = parseKey;
exports.makeEmptyCell = makeEmptyCell;
exports.makeErrorCell = makeErrorCell;
function colName(c) {
    return String.fromCharCode(65 + c);
}
function colIndex(name) {
    return name.charCodeAt(0) - 65;
}
function cellKey(c, r) {
    return colName(c) + (r + 1);
}
function parseKey(key) {
    const m = key.match(/^([A-Z])(\d+)$/);
    if (!m)
        return null;
    const c = colIndex(m[1]);
    const r = parseInt(m[2], 10) - 1;
    if (r < 0)
        return null;
    return [c, r];
}
function makeEmptyCell() {
    return { raw: '', value: '', isError: false };
}
function makeErrorCell(msg) {
    return { raw: '', value: msg, isError: true };
}
class Sheet {
    constructor(cols, rows) {
        this.grid = new Map();
        this.cols = cols;
        this.rows = rows;
    }
    get(c, r) {
        const key = cellKey(c, r);
        return this.grid.get(key) || makeEmptyCell();
    }
    set(c, r, data) {
        const key = cellKey(c, r);
        this.grid.set(key, data);
    }
    clear(c, r) {
        const key = cellKey(c, r);
        this.grid.delete(key);
    }
    forEach(fn) {
        for (const [key, data] of this.grid) {
            const parsed = parseKey(key);
            if (parsed)
                fn(parsed[0], parsed[1], data);
        }
    }
    save() {
        const obj = {};
        for (const [key, data] of this.grid) {
            obj[key] = data;
        }
        localStorage.setItem('spreadsheet_data', JSON.stringify(obj));
        localStorage.setItem('spreadsheet_cols', String(this.cols));
        localStorage.setItem('spreadsheet_rows', String(this.rows));
    }
    load() {
        const colsStr = localStorage.getItem('spreadsheet_cols');
        const rowsStr = localStorage.getItem('spreadsheet_rows');
        if (colsStr)
            this.cols = parseInt(colsStr, 10);
        if (rowsStr)
            this.rows = parseInt(rowsStr, 10);
        const raw = localStorage.getItem('spreadsheet_data');
        if (!raw)
            return;
        try {
            const obj = JSON.parse(raw);
            this.grid = new Map(Object.entries(obj));
        }
        catch {
            this.grid = new Map();
        }
    }
}
exports.Sheet = Sheet;
};

// ── module: src/formula.ts ──
__mods["src/formula.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DepGraph = void 0;
exports.tokenize = tokenize;
exports.parseFormula = parseFormula;
exports.collectRefs = collectRefs;
exports.evaluate = evaluate;
exports.recalcCell = recalcCell;
exports.recalcAndCascade = recalcAndCascade;
exports.rebuildDeps = rebuildDeps;
exports.fullRecalc = fullRecalc;
const config_1 = require("./config");
const state_1 = require("./state");
function tokenize(input) {
    const toks = [];
    let i = 0;
    const s = input.toUpperCase();
    while (i < s.length) {
        if (s[i] === ' ') {
            i++;
            continue;
        }
        if (s[i] >= '0' && s[i] <= '9' || s[i] === '.') {
            let num = '';
            while (i < s.length && ((s[i] >= '0' && s[i] <= '9') || s[i] === '.'))
                num += s[i++];
            toks.push({ type: 'NUMBER', val: num });
        }
        else if (s[i] >= 'A' && s[i] <= 'Z') {
            let id = '';
            while (i < s.length && ((s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= '0' && s[i] <= '9')))
                id += s[i++];
            toks.push({ type: 'IDENT', val: id });
        }
        else {
            switch (s[i]) {
                case '+':
                    toks.push({ type: 'PLUS', val: '+' });
                    i++;
                    break;
                case '-':
                    toks.push({ type: 'MINUS', val: '-' });
                    i++;
                    break;
                case '*':
                    toks.push({ type: 'STAR', val: '*' });
                    i++;
                    break;
                case '/':
                    toks.push({ type: 'SLASH', val: '/' });
                    i++;
                    break;
                case '(':
                    toks.push({ type: 'LPAREN', val: '(' });
                    i++;
                    break;
                case ')':
                    toks.push({ type: 'RPAREN', val: ')' });
                    i++;
                    break;
                case ',':
                    toks.push({ type: 'COMMA', val: ',' });
                    i++;
                    break;
                case ':':
                    toks.push({ type: 'COLON', val: ':' });
                    i++;
                    break;
                default: throw new Error(config_1.ERROR_MSGS.ERR);
            }
        }
    }
    toks.push({ type: 'EOF', val: '' });
    return toks;
}
// ---- Parser (recursive descent) ----
class Parser {
    constructor(toks) {
        this.toks = toks;
        this.pos = 0;
    }
    peek() {
        return this.toks[this.pos];
    }
    consume() {
        return this.toks[this.pos++];
    }
    expect(type) {
        const t = this.consume();
        if (t.type !== type)
            throw new Error(config_1.ERROR_MSGS.ERR);
        return t;
    }
    parse() {
        try {
            const node = this.expr();
            if (this.peek().type !== 'EOF')
                throw new Error(config_1.ERROR_MSGS.ERR);
            return node;
        }
        catch {
            return { tag: 'err', msg: config_1.ERROR_MSGS.ERR };
        }
    }
    // expr → addSub
    expr() {
        return this.addSub();
    }
    // addSub → mulDiv (('+' | '-') mulDiv)*
    addSub() {
        let left = this.mulDiv();
        while (this.peek().type === 'PLUS' || this.peek().type === 'MINUS') {
            const op = this.consume().val;
            const right = this.mulDiv();
            left = { tag: 'binop', op, left, right };
        }
        return left;
    }
    // mulDiv → unary (('*' | '/') unary)*
    mulDiv() {
        let left = this.unary();
        while (this.peek().type === 'STAR' || this.peek().type === 'SLASH') {
            const op = this.consume().val;
            const right = this.unary();
            left = { tag: 'binop', op, left, right };
        }
        return left;
    }
    // unary → '-' unary | primary
    unary() {
        if (this.peek().type === 'MINUS') {
            this.consume();
            const operand = this.unary();
            return { tag: 'unary', op: '-', operand };
        }
        return this.primary();
    }
    // primary → NUMBER | cellRef (':' cellRef)? | func '(' args ')' | '(' expr ')'
    primary() {
        const t = this.peek();
        if (t.type === 'NUMBER') {
            this.consume();
            const v = parseFloat(t.val);
            return { tag: 'num', val: isNaN(v) ? NaN : v };
        }
        if (t.type === 'LPAREN') {
            this.consume();
            const node = this.expr();
            this.expect('RPAREN');
            return node;
        }
        if (t.type === 'IDENT') {
            this.consume();
            // Check if it's a function name
            if (['SUM', 'AVG', 'AVERAGE', 'MIN', 'MAX', 'COUNT'].includes(t.val)) {
                this.expect('LPAREN');
                const args = [];
                if (this.peek().type !== 'RPAREN') {
                    args.push(this.expr());
                    while (this.peek().type === 'COMMA') {
                        this.consume();
                        args.push(this.expr());
                    }
                }
                this.expect('RPAREN');
                return { tag: 'func', name: t.val, args };
            }
            // Must be a cell reference like A1
            const parsed = (0, state_1.parseKey)(t.val);
            if (!parsed)
                throw new Error(config_1.ERROR_MSGS.ERR);
            const refNode = { tag: 'ref', col: parsed[0], row: parsed[1] };
            // Check for range operator ':'
            if (this.peek().type === 'COLON') {
                this.consume();
                const toNode = this.primary();
                return { tag: 'range', from: refNode, to: toNode };
            }
            return refNode;
        }
        throw new Error(config_1.ERROR_MSGS.ERR);
    }
}
function parseFormula(raw) {
    if (!raw.startsWith('=')) {
        // Not a formula
        const n = parseFloat(raw);
        if (!isNaN(n))
            return { tag: 'num', val: n };
        return { tag: 'num', val: NaN }; // text
    }
    const expr = raw.substring(1);
    try {
        const toks = tokenize(expr);
        const parser = new Parser(toks);
        return parser.parse();
    }
    catch {
        return { tag: 'err', msg: config_1.ERROR_MSGS.ERR };
    }
}
// ---- Dependency Extraction ----
function collectRefs(node) {
    const refs = new Set();
    collectRefsImpl(node, refs);
    return [...refs];
}
function collectRefsImpl(node, set) {
    if (!node)
        return;
    switch (node.tag) {
        case 'ref':
            set.add(node.col + ',' + node.row);
            break;
        case 'binop':
            collectRefsImpl(node.left, set);
            collectRefsImpl(node.right, set);
            break;
        case 'unary':
            collectRefsImpl(node.operand, set);
            break;
        case 'func':
            for (const a of node.args)
                collectRefsImpl(a, set);
            break;
        case 'range':
            collectRefsImpl(node.from, set);
            collectRefsImpl(node.to, set);
            break;
    }
}
function isEvalError(v) {
    return typeof v === 'object' && v !== null && '_error' in v;
}
function makeEvalError(msg) {
    return { _error: msg };
}
function numVal(v) {
    if (isEvalError(v))
        return v;
    if (typeof v === 'number')
        return v;
    if (typeof v === 'string') {
        const n = parseFloat(v);
        return isNaN(n) ? 0 : n;
    }
    return 0;
}
function evaluate(sheet, node, computed, visiting) {
    if (!computed)
        computed = new Set();
    if (!visiting)
        visiting = new Set();
    switch (node.tag) {
        case 'num':
            return node.val;
        case 'err':
            return makeEvalError(node.msg);
        case 'ref': {
            const key = node.col + ',' + node.row;
            if (visiting.has(key))
                return makeEvalError(config_1.ERROR_MSGS.CYCLE);
            visiting.add(key);
            const cell = sheet.get(node.col, node.row);
            if (cell.raw === '' || cell.raw === undefined) {
                visiting.delete(key);
                computed.add(key);
                return 0;
            }
            if (cell.isError) {
                visiting.delete(key);
                return makeEvalError(cell.value);
            }
            if (!cell.raw.startsWith('=')) {
                const n = parseFloat(cell.raw);
                visiting.delete(key);
                computed.add(key);
                return isNaN(n) ? cell.raw : n;
            }
            // For formula cells, check if already computed this pass
            if (computed.has(key)) {
                visiting.delete(key);
                return cell.value; // use already-computed value (non-formula short-circuit above)
            }
            // Evaluate this formula cell
            const ast = parseFormula(cell.raw);
            const result = evaluate(sheet, ast, computed, visiting);
            visiting.delete(key);
            computed.add(key);
            // Update cell value in sheet
            if (isEvalError(result)) {
                sheet.set(node.col, node.row, { raw: cell.raw, value: result._error, isError: true });
            }
            else {
                const display = typeof result === 'number' ? formatNumber(result) : String(result);
                sheet.set(node.col, node.row, { raw: cell.raw, value: display, isError: false });
            }
            return result;
        }
        case 'unary': {
            const v = evaluate(sheet, node.operand, computed, visiting);
            if (isEvalError(v))
                return v;
            return -numVal(v);
        }
        case 'binop': {
            const left = evaluate(sheet, node.left, computed, visiting);
            if (isEvalError(left))
                return left;
            const right = evaluate(sheet, node.right, computed, visiting);
            if (isEvalError(right))
                return right;
            const ln = numVal(left);
            if (isEvalError(ln))
                return ln;
            const rn = numVal(right);
            if (isEvalError(rn))
                return rn;
            switch (node.op) {
                case '+': return ln + rn;
                case '-': return ln - rn;
                case '*': return ln * rn;
                case '/': return rn === 0 ? makeEvalError(config_1.ERROR_MSGS.DIV0) : ln / rn;
                default: return makeEvalError(config_1.ERROR_MSGS.ERR);
            }
        }
        case 'func': {
            const values = [];
            for (const arg of node.args) {
                const expanded = expandArg(sheet, arg, computed, visiting);
                if (isEvalError(expanded))
                    return expanded;
                if (Array.isArray(expanded))
                    values.push(...expanded);
                else
                    values.push(expanded);
            }
            return applyFunc(node.name, values);
        }
        case 'range': {
            return expandRange(sheet, node, computed, visiting);
        }
        default:
            return makeEvalError(config_1.ERROR_MSGS.ERR);
    }
}
function expandArg(sheet, node, visited, visiting) {
    if (node.tag === 'range') {
        return expandRange(sheet, node, visited, visiting);
    }
    return evaluate(sheet, node, visited, visiting);
}
function expandRange(sheet, node, visited, visiting) {
    if (node.tag !== 'range')
        return makeEvalError(config_1.ERROR_MSGS.ERR);
    // Extract col/row from range endpoints. They can be refs or other expressions.
    // For refs, get coordinates directly. For evaluated expressions, try to parse as cell key.
    const fCoords = resolveRangeEndpoint(node.from, sheet, visited, visiting);
    if (isEvalError(fCoords))
        return fCoords;
    const tCoords = resolveRangeEndpoint(node.to, sheet, visited, visiting);
    if (isEvalError(tCoords))
        return tCoords;
    const vals = [];
    const c1 = Math.min(fCoords[0], tCoords[0]), c2 = Math.max(fCoords[0], tCoords[0]);
    const r1 = Math.min(fCoords[1], tCoords[1]), r2 = Math.max(fCoords[1], tCoords[1]);
    for (let c = c1; c <= c2; c++) {
        for (let r = r1; r <= r2; r++) {
            const key = c + ',' + r;
            if (visiting.has(key))
                return makeEvalError(config_1.ERROR_MSGS.CYCLE);
            visiting.add(key);
            const cell = sheet.get(c, r);
            visiting.delete(key);
            if (visited.has(key)) {
                vals.push(cell.isError ? makeEvalError(cell.value) : (isNaN(parseFloat(cell.value)) ? cell.value : parseFloat(cell.value)));
                continue;
            }
            visited.add(key);
            if (cell.raw === '' || cell.raw === undefined)
                vals.push(0);
            else if (cell.isError)
                vals.push(makeEvalError(cell.value));
            else if (!cell.raw.startsWith('=')) {
                const n = parseFloat(cell.raw);
                vals.push(isNaN(n) ? cell.raw : n);
            }
            else {
                const ast = parseFormula(cell.raw);
                vals.push(evaluate(sheet, ast, visited, visiting));
            }
        }
    }
    return vals;
}
// Resolve a range endpoint to [col, row] coordinates
function resolveRangeEndpoint(node, sheet, visited, visiting) {
    if (node.tag === 'ref')
        return [node.col, node.row];
    // Evaluate the expression and try to parse as cell key
    const val = evaluate(sheet, node, visited, visiting);
    if (isEvalError(val))
        return val;
    const parsed = (0, state_1.parseKey)(String(val));
    if (!parsed)
        return makeEvalError(config_1.ERROR_MSGS.REF);
    return parsed;
}
function tryParseCellRef(v) {
    if (typeof v === 'string') {
        const parsed = (0, state_1.parseKey)(v.toUpperCase());
        return parsed;
    }
    return null;
}
function applyFunc(name, values) {
    const nums = values.filter(v => !isEvalError(v) && typeof v === 'number');
    if (name === 'SUM')
        return nums.reduce((a, b) => a + b, 0);
    if (name === 'AVG' || name === 'AVERAGE')
        return nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;
    if (name === 'MIN')
        return nums.length ? Math.min(...nums) : 0;
    if (name === 'MAX')
        return nums.length ? Math.max(...nums) : 0;
    if (name === 'COUNT')
        return nums.length;
    return makeEvalError(config_1.ERROR_MSGS.ERR);
}
// ---- Dependency Graph & Recalculation ----
// deps: cellKey -> Set of cellKeys that depend on this cell
class DepGraph {
    constructor() {
        this.deps = new Map();
    }
    add(from, to) {
        if (!this.deps.has(from))
            this.deps.set(from, new Set());
        this.deps.get(from).add(to);
    }
    getDependents(key) {
        return [...(this.deps.get(key) || [])];
    }
    removeCell(key) {
        // Remove this cell's dependencies (it doesn't depend on anything anymore)
        // Remove this cell from other cells' dependent lists
        for (const [, set] of this.deps) {
            set.delete(key);
        }
    }
    clear() {
        this.deps = new Map();
    }
}
exports.DepGraph = DepGraph;
// Recompute a cell and cascade to dependents
// recalcing tracks which cells are currently being recalculated to prevent infinite recursion
function recalcCell(sheet, key, depGraph, recalcing) {
    if (!recalcing)
        recalcing = new Set();
    if (recalcing.has(key))
        return;
    recalcing.add(key);
    const parsed = (0, state_1.parseKey)(key);
    if (!parsed)
        return;
    const [c, r] = parsed;
    const cell = sheet.get(c, r);
    if (!cell.raw || !cell.raw.startsWith('=')) {
        sheet.set(c, r, { raw: cell.raw || '', value: cell.raw || '', isError: false });
    }
    else {
        const ast = parseFormula(cell.raw);
        let result;
        try {
            result = evaluate(sheet, ast, new Set(), new Set());
        }
        catch {
            result = makeEvalError(config_1.ERROR_MSGS.ERR);
        }
        if (isEvalError(result)) {
            sheet.set(c, r, { raw: cell.raw, value: result._error, isError: true });
        }
        else {
            const display = typeof result === 'number' ? formatNumber(result) : String(result);
            sheet.set(c, r, { raw: cell.raw, value: display, isError: false });
        }
    }
    // Cascade to dependents
    const dependents = depGraph.getDependents(key);
    for (const dep of dependents) {
        recalcCell(sheet, dep, depGraph, recalcing);
    }
}
// For full recalc, detect cycles by walking the dependency graph
function detectCycles(depGraph, keys) {
    const cycleCells = new Set();
    const WHITE = 0, GRAY = 1, BLACK = 2;
    const color = new Map();
    for (const k of keys)
        color.set(k, WHITE);
    function dfs(k, path) {
        color.set(k, GRAY);
        path.push(k);
        for (const dep of depGraph.getDependents(k)) {
            if (!color.has(dep)) {
                color.set(dep, WHITE);
            }
            if (color.get(dep) === GRAY) {
                // Found cycle - mark all cells in the cycle
                const idx = path.indexOf(dep);
                for (let i = idx; i < path.length; i++) {
                    cycleCells.add(path[i]);
                }
            }
            else if (color.get(dep) === WHITE) {
                dfs(dep, path);
            }
        }
        path.pop();
        color.set(k, BLACK);
    }
    for (const k of keys) {
        if (color.get(k) === WHITE) {
            dfs(k, []);
        }
    }
    return cycleCells;
}
// Recompute a cell's dependencies and recalc it
function recalcAndCascade(sheet, key, depGraph) {
    recalcCell(sheet, key, depGraph, null);
}
// Rebuild dependency graph from scratch
function rebuildDeps(sheet, depGraph) {
    depGraph.clear();
    sheet.forEach((c, r, cell) => {
        if (!cell.raw || !cell.raw.startsWith('='))
            return;
        const ast = parseFormula(cell.raw);
        const refs = collectRefs(ast);
        const key = (0, state_1.cellKey)(c, r);
        for (const ref of refs) {
            const [rc, rr] = ref.split(',').map(Number);
            const refKey = (0, state_1.cellKey)(rc, rr);
            depGraph.add(refKey, key);
        }
    });
}
// Full recalculation
function fullRecalc(sheet, depGraph) {
    rebuildDeps(sheet, depGraph);
    sheet.forEach((c, r, cell) => {
        if (cell.raw && cell.raw.startsWith('=')) {
            recalcCell(sheet, (0, state_1.cellKey)(c, r), depGraph, null);
        }
        else {
            sheet.set(c, r, { raw: cell.raw || '', value: cell.raw || '', isError: false });
        }
    });
}
function formatNumber(n) {
    if (Number.isInteger(n))
        return String(n);
    return parseFloat(n.toFixed(10)).toString();
}
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSelected = getSelected;
exports.setSelected = setSelected;
exports.buildGrid = buildGrid;
exports.renderCell = renderCell;
exports.renderAll = renderAll;
exports.getCellElement = getCellElement;
exports.updateCellRefDisplay = updateCellRefDisplay;
exports.startEdit = startEdit;
exports.finishEdit = finishEdit;
exports.moveSelection = moveSelection;
exports.setSheet = setSheet;
exports.setSheetSize = setSheetSize;
const state_1 = require("./state");
const formula_1 = require("./formula");
let table;
let selectedCell = null;
let editingCell = null;
let editingInput = null;
function getSelected() { return selectedCell; }
function setSelected(c, r) { selectedCell = { col: c, row: r }; }
function buildGrid(sheet) {
    table = document.getElementById('grid');
    table.innerHTML = '';
    // Build header row
    const thead = document.createElement('thead');
    const headerRow = document.createElement('tr');
    const corner = document.createElement('th');
    corner.className = 'corner';
    headerRow.appendChild(corner);
    for (let c = 0; c < sheet.cols; c++) {
        const th = document.createElement('th');
        th.className = 'col-header';
        th.textContent = (0, state_1.colName)(c);
        th.dataset.col = String(c);
        headerRow.appendChild(th);
    }
    thead.appendChild(headerRow);
    table.appendChild(thead);
    // Build body
    const tbody = document.createElement('tbody');
    for (let r = 0; r < sheet.rows; r++) {
        const tr = document.createElement('tr');
        const rowHeader = document.createElement('td');
        rowHeader.className = 'row-header';
        rowHeader.textContent = String(r + 1);
        tr.appendChild(rowHeader);
        for (let c = 0; c < sheet.cols; c++) {
            const td = document.createElement('td');
            td.dataset.col = String(c);
            td.dataset.row = String(r);
            td.className = 'cell';
            tr.appendChild(td);
        }
        tbody.appendChild(tr);
    }
    table.appendChild(tbody);
}
function renderCell(td, data) {
    td.textContent = data.value;
    td.className = 'cell';
    if (data.isError)
        td.classList.add('cell-error');
    const n = parseFloat(data.value);
    if (!isNaN(n) && data.value !== '' && !data.isError) {
        td.classList.add('cell-number');
    }
}
function renderAll(sheet) {
    const tbody = table.querySelector('tbody');
    if (!tbody)
        return;
    const rows = tbody.querySelectorAll('tr');
    for (let r = 0; r < rows.length; r++) {
        const tds = rows[r].querySelectorAll('td:not(.row-header)');
        for (let c = 0; c < tds.length; c++) {
            const data = sheet.get(c, r);
            renderCell(tds[c], data);
        }
    }
    if (selectedCell)
        highlightCell(selectedCell.col, selectedCell.row);
}
function highlightCell(c, r) {
    // Remove previous selection
    const prev = table.querySelector('.cell-selected');
    if (prev)
        prev.classList.remove('cell-selected');
    // Remove header highlights
    table.querySelectorAll('.col-header-active').forEach(el => el.classList.remove('col-header-active'));
    table.querySelectorAll('.row-header-active').forEach(el => el.classList.remove('row-header-active'));
    // Add selection
    const td = table.querySelector(`td[data-col="${c}"][data-row="${r}"]:not(.row-header)`);
    if (td)
        td.classList.add('cell-selected');
    // Highlight headers
    const colHeader = table.querySelector(`th.col-header[data-col="${c}"]`);
    if (colHeader)
        colHeader.classList.add('col-header-active');
    const rowHeader = table.querySelector(`td.row-header[data-row="${r}"]`);
    if (rowHeader)
        rowHeader.classList.add('row-header-active');
}
function getCellElement(c, r) {
    return table.querySelector(`td[data-col="${c}"][data-row="${r}"]:not(.row-header)`);
}
function updateCellRefDisplay() {
    const el = document.getElementById('cell-ref');
    if (!el)
        return;
    if (selectedCell) {
        el.textContent = (0, state_1.cellKey)(selectedCell.col, selectedCell.row);
    }
    else {
        el.textContent = '';
    }
}
function startEdit(sheet) {
    if (!selectedCell || editingCell)
        return;
    const c = selectedCell.col;
    const r = selectedCell.row;
    editingCell = { col: c, row: r };
    const td = getCellElement(c, r);
    if (!td)
        return;
    const cellData = sheet.get(c, r);
    td.textContent = '';
    const input = document.createElement('input');
    input.type = 'text';
    input.className = 'cell-input';
    input.value = cellData.raw;
    input.spellcheck = false;
    input.autocomplete = 'off';
    td.appendChild(input);
    input.focus();
    input.select();
    editingInput = input;
    // Sync formula bar
    const formulaInput = document.getElementById('formula-input');
    if (formulaInput)
        formulaInput.value = cellData.raw;
    input.addEventListener('blur', () => commitEdit(sheet, c, r));
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            commitEdit(sheet, c, r);
            moveSelection(0, 1);
        }
        else if (e.key === 'Tab') {
            e.preventDefault();
            commitEdit(sheet, c, r);
            moveSelection(1, 0);
        }
        else if (e.key === 'Escape') {
            cancelEdit(sheet, c, r);
        }
    });
    // Sync formula bar while typing
    input.addEventListener('input', () => {
        const fb = document.getElementById('formula-input');
        if (fb)
            fb.value = input.value;
    });
}
function commitEdit(sheet, c, r) {
    if (editingInput) {
        const raw = editingInput.value;
        finishEdit(sheet, c, r, raw, currentDepGraph);
    }
    else {
        finishEdit(sheet, c, r, '', currentDepGraph);
    }
}
function finishEdit(sheet, c, r, raw, depGraph) {
    editingCell = null;
    editingInput = null;
    // Remove input from td
    const td = getCellElement(c, r);
    if (td) {
        const inp = td.querySelector('input');
        if (inp)
            inp.remove();
    }
    // Store raw value
    const cellData = sheet.get(c, r);
    const wasFormula = cellData.raw && cellData.raw.startsWith('=');
    sheet.set(c, r, { raw, value: '', isError: false });
    // Recalculate
    (0, formula_1.rebuildDeps)(sheet, depGraph);
    (0, formula_1.recalcAndCascade)(sheet, (0, state_1.cellKey)(c, r), depGraph);
    // Re-render
    const newData = sheet.get(c, r);
    const td2 = getCellElement(c, r);
    if (td2)
        renderCell(td2, newData);
    if (selectedCell)
        highlightCell(selectedCell.col, selectedCell.row);
    // Save
    sheet.save();
    // Sync formula bar
    const formulaInput = document.getElementById('formula-input');
    if (formulaInput && selectedCell) {
        const sd = sheet.get(selectedCell.col, selectedCell.row);
        formulaInput.value = sd.raw;
    }
}
function cancelEdit(sheet, c, r) {
    editingCell = null;
    editingInput = null;
    const td = getCellElement(c, r);
    if (td) {
        const inp = td.querySelector('input');
        if (inp)
            inp.remove();
        const data = sheet.get(c, r);
        renderCell(td, data);
    }
    if (selectedCell)
        highlightCell(selectedCell.col, selectedCell.row);
}
function moveSelection(dc, dr) {
    if (!selectedCell)
        return;
    let nc = Math.max(0, Math.min(sheet_cols - 1, selectedCell.col + dc));
    let nr = Math.max(0, Math.min(sheet_rows - 1, selectedCell.row + dr));
    selectedCell = { col: nc, row: nr };
    highlightCell(nc, nr);
    updateCellRefDisplay();
    const data = currentSheet.get(nc, nr);
    const formulaInput = document.getElementById('formula-input');
    if (formulaInput)
        formulaInput.value = data.raw;
    // Scroll into view
    const td = getCellElement(nc, nr);
    if (td)
        td.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
let sheet_cols = 26;
let sheet_rows = 100;
let currentSheet = null;
let currentDepGraph = null;
function setSheet(sheet, depGraph) {
    currentSheet = sheet;
    currentDepGraph = depGraph;
}
// Public API
function setSheetSize(cols, rows) {
    sheet_cols = cols;
    sheet_rows = rows;
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"ThinkingCap-Qwen3.6-27B-GGUF","provider":"brigid","persona":"typescript","sessionId":"c87a7375-990a-48b7-b1e1-c4cab5458d74","tokensIn":1251558,"tokensOut":35590,"tokensTotal":1287148,"cost":0,"turns":44,"toolCalls":44,"failedToolCalls":1,"timestamp":"2026-08-03T20:01:45.305Z"} -->