Spreadsheet
nex agi Nex N2.5 mini GGUF · typescript

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 33,677 bytes · SHA-256 331ca01c7c5f
<!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: app ── */
:root {
--gridline: #d7dce2;
--gridline-strong: #b8c0ca;
--header-bg: #f7f8fa;
--header-text: #3c4043;
--blue: #1a73e8;
--blue-soft: #e8f0fe;
--text: #202124;
--muted: #5f6368;
--danger: #c5221f;
--shadow: 0 1px 2px rgba(60,64,67,.18), 0 1px 3px rgba(60,64,67,.12);
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { font-family: Arial, Helvetica, sans-serif; color: var(--text); background: #fff; overflow: hidden; }
#app { display: flex; flex-direction: column; height: 100%; min-width: 760px; }
.topbar { height: 48px; display: flex; align-items: center; gap: 12px; padding: 0 14px; border-bottom: 1px solid #e2e5e9; background: #fff; }
.brand { font-size: 15px; font-weight: 700; letter-spacing: .02em; }
.brand span { color: var(--blue); }
.toolbar { display: flex; align-items: center; gap: 6px; margin-left: 12px; flex: 1; }
button { height: 30px; border: 1px solid #d2d7dd; background: #fff; color: var(--text); border-radius: 4px; padding: 0 10px; font-size: 12px; cursor: pointer; }
button:hover { background: #f5f8ff; border-color: #aebbdd; }
button:active { background: var(--blue-soft); }
button.primary { background: var(--blue); border-color: var(--blue); color: #fff; }
button.primary:hover { background: #1765cc; }
button:disabled { opacity: .45; cursor: default; }
.toolbar-separator { width: 1px; height: 20px; background: #e2e5e9; margin: 0 2px; }
.status { font-size: 12px; color: var(--muted); white-space: nowrap; }
.formula-bar { height: 42px; display: flex; align-items: center; gap: 10px; padding: 6px 12px; border-bottom: 1px solid #e2e5e9; background: #fbfcfd; }
.name-box { width: 66px; height: 30px; border: 1px solid transparent; background: transparent; font-size: 13px; font-weight: 700; color: var(--text); outline: none; }
.name-box:focus { border-color: var(--blue); background: #fff; }
.formula-separator { width: 1px; height: 24px; background: #d8dde3; }
.formula-input { flex: 1; height: 32px; border: 1px solid #d2d7dd; border-radius: 4px; padding: 0 10px; font-size: 13px; color: var(--text); outline: none; background: #fff; }
.formula-input:focus { border-color: var(--blue); box-shadow: 0 0 0 2px rgba(26,115,232,.12); }
.mini-label { font-size: 12px; color: var(--muted); }
.sheet-shell { flex: 1; min-height: 0; overflow: auto; position: relative; background: #fff; }
#grid-table { border-collapse: separate; border-spacing: 0; table-layout: fixed; font-size: 13px; background: #fff; }
#grid-table th, #grid-table td { border-right: 1px solid var(--gridline); border-bottom: 1px solid var(--gridline); padding: 0; height: 28px; min-width: 112px; }
#grid-table th { position: sticky; z-index: 3; background: var(--header-bg); color: var(--header-text); font-weight: 700; text-align: center; font-size: 12px; user-select: none; }
#grid-table th.row-header { position: sticky; left: 0; z-index: 4; width: 48px; min-width: 48px; border-left: 1px solid var(--gridline); }
#grid-table thead th { top: 0; z-index: 5; height: 30px; }
#grid-table thead th.row-header { z-index: 8; }
#grid-table td { position: relative; background: #fff; cursor: default; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 8px; }
#grid-table td.active { background: #fff; }
#grid-table td.active::after { content: ""; position: absolute; inset: -1px; border: 2px solid var(--blue); pointer-events: none; z-index: 2; }
#grid-table td.active .cell-content { color: #0b57d0; }
#grid-table td.error { color: var(--danger); font-weight: 700; }
#grid-table td.number { text-align: right; }
#grid-table td.text { text-align: left; }
#grid-table td.formula { color: #202124; }
#grid-table .cell-content { display: block; height: 28px; line-height: 28px; }
.cell-edit { position: absolute; z-index: 6; left: 2px; top: 2px; width: calc(100% - 4px); height: 26px; border: 1px solid var(--blue); border-radius: 1px; padding: 0 6px; font: inherit; outline: none; box-shadow: 0 0 0 1px rgba(26,115,232,.12); }
.context-menu { position: fixed; z-index: 30; min-width: 180px; padding: 5px 0; border: 1px solid #d2d7dd; border-radius: 4px; background: #fff; box-shadow: var(--shadow); }
.context-menu button { display: block; width: 100%; height: 30px; text-align: left; border: 0; border-radius: 0; padding: 0 12px; background: #fff; font-size: 12px; }
.context-menu button:hover { background: var(--blue-soft); }
.context-menu .menu-title { padding: 6px 12px; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
#context-menu[hidden] { display: none; }
.sheet-footer { height: 26px; border-top: 1px solid #e2e5e9; background: #fbfcfd; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; color: var(--muted); font-size: 12px; }
.hint { color: #7b828a; }
</style>
</head>
<body>
<header class="topbar">
<div class="brand"><span>◆</span> SheetForge</div>
<div class="toolbar">
<button id="insert-row" type="button">Insert row</button>
<button id="delete-row" type="button">Delete row</button>
<span class="toolbar-separator"></span>
<button id="insert-col" type="button">Insert column</button>
<button id="delete-col" type="button">Delete column</button>
<span class="toolbar-separator"></span>
<button id="clear-cell" type="button">Clear cell</button>
<span class="toolbar-separator"></span>
<span class="mini-label" id="operation-label">Ready</span>
</div>
<div class="status" id="status">26 columns × 100 rows</div>
</header>
<section class="formula-bar">
<input id="name-box" class="name-box" spellcheck="false" aria-label="Selected cell">
<span class="formula-separator"></span>
<input id="formula-input" class="formula-input" spellcheck="false" aria-label="Formula bar">
<span class="mini-label">Show: computed value · Edit: raw formula</span>
</section>
<main id="sheet-shell" class="sheet-shell">
<table id="grid-table" aria-label="Spreadsheet grid"></table>
<div id="context-menu" class="context-menu" hidden=""></div>
</main>
<footer class="sheet-footer">
<span id="selection-status">Ready</span>
<span class="hint">Enter commits down · Tab commits right · Delete clears · Double-click edits</span>
</footer>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./constants":"src/constants.ts","./formula":"src/formula.ts","./spreadsheet":"src/spreadsheet.ts"},"src/spreadsheet.ts":{"./constants":"src/constants.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 spreadsheet_1 = require("./spreadsheet");
const app = document.querySelector('#app');
const table = document.querySelector('#grid-table');
const shell = document.querySelector('#sheet-shell');
const nameBox = document.querySelector('#name-box');
const formulaInput = document.querySelector('#formula-input');
const contextMenu = document.querySelector('#context-menu');
const status = document.querySelector('#status');
const selectionStatus = document.querySelector('#selection-status');
const operationLabel = document.querySelector('#operation-label');
const sheet = new spreadsheet_1.Spreadsheet();
window.sheetApp = sheet;
let selectedRow = 0;
};
// ── module: src/constants.ts ──
__mods["src/constants.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.COLOR = exports.STORAGE_KEY = exports.ROW_HEADER_WIDTH = exports.HEADER_HEIGHT = exports.CELL_HEIGHT = exports.CELL_WIDTH = exports.GRID_ROWS = exports.GRID_COLUMNS = void 0;
exports.columnName = columnName;
exports.columnFromName = columnFromName;
exports.rowFromName = rowFromName;
exports.cellAddress = cellAddress;
exports.splitAddress = splitAddress;
exports.isInside = isInside;
exports.GRID_COLUMNS = 26;
exports.GRID_ROWS = 100;
exports.CELL_WIDTH = 112;
exports.CELL_HEIGHT = 28;
exports.HEADER_HEIGHT = 30;
exports.ROW_HEADER_WIDTH = 48;
exports.STORAGE_KEY = 'sheetforge.spreadsheet.raw.v1';
exports.COLOR = { blue: '#1a73e8', gridline: '#d7dce2', gridlineStrong: '#b8c0ca', error: '#c5221f' };
function columnName(index0) { let n = index0 + 1; let result = ''; while (n > 0) {
n -= 1;
result = String.fromCharCode(65 + (n % 26)) + result;
n = Math.floor(n / 26);
} return result; }
function columnFromName(name) { let result = 0; for (const char of name.toUpperCase()) {
if (char < 'A' || char > 'Z')
return null;
result = result * 26 + char.charCodeAt(0) - 64;
} return result === null || result < 1 || result > exports.GRID_COLUMNS ? null : result - 1; }
function rowFromName(name) { if (!/^[1-9][0-9]*$/.test(name))
return null; const row = Number(name); return row < 1 || row > exports.GRID_ROWS ? null : row - 1; }
function cellAddress(row, col) { return `${columnName(col)}${row + 1}`; }
function splitAddress(address) { const match = address.match(/^([A-Z]{1,3})([1-9][0-9]*)$/i); if (!match)
return null; const col = columnFromName(match[1]); const row = rowFromName(match[2]); return col === null || row === null ? null : { row, col }; }
function isInside(row, col) { return row >= 0 && row < exports.GRID_ROWS && col >= 0 && col < exports.GRID_COLUMNS; }
};
// ── module: src/formula.ts ──
__mods["src/formula.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERR_CYCLE = exports.ERR_PARSE = exports.ERR_DIV = exports.ERR_REF = void 0;
exports.tokenizeFormula = tokenizeFormula;
exports.parseFormula = parseFormula;
exports.formulaDependencies = formulaDependencies;
exports.evaluateFormula = evaluateFormula;
exports.evaluateNode = evaluateNode;
exports.formatValue = formatValue;
exports.ERR_REF = '#REF!';
exports.ERR_DIV = '#DIV/0!';
exports.ERR_PARSE = '#ERR!';
exports.ERR_CYCLE = '#CYCLE!';
const PRECEDENCE = { '+': 1, '-': 1, '*': 2, '/': 2, '^': 3 };
const MAX_COLUMN = 26;
const MAX_ROW = 100;
function fail() { throw new Error('parse'); }
function isLetter(char) { return char >= 'A' && char <= 'Z'; }
function tokenizeFormula(input) {
const tokens = [];
let i = 0;
while (i < input.length) {
const char = input[i];
if (/\s/.test(char)) {
i += 1;
continue;
}
if (char === '"' || char === "'") {
const quote = char;
let j = i + 1;
let value = '';
while (j < input.length) {
if (input[j] === quote) {
if (input[j + 1] === quote) {
value += quote;
j += 2;
continue;
}
j += 1;
break;
}
value += input[j];
j += 1;
}
if (j > input.length || input[j - 1] !== quote)
fail();
tokens.push({ kind: 'text', text: input.slice(i, j), value, start: i, end: j });
i = j;
continue;
}
if (/\d/.test(char) || (char === '.' && /\d/.test(input[i + 1] || ''))) {
let j = i;
let sawDigit = false;
while (j < input.length && /\d/.test(input[j])) {
sawDigit = true;
j += 1;
}
if (input[j] === '.') {
j += 1;
while (j < input.length && /\d/.test(input[j])) {
sawDigit = true;
j += 1;
}
}
if (!sawDigit)
fail();
const raw = input.slice(i, j);
tokens.push({ kind: 'number', text: raw, value: Number(raw), start: i, end: j });
i = j;
continue;
}
if (/[A-Za-z]/.test(char)) {
let j = i + 1;
while (j < input.length && /[A-Za-z]/.test(input[j]))
j += 1;
if (/\d/.test(input[j] || '')) {
let k = j;
while (k < input.length && /\d/.test(input[k]))
k += 1;
const raw = input.slice(i, k);
const parsed = parseAddressText(raw);
tokens.push({ kind: 'ref', text: raw, row: parsed?.row ?? -1, col: parsed?.col ?? -1, invalid: !parsed, start: i, end: k });
i = k;
continue;
}
tokens.push({ kind: 'ident', text: input.slice(i, j), start: i, end: j });
i = j;
continue;
}
if (char === ':') {
tokens.push({ kind: 'colon', text: ':', start: i, end: i + 1 });
i += 1;
continue;
}
if (char === '(' || char === ')' || char === ',') {
const kind = char === '(' ? 'lp' : char === ')' ? 'rp' : 'comma';
tokens.push({ kind, text: char, start: i, end: i + 1 });
i += 1;
continue;
}
if ('+-*/^'.includes(char)) {
tokens.push({ kind: 'op', text: char, start: i, end: i + 1 });
i += 1;
continue;
}
fail();
}
tokens.push({ kind: 'eof', text: '', start: input.length, end: input.length });
return tokens;
}
function parseAddressText(address) {
const match = address.toUpperCase().match(/^([A-Z]{1,3})([0-9]+)$/);
if (!match)
return null;
let col = 0;
for (const char of match[1]) {
if (!isLetter(char))
return null;
col = col * 26 + char.charCodeAt(0) - 64;
}
if (col < 1 || col > MAX_COLUMN)
return null;
const row = Number(match[2]);
if (row < 1 || row > MAX_ROW)
return null;
return { row, col };
}
class FormulaParser {
constructor(input) {
this.input = input;
this.index = 0;
this.tokens = tokenizeFormula(input);
}
peek(offset = 0) { return this.tokens[Math.min(this.index + offset, this.tokens.length - 1)]; }
atKind(kind) { return this.peek().kind === kind; }
consumeKind(kind) { const token = this.peek(); if (token.kind !== kind)
fail(); this.index += 1; return token; }
acceptKind(kind) { return this.atKind(kind) ? this.consumeKind(kind) : null; }
parse() { const expression = this.parseExpression(); if (this.peek().kind !== 'eof')
fail(); return expression; }
parseExpression(minPrecedence = 0) {
let left = this.parseUnary();
while (true) {
if (this.atKind('colon')) {
this.consumeKind('colon');
const end = this.parsePrimary();
if (end.kind !== 'ref')
fail();
left = { kind: 'range', start: left, end };
continue;
}
if (!this.atKind('op'))
break;
const op = this.consumeKind('op').text;
const precedence = PRECEDENCE[op] ?? -1;
const right = this.parseExpression(precedence + 1);
left = { kind: 'binary', op, left, right };
}
return left;
}
parseUnary() {
if (this.atKind('op') && (this.peek().text === '+' || this.peek().text === '-')) {
const op = this.consumeKind('op').text;
return { kind: 'unary', op, operand: this.parseUnary() };
}
return this.parsePrimary();
}
parsePrimary() {
const token = this.peek();
if (token.kind === 'number') {
this.index += 1;
return { kind: 'number', value: token.value };
}
if (token.kind === 'text') {
this.index += 1;
return { kind: 'text', value: token.value };
}
if (token.kind === 'ref') {
this.index += 1;
return { kind: 'ref', row: token.row, col: token.col, invalid: token.invalid ?? false };
}
if (token.kind === 'lp') {
this.consumeKind('lp');
const expression = this.parseExpression();
this.consumeKind('rp');
return expression;
}
if (token.kind === 'ident') {
const nameToken = this.consumeKind('ident');
if (!this.atKind('lp'))
fail();
this.consumeKind('lp');
const args = [];
if (!this.atKind('rp')) {
args.push(this.parseExpression());
while (this.acceptKind('comma')) {
if (this.atKind('rp'))
fail();
args.push(this.parseExpression());
}
}
this.consumeKind('rp');
return { kind: 'func', name: nameToken.text, args };
}
fail();
}
}
function parseFormula(raw) {
try {
const normalized = raw.trim();
if (normalized[0] !== '=')
return null;
return new FormulaParser(normalized.slice(1)).parse();
}
catch {
return null;
}
}
function formulaDependencies(raw) {
const normalized = raw.trim();
if (!normalized.startsWith('='))
return new Set();
const ast = parseFormula(normalized);
if (!ast)
return new Set();
const result = new Set();
const visit = (node) => {
if (node.kind === 'ref') {
if (!node.invalid)
result.add(`${node.row}-${node.col}`);
return;
}
if (node.kind === 'range') {
const startRow = Math.min(node.start.row, node.end.row), endRow = Math.max(node.start.row, node.end.row);
const startCol = Math.min(node.start.col, node.end.col), endCol = Math.max(node.start.col, node.end.col);
for (let row = startRow; row <= endRow; row += 1)
for (let col = startCol; col <= endCol; col += 1)
result.add(`${row}-${col}`);
return;
}
if (node.kind === 'unary')
visit(node.operand);
if (node.kind === 'binary') {
visit(node.left);
visit(node.right);
}
if (node.kind === 'func')
for (const arg of node.args)
visit(arg);
};
visit(ast);
return result;
}
function evaluateFormula(raw, getCell) {
const ast = parseFormula(raw);
if (!ast)
return { __formulaError: true, code: exports.ERR_PARSE };
try {
return evaluateNode(ast, getCell);
}
catch {
return { __formulaError: true, code: exports.ERR_PARSE };
}
}
function isFormulaError(value) { return typeof value === 'object' && value !== null && '__formulaError' in value; }
function rangeValues(value) { return value && typeof value === 'object' && '__range' in value ? value.values : [value]; }
function evaluateNode(node, getCell) {
if (node.kind === 'number')
return node.value;
if (node.kind === 'text')
return node.value;
if (node.kind === 'ref') {
if (node.invalid)
return { __formulaError: true, code: exports.ERR_REF };
return getCell(`${node.row}-${node.col}`);
}
if (node.kind === 'range') {
const startRow = Math.min(node.start.row, node.end.row), endRow = Math.max(node.start.row, node.end.row);
const startCol = Math.min(node.start.col, node.end.col), endCol = Math.max(node.start.col, node.end.col);
const values = [];
for (let row = startRow; row <= endRow; row += 1)
for (let col = startCol; col <= endCol; col += 1)
values.push(getCell(`${row}-${col}`));
return { __range: true, values };
}
if (node.kind === 'unary') {
const value = evaluateNode(node.operand, getCell);
if (isFormulaError(value))
return value;
const number = coerceNumber(value);
if (number === undefined)
return { __formulaError: true, code: exports.ERR_PARSE };
return node.op === '-' ? -number : number;
}
if (node.kind === 'binary') {
const left = evaluateNode(node.left, getCell), right = evaluateNode(node.right, getCell);
if (isFormulaError(left))
return left;
if (isFormulaError(right))
return right;
const leftNumber = coerceNumber(left), rightNumber = coerceNumber(right);
if (leftNumber === undefined || rightNumber === undefined)
return { __formulaError: true, code: exports.ERR_PARSE };
if (node.op === '+')
return leftNumber + rightNumber;
if (node.op === '-')
return leftNumber - rightNumber;
if (node.op === '*')
return leftNumber * rightNumber;
if (node.op === '/')
return rightNumber === 0 ? { __formulaError: true, code: exports.ERR_DIV } : leftNumber / rightNumber;
if (node.op === '^')
return Math.pow(leftNumber, rightNumber);
}
if (node.kind === 'func') {
const args = node.args.flatMap(arg => rangeValues(evaluateNode(arg, getCell)));
const firstError = args.find(isFormulaError);
if (firstError)
return firstError;
const scalars = args.filter((value) => !isFormulaError(value) && typeof value !== 'object');
const numeric = scalars.map(value => coerceNumber(value)).filter((value) => value !== undefined);
const name = node.name.toUpperCase();
if (name === 'SUM' || name === 'AVG' || name === 'AVERAGE' || name === 'MIN' || name === 'MAX' || name === 'COUNT') {
if (name === 'COUNT')
return numeric.length;
if (numeric.length === 0)
return name === 'MIN' || name === 'MAX' ? 0 : { __formulaError: true, code: exports.ERR_DIV };
if (name === 'SUM')
return numeric.reduce((sum, value) => sum + value, 0);
if (name === 'MIN')
return Math.min(...numeric);
if (name === 'MAX')
return Math.max(...numeric);
return numeric.reduce((sum, value) => sum + value, 0) / numeric.length;
}
return { __formulaError: true, code: exports.ERR_PARSE };
}
return { __formulaError: true, code: exports.ERR_PARSE };
}
function coerceNumber(value) {
if (typeof value === 'number')
return Number.isFinite(value) ? value : undefined;
if (typeof value !== 'string')
return undefined;
const trimmed = value.trim();
if (trimmed === '')
return 0;
const parsed = Number(trimmed);
return Number.isFinite(parsed) ? parsed : undefined;
}
function formatValue(value) {
if (isFormulaError(value))
return value.code;
if (value && typeof value === 'object' && '__range' in value)
return value.values.map(formatValue).join(', ');
if (typeof value === 'boolean')
return value ? 'TRUE' : 'FALSE';
if (typeof value === 'number')
return Object.is(value, -0) ? '0' : String(value);
return String(value);
}
};
// ── module: src/spreadsheet.ts ──
__mods["src/spreadsheet.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Spreadsheet = void 0;
const constants_1 = require("./constants");
const formula_1 = require("./formula");
function keyForRowCol(row, col) { return `${row}-${col}`; }
function parseAddress(address) {
const match = address.match(/^([A-Za-z]{1,3})([1-9][0-9]*)$/i);
if (!match)
return null;
const col = (0, constants_1.columnFromName)(match[1]);
const row = (0, constants_1.rowFromName)(match[2]);
if (col === null || row === null)
return null;
return { row: row - 1, col: col - 1 };
}
function dependenciesFor(raw) {
const ast = (0, formula_1.parseFormula)(raw);
const result = new Set();
if (!ast)
return result;
const visit = (node) => {
if (node.kind === 'ref') {
if (!node.invalid)
result.add(`${node.row}-${node.col}`);
return;
}
if (node.kind === 'range') {
for (let row = Math.min(node.start.row, node.end.row); row <= Math.max(node.start.row, node.end.row); row += 1)
for (let col = Math.min(node.start.col, node.end.col); col <= Math.max(node.start.col, node.end.col); col += 1)
result.add(`${row}-${col}`);
return;
}
if (node.kind === 'unary')
return visit(node.operand);
if (node.kind === 'binary') {
visit(node.left);
return visit(node.right);
}
if (node.kind === 'func')
for (const arg of node.args)
visit(arg);
};
visit(ast);
return result;
}
class Spreadsheet {
constructor() {
this.raw = new Map();
this.computed = new Map();
this.dependents = new Map();
this.errors = new Map();
this.cycles = new Set();
this.rowCount = constants_1.GRID_ROWS;
this.colCount = constants_1.GRID_COLUMNS;
this.pending = [];
this.changedKeys = new Set();
this.load();
}
load() { try {
const stored = JSON.parse(localStorage.getItem(constants_1.STORAGE_KEY) ?? 'null');
if (!stored || stored.versions !== 1 || typeof stored.cells !== 'object')
return;
for (const [address, value] of Object.entries(stored.cells)) {
const parsed = parseAddress(address);
if (parsed && (0, constants_1.isInside)(parsed.row, parsed.col))
this.raw.set(address.toUpperCase(), String(value));
}
}
catch { /* ignore corrupt storage */ } this.recalculate(); }
getRaw(row, col) { return this.raw.get(keyForRowCol(row, col)) ?? ''; }
getComputed(row, col) { return this.computed.get(keyForRowCol(row, col)) ?? ''; }
getAddress(row, col) { return keyForRowCol(row, col); }
setCell(row, col, raw) { if (!(0, constants_1.isInside)(row, col))
return null; const address = keyForRowCol(row, col); if (this.getRaw(row, col) === raw)
return null; this.raw.set(address, raw); this.recalculate(); return { changed: true, affected: this.changedKeys }; }
clearCell(row, col) { if (!(0, constants_1.isInside)(row, col))
return null; const address = keyForRowCol(row, col); if (!this.raw.has(address))
return null; this.raw.delete(address); this.recalculate(); return { changed: true, affected: this.changedKeys }; }
insertRow(index) { if (index < 0 || index > this.rowCount || this.rowCount >= constants_1.GRID_ROWS)
return null; const affected = this.collectRawCellsForRow(index); for (const [address, raw] of this.raw) {
const parsed = parseAddress(address);
if (!parsed || parsed.row < index)
continue;
this.raw.delete(address);
this.raw.set(keyForRowCol(parsed.row + 1, parsed.col), raw);
} this.rowCount += 1; this.recalculate(); return { changed: true, affected, inserted: 'row', index }; }
deleteRow(index) { if (index < 0 || index >= this.rowCount)
return null; const affected = this.collectRawCellsForRow(index); if (affected.size === 0)
return null; for (const [address, raw] of this.raw) {
const parsed = parseAddress(address);
if (!parsed || parsed.row < index)
continue;
this.raw.delete(address);
this.raw.set(keyForRowCol(parsed.row - 1, parsed.col), raw);
} this.rowCount -= 1; this.recalculate(); return { changed: true, affected, inserted: 'row', index }; }
insertColumn(index) { if (index < 0 || index >= this.colCount || this.colCount >= constants_1.GRID_COLUMNS)
return null; const affected = this.collectRawCellsForColumn(index); for (const [address, raw] of this.raw) {
const parsed = parseAddress(address);
if (!parsed || parsed.col < index)
continue;
this.raw.delete(address);
this.raw.set(keyForRowCol(parsed.row, parsed.col + 1), raw);
} this.colCount += 1; this.recalculate(); return { changed: true, affected, inserted: 'column', index }; }
deleteColumn(index) { if (index < 0 || index >= this.colCount)
return null; const affected = this.collectRawCellsForColumn(index); if (affected.size === 0)
return null; for (const [address, raw] of this.raw) {
const parsed = parseAddress(address);
if (!parsed || parsed.col < index)
continue;
this.raw.delete(address);
this.raw.set(keyForRowCol(parsed.row, parsed.col - 1), raw);
} this.colCount -= 1; this.recalculate(); return { changed: true, affected, inserted: 'column', index }; }
collectRawCellsForRow(row) { const result = new Set(); for (let col = 0; col < this.colCount; col += 1) {
const address = keyForRowCol(row, col);
if (this.raw.has(address))
result.add(address);
} return result; }
collectRawCellsForColumn(col) { const result = new Set(); for (let row = 0; row < this.rowCount; row += 1) {
const address = keyForRowCol(row, col);
if (this.raw.has(address))
result.add(address);
} return result; }
buildGraph() { this.pending = Array.from(this.raw.entries()).map(([address, raw]) => { const parsed = parseAddress(address); return parsed ? { row: parsed.row, col: parsed.col, raw, dependencies: dependenciesFor(raw) } : { row: 0, col: 0, raw, dependencies: new Set() }; }); this.dependents.clear(); for (const pending of this.pending)
for (const dependency of pending.dependencies) {
const set = this.dependents.get(dependency) ?? new Set();
set.add(keyForRowCol(pending.row, pending.col));
this.dependents.set(dependency, set);
} }
recalculate() {
this.buildGraph();
const states = new Map();
const visiting = new Set();
const cycleNodes = new Set();
const visit = (address) => {
const state = states.get(address) ?? 0;
if (state === 2)
return 1;
if (state === 1)
return 0;
states.set(address, 1);
visiting.add(address);
const pending = this.pending.find(item => address === keyForRowCol(item.row, item.col));
if (!pending) {
visiting.delete(address);
states.set(address, 2);
return 1;
}
for (const dependency of pending.dependencies) {
if (this.pending.some(item => keyForRowCol(item.row, item.col) === dependency) && visit(dependency) === 0) {
cycleNodes.add(address);
visiting.delete(address);
states.set(address, 3);
return 0;
}
}
visiting.delete(address);
states.set(address, 2);
return 1;
};
for (const pending of this.pending)
visit(keyForRowCol(pending.row, pending.col));
this.cycles = cycleNodes;
const evaluateValue = (address) => {
if (this.cycles.has(address))
return { __formulaError: true, code: '#CYCLE!' };
const pending = this.pending.find(item => address === keyForRowCol(item.row, item.col));
if (!pending)
return 0;
if (!pending.raw.startsWith('='))
return pending.raw;
const ast = (0, formula_1.parseFormula)(pending.raw);
if (!ast)
return { __formulaError: true, code: '#ERR!' };
try {
return (0, formula_1.evaluateNode)(ast, dependency => evaluateValue(dependency));
}
catch {
return { __formulaError: true, code: '#ERR!' };
}
;
};
for (const pending of this.pending) {
const address = keyForRowCol(pending.row, pending.col);
this.computed.set(address, evaluateValue(address));
}
this.changedKeys = new Set(this.raw.keys());
this.persist();
}
persist() { const cells = {}; for (const [address, raw] of this.raw) {
const parsed = parseAddress(address);
if (parsed)
cells[(0, constants_1.cellAddress)(parsed.row, parsed.col)] = raw;
} try {
localStorage.setItem(constants_1.STORAGE_KEY, JSON.stringify({ versions: 1, cells }));
}
catch { /* ignore quota */ } }
}
exports.Spreadsheet = Spreadsheet;
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"bartowski/nex-agi_Nex-N2.5-mini-GGUF","provider":"brigid","persona":"typescript","sessionId":"72532635-2472-4d5b-8bb7-64dc7705c460","tokensIn":76368379,"tokensOut":198407,"tokensTotal":76566786,"cost":0,"turns":517,"toolCalls":517,"failedToolCalls":27,"timestamp":"2026-09-14T16:55:08.472Z"} -->