← Spreadsheet results

Spreadsheet

Qwen 3.6 27B Fable Fusion 711 Uncensored Heretic NM DAU NEO MAX MTP GGUF · typescript

Initial view of Spreadsheet

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 51,204 bytes · SHA-256 2f759ba73877
<!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: layout ── */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 13px;
  color: #202124;
  background: #ffffff;
  overflow: hidden;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

#app {
  display: flex;
  flex-direction: column;
  height: 100%;
}

#toolbar {
  display: flex;
  gap: 4px;
  padding: 4px 8px;
  background: #f1f3f4;
  border-bottom: 1px solid #dadce0;
  flex-shrink: 0;
}

#toolbar button {
  padding: 4px 10px;
  font-size: 12px;
  border: 1px solid #dadce0;
  border-radius: 3px;
  background: #ffffff;
  cursor: pointer;
  color: #202124;
  transition: background 0.1s;
}

#toolbar button:hover {
  background: #e8eaed;
}

#formula-bar {
  display: flex;
  align-items: center;
  padding: 4px 8px;
  background: #ffffff;
  border-bottom: 1px solid #dadce0;
  gap: 8px;
  flex-shrink: 0;
}

#cell-ref {
  min-width: 60px;
  text-align: center;
  font-weight: 600;
  color: #5f6368;
  font-size: 13px;
  border: 1px solid #dadce0;
  border-radius: 3px;
  padding: 4px 6px;
  background: #f8f9fa;
}

#formula-input {
  flex: 1;
  padding: 4px 8px;
  border: 1px solid #dadce0;
  border-radius: 3px;
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: border-color 0.1s;
}

#formula-input:focus {
  border-color: #1a73e8;
  box-shadow: 0 0 0 1px rgba(26, 115, 232, 0.3);
}

#grid-container {
  flex: 1;
  overflow: auto;
  position: relative;
}

#grid-wrapper {
  position: relative;
}

#corner-header {
  position: sticky;
  top: 0;
  left: 0;
  width: 45px;
  height: 25px;
  background: #f8f9fa;
  border-right: 1px solid #dadce0;
  border-bottom: 1px solid #dadce0;
  z-index: 20;
}

#col-headers {
  position: sticky;
  top: 0;
  display: flex;
  background: #f8f9fa;
  border-bottom: 1px solid #dadce0;
  z-index: 15;
}

.col-header {
  flex-shrink: 0;
  width: 100px;
  height: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 12px;
  color: #5f6368;
  border-right: 1px solid #e0e0e0;
  cursor: default;
  user-select: none;
}

.col-header:hover {
  background: #e8eaed;
}

#rows-container {
  position: relative;
}

.grid-row {
  display: flex;
}

.row-header {
  position: sticky;
  left: 0;
  width: 45px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: #5f6368;
  background: #f8f9fa;
  border-right: 1px solid #dadce0;
  border-bottom: 1px solid #e0e0e0;
  z-index: 10;
  user-select: none;
}

.row-header:hover {
  background: #e8eaed;
}

.cell {
  flex-shrink: 0;
  width: 100px;
  height: 24px;
  padding: 0 4px;
  border-right: 1px solid #e0e0e0;
  border-bottom: 1px solid #e0e0e0;
  display: flex;
  align-items: center;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  cursor: cell;
  font-size: 13px;
  user-select: none;
  position: relative;
}

.cell:hover {
  background: #f5f8ff;
}

.cell.selected {
  outline: 2px solid #1a73e8;
  outline-offset: -2px;
  background: rgba(26, 115, 232, 0.08);
  z-index: 5;
}

.cell.editing {
  background: #ffffff;
  cursor: text;
  user-select: text;
}

.cell.right-aligned {
  justify-content: flex-end;
}

.cell.error {
  color: #d93025;
  font-family: 'SF Mono', 'Consolas', monospace;
  font-size: 12px;
}

.cell input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: none;
  padding: 0 4px;
  font-size: 13px;
  font-family: inherit;
  outline: none;
  background: transparent;
}
</style>
</head>
<body>
<div id="app">
  <div id="toolbar">
    <button id="btn-insert-row" title="Insert row">+ Row</button>
    <button id="btn-delete-row" title="Delete row">- Row</button>
    <button id="btn-insert-col" title="Insert column">+ Col</button>
    <button id="btn-delete-col" title="Delete column">- Col</button>
  </div>
  <div id="formula-bar">
    <span id="cell-ref"></span>
    <input id="formula-input" type="text" autocomplete="off" spellcheck="false">
  </div>
  <div id="grid-container">
    <div id="grid-wrapper">
      <div id="corner-header"></div>
      <div id="col-headers"></div>
      <div id="rows-container"></div>
    </div>
  </div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./cellstore":"src/cellstore.ts","./grid":"src/grid.ts","./storage":"src/storage.ts"},"src/tokenizer.ts":{"./constants":"src/constants.ts"},"src/evaluator.ts":{"./parser":"src/parser.ts","./constants":"src/constants.ts"},"src/cellstore.ts":{"./constants":"src/constants.ts","./parser":"src/parser.ts","./evaluator":"src/evaluator.ts"},"src/grid.ts":{"./constants":"src/constants.ts","./cellstore":"src/cellstore.ts"},"src/storage.ts":{"./constants":"src/constants.ts","./cellstore":"src/cellstore.ts"},"src/parser.ts":{"./tokenizer":"src/tokenizer.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 });
exports.main = main;
const cellstore_1 = require("./cellstore");
const grid_1 = require("./grid");
const storage_1 = require("./storage");
function main() {
    const store = new cellstore_1.CellStore();
    // Exposed for testing/debugging only
    window.__store = store;
    // Load persisted data
    (0, storage_1.loadFromStorage)(store);
    // Create the grid renderer
    const grid = new grid_1.GridRenderer(store);
    // Auto-save on changes
    store.subscribe(() => (0, storage_1.saveToStorage)(store));
}
// Run at startup — DOM is already parsed
main();
};

// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONFIG = void 0;
exports.colToLetter = colToLetter;
exports.letterToCol = letterToCol;
exports.cellKey = cellKey;
exports.parseCellRef = parseCellRef;
exports.isValidCellRef = isValidCellRef;
// Tunable constants
exports.CONFIG = {
    ROWS: 100,
    COLS: 26,
    CELL_WIDTH: 100,
    CELL_HEIGHT: 24,
    HEADER_WIDTH: 45,
    HEADER_HEIGHT: 25,
    STORAGE_KEY: "spreadsheet_state_v1",
    COLORS: {
        bg: "#ffffff",
        headerBg: "#f8f9fa",
        gridLine: "#e0e0e0",
        selectionBorder: "#1a73e8",
        selectionBg: "rgba(26, 115, 232, 0.08)",
        error: "#d93025",
        text: "#202124",
        hoverHeaderBg: "#e8eaed",
    },
};
// Column letter helpers
function colToLetter(col) {
    return String.fromCharCode(65 + col);
}
function letterToCol(letter) {
    return letter.charCodeAt(0) - 65;
}
// Cell reference helpers
function cellKey(col, row) {
    return `${colToLetter(col)}${row + 1}`;
}
function parseCellRef(ref) {
    const m = /^([A-Z])(\d+)$/.exec(ref);
    if (!m)
        return null;
    const col = letterToCol(m[1]);
    const row = parseInt(m[2], 10) - 1;
    if (col < 0 || col >= exports.CONFIG.COLS || row < 0 || row >= exports.CONFIG.ROWS)
        return null;
    return { col, row };
}
function isValidCellRef(ref) {
    return parseCellRef(ref) !== null;
}
};

// ── module: src/tokenizer.ts ──
__mods["src/tokenizer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.tokenize = tokenize;
function tokenize(input) {
    const tokens = [];
    let i = 0;
    const len = input.length;
    while (i < len) {
        const ch = input[i];
        // Skip whitespace
        if (/\s/.test(ch)) {
            i++;
            continue;
        }
        // Numbers (including decimals)
        if (/\d/.test(ch) || (ch === "." && i + 1 < len && /\d/.test(input[i + 1]))) {
            let num = "";
            while (i < len && (/\d/.test(input[i]) || input[i] === ".")) {
                num += input[i];
                i++;
            }
            tokens.push({ type: "NUMBER", value: parseFloat(num), pos: i - num.length });
            continue;
        }
        // Identifiers (function names, cell references like A1, B10)
        if (/[A-Za-z]/.test(ch)) {
            let id = "";
            while (i < len && /[A-Za-z0-9]/.test(input[i])) {
                id += input[i];
                i++;
            }
            tokens.push({ type: "IDENT", value: id.toUpperCase(), pos: i - id.length });
            continue;
        }
        // Operators (excluding parens, comma, colon which have their own types)
        if ("+-*/".includes(ch)) {
            if (ch === "-") {
                // Unary minus if at start, after (, after comma, or after another operator
                const prev = tokens[tokens.length - 1];
                if (tokens.length === 0 ||
                    prev?.type === "OP" ||
                    prev?.type === "UNARY_MINUS" ||
                    prev?.type === "LPAREN" ||
                    prev?.type === "COMMA" ||
                    prev?.type === "COLON") {
                    tokens.push({ type: "UNARY_MINUS", value: "-", pos: i });
                }
                else {
                    tokens.push({ type: "OP", value: "-", pos: i });
                }
            }
            else if (ch === "+") {
                tokens.push({ type: "OP", value: "+", pos: i });
            }
            else {
                tokens.push({ type: "OP", value: ch, pos: i });
            }
            i++;
            continue;
        }
        // Parentheses
        if (ch === "(") {
            tokens.push({ type: "LPAREN", value: "(", pos: i });
            i++;
            continue;
        }
        if (ch === ")") {
            tokens.push({ type: "RPAREN", value: ")", pos: i });
            i++;
            continue;
        }
        // Colon for ranges
        if (ch === ":") {
            tokens.push({ type: "COLON", value: ":", pos: i });
            i++;
            continue;
        }
        // Comma
        if (ch === ",") {
            tokens.push({ type: "COMMA", value: ",", pos: i });
            i++;
            continue;
        }
        // Unknown character -> error token
        tokens.push({ type: "IDENT", value: ch, pos: i });
        i++;
    }
    return tokens;
}
};

// ── module: src/evaluator.ts ──
__mods["src/evaluator.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERR_CYCLE = exports.ERR_PARSE = exports.ERR_DIV0 = exports.ERR_REF = void 0;
exports.isFormula = isFormula;
exports.extractFormula = extractFormula;
exports.extractReferences = extractReferences;
exports.evaluate = evaluate;
const constants_1 = require("./constants");
// Errors
exports.ERR_REF = "#REF!";
exports.ERR_DIV0 = "#DIV/0!";
exports.ERR_PARSE = "#ERR!";
exports.ERR_CYCLE = "#CYCLE!";
function isFormula(raw) {
    return raw.startsWith("=");
}
function extractFormula(raw) {
    if (!isFormula(raw))
        return "";
    return raw.slice(1);
}
// Extract all cell references and ranges from an AST
function extractReferences(node, refs = new Set()) {
    if (!node || node.type === "error")
        return refs;
    switch (node.type) {
        case "cell":
            if ((0, constants_1.parseCellRef)(node.ref))
                refs.add(node.ref);
            break;
        case "range": {
            const start = (0, constants_1.parseCellRef)(node.start);
            const end = (0, constants_1.parseCellRef)(node.end);
            if (!start || !end)
                break;
            const minC = Math.min(start.col, end.col);
            const maxC = Math.max(start.col, end.col);
            const minR = Math.min(start.row, end.row);
            const maxR = Math.max(start.row, end.row);
            for (let c = minC; c <= maxC; c++) {
                for (let r = minR; r <= maxR; r++) {
                    refs.add((0, constants_1.cellKey)(c, r));
                }
            }
            break;
        }
        case "binop":
            extractReferences(node.left, refs);
            extractReferences(node.right, refs);
            break;
        case "unary":
            extractReferences(node.operand, refs);
            break;
        case "call":
            for (const arg of node.args)
                extractReferences(arg, refs);
            break;
    }
    return refs;
}
// Evaluate a single AST node given a cell reader
// The cell reader maps cell ref -> CellValue
function evaluate(node, readCell, evaluating, currentCell) {
    if (!node)
        return { type: "error", code: exports.ERR_PARSE };
    switch (node.type) {
        case "error":
            return { type: "error", code: exports.ERR_PARSE };
        case "number":
            return { type: "number", value: node.value };
        case "cell": {
            const val = readCell(node.ref);
            if (val.type === "error")
                return val;
            if (val.type === "empty")
                return { type: "number", value: 0 };
            if (val.type === "text") {
                // Try to parse as number
                const n = parseFloat(val.value);
                return isNaN(n) ? { type: "number", value: 0 } : { type: "number", value: n };
            }
            return val;
        }
        case "range": {
            // Ranges in function arguments are passed as arrays
            // If a range appears outside a function context, it's an error
            return { type: "error", code: exports.ERR_PARSE };
        }
        case "unary": {
            const operand = evaluate(node.operand, readCell, evaluating, currentCell);
            if (operand.type === "error")
                return operand;
            if (operand.type === "number") {
                return { type: "number", value: -operand.value };
            }
            return { type: "error", code: exports.ERR_PARSE };
        }
        case "binop": {
            const left = evaluate(node.left, readCell, evaluating, currentCell);
            if (left.type === "error")
                return left;
            const right = evaluate(node.right, readCell, evaluating, currentCell);
            if (right.type === "error")
                return right;
            const ln = toNumber(left);
            const rn = toNumber(right);
            if (ln === null || rn === null)
                return { type: "error", code: exports.ERR_PARSE };
            switch (node.op) {
                case "+": return { type: "number", value: ln + rn };
                case "-": return { type: "number", value: ln - rn };
                case "*": return { type: "number", value: ln * rn };
                case "/":
                    if (rn === 0)
                        return { type: "error", code: exports.ERR_DIV0 };
                    return { type: "number", value: ln / rn };
            }
            return { type: "error", code: exports.ERR_PARSE };
        }
        case "call": {
            return evaluateFunction(node.name, node.args, readCell, evaluating, currentCell);
        }
    }
    return { type: "error", code: exports.ERR_PARSE };
}
function toNumber(val) {
    if (val.type === "number")
        return val.value;
    if (val.type === "text") {
        const n = parseFloat(val.value);
        return isNaN(n) ? null : n;
    }
    if (val.type === "empty")
        return 0;
    return null;
}
function evaluateArg(node, readCell, evaluating, currentCell) {
    if (node.type === "range") {
        const start = (0, constants_1.parseCellRef)(node.start);
        const end = (0, constants_1.parseCellRef)(node.end);
        if (!start || !end)
            return { scalar: false, numbers: [] };
        const minC = Math.min(start.col, end.col);
        const maxC = Math.max(start.col, end.col);
        const minR = Math.min(start.row, end.row);
        const maxR = Math.max(start.row, end.row);
        const numbers = [];
        for (let c = minC; c <= maxC; c++) {
            for (let r = minR; r <= maxR; r++) {
                const val = readCell((0, constants_1.cellKey)(c, r));
                if (val.type === "error")
                    continue;
                if (val.type === "empty")
                    continue;
                const n = val.type === "number" ? val.value : parseFloat(val.value);
                if (!isNaN(n))
                    numbers.push(n);
            }
        }
        return { scalar: false, numbers };
    }
    const val = evaluate(node, readCell, evaluating, currentCell);
    if (val.type === "error")
        return { scalar: true, numbers: [] };
    if (val.type === "empty")
        return { scalar: true, numbers: [] };
    const n = val.type === "number" ? val.value : parseFloat(val.value);
    return isNaN(n) ? { scalar: true, numbers: [] } : { scalar: true, numbers: [n] };
}
function evaluateFunction(name, args, readCell, evaluating, currentCell) {
    const upperName = name.toUpperCase();
    const allNumbers = [];
    for (const arg of args) {
        // First check if the argument node (or nested nodes) reference cells we're currently evaluating
        if (arg.type !== "number") {
            const refs = new Set();
            if (arg.type === "cell")
                refs.add(arg.ref);
            else if (arg.type === "range") {
                const s = (0, constants_1.parseCellRef)(arg.start);
                const e = (0, constants_1.parseCellRef)(arg.end);
                if (s && e) {
                    const minC = Math.min(s.col, e.col);
                    const maxC = Math.max(s.col, e.col);
                    const minR = Math.min(s.row, e.row);
                    const maxR = Math.max(s.row, e.row);
                    for (let c = minC; c <= maxC; c++) {
                        for (let r = minR; r <= maxR; r++) {
                            refs.add((0, constants_1.cellKey)(c, r));
                        }
                    }
                }
            }
            for (const ref of refs) {
                if (evaluating.has(ref)) {
                    return { type: "error", code: exports.ERR_CYCLE };
                }
            }
        }
        const evaluated = evaluateArg(arg, readCell, evaluating, currentCell);
        allNumbers.push(...evaluated.numbers);
    }
    switch (upperName) {
        case "SUM":
            return { type: "number", value: allNumbers.reduce((a, b) => a + b, 0) };
        case "AVG":
        case "AVERAGE":
            if (allNumbers.length === 0)
                return { type: "error", code: exports.ERR_DIV0 };
            return { type: "number", value: allNumbers.reduce((a, b) => a + b, 0) / allNumbers.length };
        case "MIN":
            if (allNumbers.length === 0)
                return { type: "number", value: 0 };
            return { type: "number", value: Math.min(...allNumbers) };
        case "MAX":
            if (allNumbers.length === 0)
                return { type: "number", value: 0 };
            return { type: "number", value: Math.max(...allNumbers) };
        case "COUNT":
            return { type: "number", value: allNumbers.length };
        default:
            return { type: "error", code: exports.ERR_PARSE };
    }
}
};

// ── module: src/cellstore.ts ──
__mods["src/cellstore.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CellStore = void 0;
exports.formatValue = formatValue;
exports.isNumericValue = isNumericValue;
const constants_1 = require("./constants");
const parser_1 = require("./parser");
const evaluator_1 = require("./evaluator");
class CellStore {
    constructor() {
        // Raw cell data
        this.cells = new Map();
        // Parsed AST cache
        this.parsedCache = new Map();
        // Dependency graph: cell -> cells it depends on
        this.deps = new Map();
        // Reverse dependency graph: cell -> cells that depend on it
        this.dependents = new Map();
        // Listeners
        this.listeners = new Set();
    }
    getRaw(ref) {
        return this.cells.get(ref) ?? "";
    }
    hasData(ref) {
        return this.cells.has(ref) && this.cells.get(ref) !== "";
    }
    setCell(col, row, raw) {
        const ref = (0, constants_1.cellKey)(col, row);
        this.setCellRef(ref, raw);
    }
    setCellRef(ref, raw) {
        const oldRaw = this.cells.get(ref) ?? "";
        const isNowFormula = (0, evaluator_1.isFormula)(raw);
        const wasFormula = (0, evaluator_1.isFormula)(oldRaw);
        // Parse new formula if applicable
        let newDeps = new Set();
        if (isNowFormula) {
            const ast = (0, parser_1.parseFormula)((0, evaluator_1.extractFormula)(raw));
            this.parsedCache.set(ref, ast);
            if (ast.type !== "error") {
                newDeps = (0, evaluator_1.extractReferences)(ast);
            }
        }
        else {
            this.parsedCache.delete(ref);
        }
        // Remove old dependency edges from this cell
        const oldDeps = this.deps.get(ref) ?? new Set();
        for (const dep of oldDeps) {
            const set = this.dependents.get(dep);
            if (set)
                set.delete(ref);
            if (set && set.size === 0)
                this.dependents.delete(dep);
        }
        // Store raw value
        if (raw === "") {
            this.cells.delete(ref);
        }
        else {
            this.cells.set(ref, raw);
        }
        // Set new dependency edges
        this.deps.set(ref, newDeps);
        for (const dep of newDeps) {
            if (!this.dependents.has(dep))
                this.dependents.set(dep, new Set());
            this.dependents.get(dep).add(ref);
        }
        // Invalidate this cell and all its dependents, then recompute in order
        this.recalculateFrom(ref);
        this.notify();
    }
    getValue(ref) {
        return this.evaluateCell(ref, new Set());
    }
    clearCell(col, row) {
        this.setCellRef((0, constants_1.cellKey)(col, row), "");
    }
    deleteRow(row) {
        // Clear cells in deleted row
        const toDelete = [];
        for (const ref of this.cells.keys()) {
            const p = (0, constants_1.parseCellRef)(ref);
            if (p && p.row === row)
                toDelete.push(ref);
        }
        for (const ref of toDelete)
            this.setCellRef(ref, "");
        // For deleted row R: rows > R shift up by 1 in references
        this.shiftFormulaRefs((r) => r > row ? r - 1 : r, null);
        this.notify();
    }
    deleteCol(col) {
        const toDelete = [];
        for (const ref of this.cells.keys()) {
            const p = (0, constants_1.parseCellRef)(ref);
            if (p && p.col === col)
                toDelete.push(ref);
        }
        for (const ref of toDelete)
            this.setCellRef(ref, "");
        this.shiftFormulaRefs(null, (c) => c > col ? c - 1 : c);
        this.notify();
    }
    insertRow(row) {
        this.shiftFormulaRefs((r) => r >= row ? r + 1 : r, null);
        this.notify();
    }
    insertCol(col) {
        this.shiftFormulaRefs(null, (c) => c >= col ? c + 1 : c);
        this.notify();
    }
    toJSON() {
        const obj = {};
        for (const [ref, raw] of this.cells) {
            obj[ref] = raw;
        }
        return obj;
    }
    fromJSON(data) {
        this.cells.clear();
        this.parsedCache.clear();
        this.deps.clear();
        this.dependents.clear();
        // Store all raw values
        for (const [ref, raw] of Object.entries(data)) {
            const p = (0, constants_1.parseCellRef)(ref);
            if (p && raw !== "") {
                this.cells.set(ref, raw);
            }
        }
        // Build dependency graph
        for (const [ref, raw] of this.cells) {
            if ((0, evaluator_1.isFormula)(raw)) {
                const ast = (0, parser_1.parseFormula)((0, evaluator_1.extractFormula)(raw));
                this.parsedCache.set(ref, ast);
                if (ast.type !== "error") {
                    const deps = (0, evaluator_1.extractReferences)(ast);
                    this.deps.set(ref, deps);
                    for (const dep of deps) {
                        if (!this.dependents.has(dep))
                            this.dependents.set(dep, new Set());
                        this.dependents.get(dep).add(ref);
                    }
                }
            }
        }
        // Evaluate all formula cells
        const formulaCells = [...this.cells.keys()].filter((r) => (0, evaluator_1.isFormula)(this.cells.get(r)));
        for (const ref of formulaCells) {
            this.evaluateCell(ref, new Set());
        }
        this.notify();
    }
    subscribe(listener) {
        this.listeners.add(listener);
        return () => this.listeners.delete(listener);
    }
    // Evaluate a single cell (for getValue calls)
    evaluateCell(ref, evaluating) {
        const raw = this.cells.get(ref);
        if (!raw || raw === "")
            return { type: "empty" };
        if (!(0, evaluator_1.isFormula)(raw)) {
            const n = parseFloat(raw);
            if (!isNaN(n))
                return { type: "number", value: n };
            return { type: "text", value: raw };
        }
        const ast = this.parsedCache.get(ref);
        if (!ast || ast.type === "error") {
            return { type: "error", code: evaluator_1.ERR_PARSE };
        }
        // Check cycle
        if (evaluating.has(ref)) {
            return { type: "error", code: evaluator_1.ERR_CYCLE };
        }
        evaluating.add(ref);
        const readCell = (depRef) => {
            const cached = this.tryReadSimple(depRef);
            if (cached !== null)
                return cached;
            // Recursively evaluate a formula cell
            return this.evaluateCell(depRef, evaluating);
        };
        const result = (0, evaluator_1.evaluate)(ast, readCell, evaluating, ref);
        evaluating.delete(ref);
        return result;
    }
    // Read a cell that doesn't need formula evaluation (literal or empty)
    tryReadSimple(ref) {
        const raw = this.cells.get(ref);
        if (!raw || raw === "")
            return { type: "empty" };
        if (!(0, evaluator_1.isFormula)(raw)) {
            const n = parseFloat(raw);
            if (!isNaN(n))
                return { type: "number", value: n };
            return { type: "text", value: raw };
        }
        return null; // Formula cell - needs evaluation
    }
    // Recalculate starting from a changed cell, cascading to all dependents
    recalculateFrom(changedRef) {
        // Collect all affected cells in topological order (changed cell first, then dependents)
        const affected = this.topologicalSort(changedRef);
        for (const ref of affected) {
            // Force re-evaluation (no caching)
            this.evaluateCell(ref, new Set());
        }
    }
    // Topological sort of affected cells using BFS
    topologicalSort(start) {
        const result = [];
        const inDegree = new Map();
        const queue = [];
        // Find all affected cells (those reachable from start via dependency edges)
        const affected = new Set();
        const visitQueue = [start];
        affected.add(start);
        while (visitQueue.length > 0) {
            const curr = visitQueue.shift();
            const deps = this.dependents.get(curr);
            if (deps) {
                for (const dep of deps) {
                    if (!affected.has(dep)) {
                        affected.add(dep);
                        visitQueue.push(dep);
                    }
                }
            }
        }
        // Compute in-degrees (within affected set) for topological sort
        for (const ref of affected) {
            const deps = this.deps.get(ref);
            let count = 0;
            if (deps) {
                for (const dep of deps) {
                    if (affected.has(dep))
                        count++;
                }
            }
            inDegree.set(ref, count);
        }
        // Start with cells that have no affected dependencies
        for (const ref of affected) {
            if (inDegree.get(ref) === 0) {
                queue.push(ref);
            }
        }
        while (queue.length > 0) {
            const curr = queue.shift();
            result.push(curr);
            const dependents = this.dependents.get(curr);
            if (dependents) {
                for (const dep of dependents) {
                    if (affected.has(dep)) {
                        const deg = inDegree.get(dep) - 1;
                        inDegree.set(dep, deg);
                        if (deg === 0)
                            queue.push(dep);
                    }
                }
            }
        }
        // Handle cycles: add remaining affected cells that weren't processed
        for (const ref of affected) {
            if (!result.includes(ref)) {
                result.push(ref);
            }
        }
        return result;
    }
    // Shift references in all formulas based on row/column mapping
    shiftFormulaRefs(mapRow, mapCol) {
        const newCells = new Map();
        for (const [ref, raw] of this.cells) {
            if (!(0, evaluator_1.isFormula)(raw)) {
                newCells.set(ref, raw);
                continue;
            }
            const formula = (0, evaluator_1.extractFormula)(raw);
            const newFormula = this.shiftRefsInFormula(formula, mapRow, mapCol);
            newCells.set(ref, "=" + newFormula);
        }
        this.cells = newCells;
        this.parsedCache.clear();
        this.deps.clear();
        this.dependents.clear();
        // Rebuild graph
        for (const [ref, raw] of this.cells) {
            if ((0, evaluator_1.isFormula)(raw)) {
                const ast = (0, parser_1.parseFormula)((0, evaluator_1.extractFormula)(raw));
                this.parsedCache.set(ref, ast);
                if (ast.type !== "error") {
                    const deps = (0, evaluator_1.extractReferences)(ast);
                    this.deps.set(ref, deps);
                    for (const dep of deps) {
                        if (!this.dependents.has(dep))
                            this.dependents.set(dep, new Set());
                        this.dependents.get(dep).add(ref);
                    }
                }
            }
        }
        // Re-evaluate all formula cells
        const formulaCells = [...this.cells.keys()].filter((r) => (0, evaluator_1.isFormula)(this.cells.get(r)));
        for (const ref of formulaCells) {
            this.evaluateCell(ref, new Set());
        }
    }
    // Walk formula string and shift cell references
    shiftRefsInFormula(formula, mapRow, mapCol) {
        // Match cell references like A1, Z100
        return formula.replace(/[A-Z]\d+/g, (match) => {
            const p = (0, constants_1.parseCellRef)(match);
            if (!p)
                return match;
            const newCol = mapCol ? mapCol(p.col) : p.col;
            const newRow = mapRow ? mapRow(p.row) : p.row;
            // Check bounds
            if (newCol < 0 || newCol >= constants_1.CONFIG.COLS || newRow < 0 || newRow >= constants_1.CONFIG.ROWS) {
                return evaluator_1.ERR_REF.slice(1); // #REF! without hash (it's inside formula)
            }
            return (0, constants_1.cellKey)(newCol, newRow);
        });
    }
    notify() {
        for (const listener of this.listeners) {
            try {
                listener();
            }
            catch (e) {
                console.error("CellStore listener error:", e);
            }
        }
    }
}
exports.CellStore = CellStore;
function formatValue(value) {
    switch (value.type) {
        case "number": {
            if (Number.isInteger(value.value) && Math.abs(value.value) < 1e15) {
                return value.value.toString();
            }
            // Limit decimals for readability
            const rounded = parseFloat(value.value.toPrecision(10));
            return rounded.toString();
        }
        case "text":
            return value.value;
        case "error":
            return value.code;
        case "empty":
            return "";
    }
}
function isNumericValue(value) {
    return value.type === "number";
}
};

// ── module: src/grid.ts ──
__mods["src/grid.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GridRenderer = void 0;
const constants_1 = require("./constants");
const cellstore_1 = require("./cellstore");
class GridRenderer {
    constructor(store) {
        this.editingCell = null;
        this.editInput = null;
        // Cell DOM cache: row -> col -> element
        this.cellCache = [];
        this.store = store;
        this.selectedCol = 0;
        this.selectedRow = 0;
        this.init();
    }
    getEl(id) {
        return document.getElementById(id);
    }
    getElAs(id) {
        return document.getElementById(id);
    }
    init() {
        this.colHeadersEl = this.getEl("col-headers");
        this.rowsContainerEl = this.getEl("rows-container");
        this.cellRefEl = this.getEl("cell-ref");
        this.formulaInputEl = this.getElAs("formula-input");
        this.gridContainerEl = this.getEl("grid-container");
        this.renderHeaders();
        this.renderCells();
        this.setupEventListeners();
        this.updateSelection();
        this.store.subscribe(() => this.refreshAllCells());
        this.setupToolbar();
    }
    renderHeaders() {
        this.colHeadersEl.innerHTML = "";
        for (let c = 0; c < constants_1.CONFIG.COLS; c++) {
            const header = document.createElement("div");
            header.className = "col-header";
            header.textContent = (0, constants_1.colToLetter)(c);
            header.dataset.col = String(c);
            header.addEventListener("contextmenu", (e) => this.onColHeaderRightClick(e, c));
            this.colHeadersEl.appendChild(header);
        }
    }
    renderCells() {
        this.rowsContainerEl.innerHTML = "";
        this.cellCache = [];
        for (let r = 0; r < constants_1.CONFIG.ROWS; r++) {
            const rowEl = document.createElement("div");
            rowEl.className = "grid-row";
            rowEl.dataset.row = String(r);
            const rowHeader = document.createElement("div");
            rowHeader.className = "row-header";
            rowHeader.textContent = String(r + 1);
            rowHeader.dataset.row = String(r);
            rowHeader.addEventListener("contextmenu", (e) => this.onRowHeaderRightClick(e, r));
            rowEl.appendChild(rowHeader);
            const rowCache = [];
            for (let c = 0; c < constants_1.CONFIG.COLS; c++) {
                const cell = document.createElement("div");
                cell.className = "cell";
                cell.dataset.col = String(c);
                cell.dataset.row = String(r);
                cell.addEventListener("click", () => this.onCellClick(c, r));
                cell.addEventListener("dblclick", () => this.onCellDoubleClick(c, r));
                rowEl.appendChild(cell);
                rowCache.push(cell);
            }
            this.rowsContainerEl.appendChild(rowEl);
            this.cellCache.push(rowCache);
        }
    }
    setupEventListeners() {
        document.addEventListener("keydown", (e) => this.onKeyDown(e));
        this.formulaInputEl.addEventListener("input", () => this.onFormulaBarInput());
        this.formulaInputEl.addEventListener("keydown", (e) => this.onFormulaBarKeyDown(e));
        this.formulaInputEl.addEventListener("focus", () => {
            if (!this.editingCell) {
                this.formulaInputEl.select();
            }
        });
    }
    setupToolbar() {
        const insertRowBtn = document.getElementById("btn-insert-row");
        const deleteRowBtn = document.getElementById("btn-delete-row");
        const insertColBtn = document.getElementById("btn-insert-col");
        const deleteColBtn = document.getElementById("btn-delete-col");
        if (insertRowBtn)
            insertRowBtn.addEventListener("click", () => this.store.insertRow(this.selectedRow));
        if (deleteRowBtn)
            deleteRowBtn.addEventListener("click", () => this.store.deleteRow(this.selectedRow));
        if (insertColBtn)
            insertColBtn.addEventListener("click", () => this.store.insertCol(this.selectedCol));
        if (deleteColBtn)
            deleteColBtn.addEventListener("click", () => this.store.deleteCol(this.selectedCol));
    }
    onCellClick(col, row) {
        if (this.editingCell)
            this.commitEdit();
        this.selectCell(col, row);
    }
    onCellDoubleClick(col, row) {
        this.selectCell(col, row);
        this.startEdit(col, row);
    }
    onRowHeaderRightClick(e, row) {
        e.preventDefault();
        const action = prompt(`Insert row before ${row + 1} (i) or delete row ${row + 1} (d)?`, "d");
        if (action === "i")
            this.store.insertRow(row);
        else if (action === "d")
            this.store.deleteRow(row);
    }
    onColHeaderRightClick(e, col) {
        e.preventDefault();
        const action = prompt(`Insert before ${(0, constants_1.colToLetter)(col)} (i) or delete (d)?`, "d");
        if (action === "i")
            this.store.insertCol(col);
        else if (action === "d")
            this.store.deleteCol(col);
    }
    onKeyDown(e) {
        if (this.editingCell)
            return;
        if (document.activeElement === this.formulaInputEl)
            return;
        const { selectedCol: col, selectedRow: row } = this;
        const key = e.key;
        switch (key) {
            case "ArrowUp":
                e.preventDefault();
                if (row > 0)
                    this.selectCell(col, row - 1);
                break;
            case "ArrowDown":
                e.preventDefault();
                if (row < constants_1.CONFIG.ROWS - 1)
                    this.selectCell(col, row + 1);
                break;
            case "ArrowLeft":
                e.preventDefault();
                if (col > 0)
                    this.selectCell(col - 1, row);
                break;
            case "ArrowRight":
                e.preventDefault();
                if (col < constants_1.CONFIG.COLS - 1)
                    this.selectCell(col + 1, row);
                break;
            case "Enter":
                e.preventDefault();
                this.startEdit(col, row);
                break;
            case "Delete":
            case "Backspace":
                e.preventDefault();
                this.store.clearCell(col, row);
                this.updateFormulaBar();
                break;
            default:
                if (key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
                    this.startEdit(col, row);
                }
        }
    }
    onFormulaBarInput() {
        // Live preview doesn't update store; only on commit
    }
    onFormulaBarKeyDown(e) {
        const { selectedCol: col, selectedRow: row } = this;
        if (e.key === "Enter") {
            e.preventDefault();
            this.store.setCell(col, row, this.formulaInputEl.value);
            if (row < constants_1.CONFIG.ROWS - 1)
                this.selectCell(col, row + 1);
            this.formulaInputEl.blur();
        }
        else if (e.key === "Escape") {
            e.preventDefault();
            this.formulaInputEl.value = this.store.getRaw((0, constants_1.cellKey)(col, row));
            this.formulaInputEl.blur();
        }
        else if (e.key === "Tab") {
            e.preventDefault();
            this.store.setCell(col, row, this.formulaInputEl.value);
            if (col < constants_1.CONFIG.COLS - 1)
                this.selectCell(col + 1, row);
            else
                this.formulaInputEl.blur();
        }
    }
    selectCell(col, row) {
        this.selectedCol = col;
        this.selectedRow = row;
        this.updateSelection();
    }
    updateSelection() {
        // Remove previous selection
        const prevRow = this.cellCache[this.selectedRow];
        if (prevRow)
            prevRow[this.selectedCol]?.classList.remove("selected");
        // Add new selection
        const newRow = this.cellCache[this.selectedRow];
        if (newRow)
            newRow[this.selectedCol]?.classList.add("selected");
        // Update UI
        this.cellRefEl.textContent = (0, constants_1.cellKey)(this.selectedCol, this.selectedRow);
        this.updateFormulaBar();
        this.scrollCellIntoView();
    }
    scrollCellIntoView() {
        const cell = this.cellCache[this.selectedRow]?.[this.selectedCol];
        if (cell)
            cell.scrollIntoView({ block: "nearest", inline: "nearest" });
    }
    startEdit(col, row) {
        if (this.editingCell)
            this.commitEdit();
        this.editingCell = { col, row };
        const cell = this.cellCache[row][col];
        cell.classList.add("editing");
        const input = document.createElement("input");
        input.type = "text";
        input.value = this.store.getRaw((0, constants_1.cellKey)(col, row));
        cell.appendChild(input);
        this.editInput = input;
        input.focus();
        input.select();
        this.formulaInputEl.value = input.value;
        const handleKeyDown = (e) => {
            if (e.key === "Enter") {
                e.preventDefault();
                this.store.setCell(col, row, input.value);
                this.endEdit();
                if (row < constants_1.CONFIG.ROWS - 1)
                    this.selectCell(col, row + 1);
            }
            else if (e.key === "Tab") {
                e.preventDefault();
                this.store.setCell(col, row, input.value);
                this.endEdit();
                if (col < constants_1.CONFIG.COLS - 1)
                    this.selectCell(col + 1, row);
            }
            else if (e.key === "Escape") {
                e.preventDefault();
                this.endEdit();
            }
        };
        const handleBlur = () => {
            this.store.setCell(col, row, input.value);
            this.endEdit();
        };
        input.addEventListener("keydown", handleKeyDown);
        input.addEventListener("blur", handleBlur);
    }
    commitEdit() {
        if (!this.editingCell || !this.editInput)
            return;
        const { col, row } = this.editingCell;
        this.store.setCell(col, row, this.editInput.value);
        this.endEdit();
    }
    endEdit() {
        if (!this.editingCell)
            return;
        const { col, row } = this.editingCell;
        const cell = this.cellCache[row][col];
        cell.classList.remove("editing");
        if (this.editInput && this.editInput.parentNode === cell) {
            this.editInput.remove();
        }
        this.editInput = null;
        this.editingCell = null;
    }
    refreshAllCells() {
        // Refresh cells that have data
        for (let r = 0; r < constants_1.CONFIG.ROWS; r++) {
            for (let c = 0; c < constants_1.CONFIG.COLS; c++) {
                const ref = (0, constants_1.cellKey)(c, r);
                if (this.store.hasData(ref)) {
                    this.renderCellValue(this.cellCache[r][c], ref);
                }
            }
        }
        if (!this.editingCell)
            this.updateFormulaBar();
    }
    renderCellValue(cell, ref) {
        cell.innerHTML = "";
        cell.classList.remove("right-aligned", "error");
        const value = this.store.getValue(ref);
        if (value.type === "error") {
            cell.textContent = value.code;
            cell.classList.add("error");
        }
        else if (value.type === "number") {
            cell.textContent = (0, cellstore_1.formatValue)(value);
            cell.classList.add("right-aligned");
        }
        else if (value.type === "text") {
            cell.textContent = value.value;
        }
    }
    updateFormulaBar() {
        if (this.editingCell)
            return;
        const ref = (0, constants_1.cellKey)(this.selectedCol, this.selectedRow);
        this.formulaInputEl.value = this.store.getRaw(ref);
    }
}
exports.GridRenderer = GridRenderer;
};

// ── module: src/storage.ts ──
__mods["src/storage.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.saveToStorage = saveToStorage;
exports.loadFromStorage = loadFromStorage;
const constants_1 = require("./constants");
function saveToStorage(store) {
    try {
        const data = store.toJSON();
        localStorage.setItem(constants_1.CONFIG.STORAGE_KEY, JSON.stringify(data));
    }
    catch (e) {
        console.warn("Failed to save to localStorage:", e);
    }
}
function loadFromStorage(store) {
    try {
        const raw = localStorage.getItem(constants_1.CONFIG.STORAGE_KEY);
        if (raw) {
            const data = JSON.parse(raw);
            store.fromJSON(data);
        }
    }
    catch (e) {
        console.warn("Failed to load from localStorage:", e);
    }
}
};

// ── module: src/parser.ts ──
__mods["src/parser.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = exports.ParseError = void 0;
exports.parseFormula = parseFormula;
const tokenizer_1 = require("./tokenizer");
class ParseError extends Error {
    constructor(message) {
        super(message);
        this.name = "ParseError";
    }
}
exports.ParseError = ParseError;
class Parser {
    constructor(input) {
        this.tokens = (0, tokenizer_1.tokenize)(input);
        this.pos = 0;
    }
    parse() {
        if (this.tokens.length === 0) {
            return { type: "error", message: "Empty expression" };
        }
        try {
            const node = this.parseExpression();
            if (this.pos < this.tokens.length) {
                return { type: "error", message: `Unexpected token: ${this.tokens[this.pos].value}` };
            }
            return node;
        }
        catch (e) {
            if (e instanceof ParseError) {
                return { type: "error", message: e.message };
            }
            return { type: "error", message: String(e) };
        }
    }
    peek() {
        return this.pos < this.tokens.length ? this.tokens[this.pos] : null;
    }
    advance() {
        if (this.pos >= this.tokens.length) {
            throw new ParseError("Unexpected end of expression");
        }
        return this.tokens[this.pos++];
    }
    expect(type) {
        const t = this.peek();
        if (!t || t.type !== type) {
            throw new ParseError(`Expected ${type}, got ${t?.type}: ${t?.value}`);
        }
        return this.advance();
    }
    isKnownFunction(name) {
        return ["SUM", "AVG", "AVERAGE", "MIN", "MAX", "COUNT"].includes(name);
    }
    // Expression: + -
    parseExpression() {
        let left = this.parseTerm();
        while (this.peek()?.type === "OP" && ["+", "-"].includes(this.peek().value)) {
            const op = this.advance().value;
            const right = this.parseTerm();
            left = { type: "binop", op, left, right };
        }
        return left;
    }
    // Term: * /
    parseTerm() {
        let left = this.parseUnary();
        while (this.peek()?.type === "OP" && ["*", "/"].includes(this.peek().value)) {
            const op = this.advance().value;
            const right = this.parseUnary();
            left = { type: "binop", op, left, right };
        }
        return left;
    }
    // Unary: -
    parseUnary() {
        if (this.peek()?.type === "UNARY_MINUS") {
            this.advance();
            return { type: "unary", op: "-", operand: this.parseUnary() };
        }
        return this.parsePostfix();
    }
    // Postfix: primary with optional range operator (e.g. A1:A5)
    parsePostfix() {
        let node = this.parsePrimary();
        if (this.peek()?.type === "COLON") {
            this.advance(); // consume :
            const endTok = this.peek();
            if (!endTok || endTok.type !== "IDENT") {
                throw new ParseError("Expected cell reference after ':'");
            }
            const end = endTok.value;
            this.advance();
            return { type: "range", start: node.ref ?? node, end };
        }
        return node;
    }
    // Primary: literals, parentheses, function calls, cell refs
    parsePrimary() {
        const tok = this.peek();
        if (!tok)
            throw new ParseError("Unexpected end of expression");
        if (tok.type === "NUMBER") {
            this.advance();
            return { type: "number", value: tok.value };
        }
        if (tok.type === "LPAREN") {
            this.advance();
            const node = this.parseExpression();
            this.expect("RPAREN");
            return node;
        }
        if (tok.type === "IDENT") {
            const name = tok.value;
            if (this.isKnownFunction(name)) {
                this.advance();
                return this.parseFunctionCall(name);
            }
            this.advance();
            return { type: "cell", ref: name };
        }
        throw new ParseError(`Unexpected token: ${tok.value}`);
    }
    parseFunctionCall(name) {
        this.expect("LPAREN");
        const args = [];
        if (this.peek()?.type === "RPAREN") {
            this.advance();
        }
        else {
            do {
                args.push(this.parseExpression());
                if (this.peek()?.type === "COMMA") {
                    this.advance();
                }
            } while (this.peek()?.type !== "RPAREN");
            this.expect("RPAREN");
        }
        return { type: "call", name, args };
    }
}
exports.Parser = Parser;
function parseFormula(input) {
    return new Parser(input).parse();
}
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-Fable-Fusion-711-Uncensored-Heretic-NM-DAU-NEO-MAX-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"45027380-2041-4809-ab37-b8001c9ef69d","tokensIn":5174247,"tokensOut":64368,"tokensTotal":5238615,"cost":0,"turns":110,"toolCalls":116,"failedToolCalls":2,"timestamp":"2026-08-07T01:04:26.942Z"} -->