Spreadsheet
Qwen 3.6 27B Fable Fusion 711 Uncensored Heretic NM DAU NEO MAX MTP 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 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"} -->