Spreadsheet
lfm2 24b a2b · basic

Browser check: Artifact runtime error. This is a technical check, not a quality review.
Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 15,862 bytes · SHA-256 1e77bb054ba3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Spreadsheet App</title>
<style>
:root {--cols:26;--rows:100;--cell-w:8.5rem;--cell-h:38px;--bg:#f9fafb;--header-bg:#e0e7f9;--header-text:#1a202d;--selected:#90caf9;--formula-bg:#fff;--formula-bar:#e0e7f9;--text:#111;--error:red;--border:1px solid #d1e8ff;}
* {box-sizing:border-box;}
body {margin:0;font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background:#f0f4f8;color:#111;}
.container {max-width:1200px;margin:0 auto;padding:1rem;}
.formula-bar {background:#e0e7f9;border:1px solid #d1e8ff;padding:0.5rem 1rem;font-family:system-ui;font-size:0.95rem;user-select:none;margin-bottom:0.5rem;}
table {width:100%;border-collapse:collapse;background:#fff;border:var(--border);}
th, td {text-align:left;vertical-align:top;border:1px solid #d1e8ff;padding:0.25rem 0.5rem;}
th {background:#e0e7f9;font-weight:600;color:#1a202d;}
td {min-height:38px;vertical-align:top;border:var(--border);}
.selected {background:#90caf9;color:#fff;border:none;outline:none;z-index:2;}
.error {color:#c00;font-weight:600;}
.toolbar {display:flex;gap:0.5rem;margin-bottom:0.5rem;}
button {background:#e0e7f9;border:1px solid #d1e8ff;color:#111;padding:0.25rem 0.75rem;border-radius:4px;cursor:pointer;font-size:0.9rem;}
button:hover {background:#d1e8ff;border:1px solid #b3c3ff;}
.colheader, .rowheader {white-space:nowrap;}
.rowheader {position:sticky;top:0;z-index:1;background:#f0f4f8;border-bottom:1px solid #d1e8ff;}
.colheader {font-weight:600;color:#1a202d;}
.formula-edit {width:100%;padding:0.25rem;border:none;border-bottom:2px dashed #d1e8ff;font-family:system-ui;font-size:0.9rem;}
.formula-bar-edit {width:100%;padding:0.25rem;border:none;border-bottom:2px dashed #d1e8ff;font-family:system-ui;font-size:0.9rem;background:#f0f4f8;}
</style>
</head>
<body>
<div class="container">
<div class="formula-bar" id="formulaBar"></div>
<table id="sheet">
<thead>
<tr>
<th class="colheader">A</th>
<!-- Generated dynamically -->
</tr>
</thead>
<tbody></tbody>
</table>
<div class="toolbar">
<button id="addCol">⇧⇒</button>
<button id="delCol">✕</button>
<button id="addRow">⇧⇒</button>
<button id="delRow">✕</button>
</div>
</div>
<script>
/* === CONFIG === */
const GRID_SIZE = {cols:26, rows:100};
const CELL_WIDTH = 8.5;
const CELL_HEIGHT = 38;
const COLOR_BG = "#f9fafb";
const COLOR_HEADER = "#e0e7f9";
const COLOR_SELECTED = "#90caf9";
const COLOR_FORMULA = "#e0e7f9";
const COLOR_TEXT = "#111";
const COLOR_ERROR = "#c00";
/* === STATE === */
let state = {
values: Array.from({length:GRID_SIZE.rows}, ()=>Array(GRID_SIZE.cols).fill(null)),
formulas: Array.from({length:GRID_SIZE.rows}, ()=>Array(GRID_SIZE.cols).fill(null)),
selectedRow:0, selectedCol:0,
history:[],
cols: Array.from({length:GRID_SIZE.cols}, (_,i)=>String.fromCharCode(65+i)),
rows: Array.from({length:GRID_SIZE.rows}, (_,r)=>r),
activeCell: null,
errorCells: new Set(),
dependencyMap: new Map(), // cell -> [dependents]
reverseDeps: new Map(), // cell -> [dependents]
formulaBarEl: null,
sheetEl: null
};
/* === UTILS === */
function getCell(row, col) { return state.values[row][col]; }
function setCell(row, col, value) {
if (value===undefined) value = null;
state.values[row][col] = value;
updateDependencies(row, col);
}
function getFormula(row, col) { return state.formulas[row][col]; }
function setFormula(row, col, f) { state.formulas[row][col]=f; updateDependencies(row, col); }
function getDependencies(row, col) {
let deps=[];
const f = getFormula(row, col);
if (!f) return deps;
// simple regex for refs and ranges
const matches = f.match(/([A-Za-z]+[0-9]+(?:,[0-9]+)*)|[A-Za-z]+|\d+/g);
if (matches) {
for (let m of matches) {
if (m[0]==='=') continue;
if (/^[A-Za-z]+$/.test(m)) deps.push({type:'ref',val:m});
else if (/^\d+$/.test(m)) deps.push({type:'num',val:Number(m)});
else if (m.match(/^[A-Za-z]+$/) && m.length===2 && /^[A-Za-z]$/.test(m[0])) {
// range
const rg = m.slice(1, -1).split(':');
if (rg[0]&&rg[1]) {
let r1 = parseInt(rg[0],10), c1=rg[0].charCodeAt(0)-65, r2=parseInt(rg[1],10),c2=rg[1].charCodeAt(0)-65;
for (let r=r1;r<=r2;r++) for (let c=c1;c<=c2;c++) deps.push({type:'cell',val:`${String.fromCharCode(r+65)}${c+1}`});
} else if (m.length===2) {
// single ref
let r1 = parseInt(m[0],10), c1=m[0].charCodeAt(0)-65, r2=r1, c2=c1;
deps.push({type:'cell',val:`${String.fromCharCode(r1+65)}${c1}`});
}
}
}
}
return deps;
}
function isFormula(cell) { return typeof cell==='string' && cell&&cell[0]==='='; }
/* === DEPENDENCY GRAPH === */
function buildDependencyMap() {
state.dependencyMap.clear(); state.reverseDeps.clear();
for (let r=0;r<GRID_SIZE.rows;r++) {
for (let c=0;c<GRID_SIZE.cols;c++) {
const f = getFormula(r,c);
if (!f) continue;
const deps = getDependencies(r,c);
state.dependencyMap.set(`${r},${c}`, deps);
for (let dep of deps) {
if (dep.type==='cell') {
let depKey = `${dep.val},${r},${c}`;
if (!state.reverseDeps.has(depKey)) state.reverseDeps.set(depKey, []);
state.reverseDeps.get(depKey).push({from:r,c: c});
}
}
}
}
}
function updateDependencies(row, col) {
buildDependencyMap();
// topological sort and recalc
const visited = new Set(), stack=[];
function dfs(r,c) {
if (visited.has(`${r},${c}`)) return;
visited.add(`${r},${c}`);
const deps = state.dependencyMap.get(`${r},${c}`)|| [];
for (let dep of deps) {
if (dep.type==='cell') {
let [r2,c2]=dep.val.split(',').map(x=>parseInt(x,10));
dfs(r2,c2);
}
}
stack.push({r,c});
}
for (let r=0;r<GRID_SIZE.rows;r++) for (let c=0;c<GRID_SIZE.cols;c++) dfs(r,c);
// recalc in reverse order
for (let i=stack.length-1;i>=0;i--) {
const {r,c}=stack[i];
let f = getFormula(r,c);
if (!f) { setCell(r,c,null); continue; }
try {
// simple eval not allowed; use recursive descent
let val = parseExpression(f, r, c);
setCell(r,c,val);
} catch(e) {
setCell(r,c,null);
state.errorCells.add(`${r},${c}`);
}
}
// detect cycles
const inCycle = new Set();
function detectCycle(startR,startC) {
let visited=[], stack=[];
function dfs(r,c) {
if (visited.has(`${r},${c}`)) return true;
visited.push(`${r},${c}`);
stack.push(`${r},${c}`);
const deps = state.dependencyMap.get(`${r},${c}`)|| [];
for (let dep of deps) {
if (dep.type==='cell') {
let [r2,c2]=dep.val.split(',').map(x=>parseInt(x,10));
if (stack.includes(`${r2},${c2}`)) return true;
if (detectCycle(r2,c2)) return true;
}
}
stack.pop();
return false;
}
return dfs(startR,startC);
}
for (let r=0;r<GRID_SIZE.rows;r++) {
for (let c=0;c<GRID_SIZE.cols;c++) {
if (state.errorCells.has(`${r},${c}`)) continue;
if (detectCycle(r,c)) {
state.errorCells.add(`${r},${c}`);
setCell(r,c,null);
}
}
}
}
function parseExpression(expr, r, c) {
// tokenize
const tokens = expr.match(/([A-Za-z]+[0-9]*|[A-Za-z]+|\d+|[()+\-*/])/g) || [];
if (!tokens.length) throw new Error('empty');
// shunting yard
let output=[], ops=[];
const precedence = {
'+':1, '-':1, '*':2, '/':2, '^':3,
'(':0, ')':-1
};
function apply(op) {
if (op==='(') return;
if (op===')') { while(ops[ops.length-1]!=='(') output.push(ops.pop()); }
else {
while (ops.length && precedence[ops[ops.length-1]]>=precedence[op]) output.push(ops.pop());
ops.push(op);
}
}
for (let t of tokens) {
if (/^[A-Za-z]+$/.test(t)) output.push(t);
else if (!isNaN(Number(t))) output.push(Number(t));
else if (t==='(') ops.push(t);
else if (t===')') { while(ops[ops.length-1]!=='(') output.push(ops.pop()); ops.pop(); }
else if (['+','-','*','/','^'].includes(t)) {
while (ops.length && ops[ops.length-1]!=='(' && precedence[ops[ops.length-1]]>=precedence[t]) apply(ops.pop());
ops.push(t);
}
}
while (ops.length) output.push(ops.pop());
// eval
let stack=[];
for (let tok of output) {
if (typeof tok==='number') stack.push(tok);
else if ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.includes(tok)) {
stack.push({type:'cell',val:tok});
} else if (typeof tok==='string' && /^[A-Za-z]+$/.test(tok)) {
let r=r,c=c;
stack.push({type:'ref',val:tok});
} else if (tok==='(') continue;
else if (tok===')') continue;
else if (['+','-','*','/'].includes(tok)) {
let b=stack.pop(), a=stack.pop();
switch(tok) {
case '+': stack.push(a+b); break;
case '-': stack.push(a-b); break;
case '*': stack.push(a*b); break;
case '/':
if (b===0) throw new Error('#DIV/0!');
stack.push(a/b);
break;
}
} else if (tok==='^') {
let b=stack.pop(); let a=stack.pop();
stack.push(Math.pow(a,b));
}
}
return stack.pop();
}
/* === UI === */
function render() {
state.sheetEl = document.getElementById('sheet');
state.formulaBarEl = document.getElementById('formulaBar');
// header
let header = document.createElement('tr');
header.innerHTML = `<th class="colheader">A</th>`;
for (let i=1;i<GRID_SIZE.cols;i++) header.innerHTML += `<th class="colheader">${String.fromCharCode(65+i)}</th>`;
state.sheetEl.appendChild(header);
// rows
let tbody = document.querySelector('tbody');
tbody.innerHTML='';
for (let r=0;r<GRID_SIZE.rows;r++) {
const tr = document.createElement('tr');
// row header
let th = document.createElement('th');
th.className='rowheader';
th.textContent = String.fromCharCode(65+r);
tr.appendChild(th);
for (let c=0;c<GRID_SIZE.cols;c++) {
const cell = document.createElement('td');
cell.className = 'cell';
let val = getCell(r,c);
let f = getFormula(r,c);
if (val!==null) {
cell.textContent = val;
if (typeof val==='number') cell.style.color='#111'; else cell.style.font-weight=0;
} else if (f!==null) {
cell.textContent = f;
cell.style.color=cell.errorCells.has(`${r},${c}`)?COLOR_ERROR:COLOR_TEXT;
cell.style.font-weight=0;
} else {
cell.textContent = '';
cell.style.color=cell.errorCells.has(`${r},${c}`)?COLOR_ERROR:COLOR_TEXT;
cell.style.font-weight=0;
}
// selection
if (r===state.selectedRow && c===state.selectedCol) cell.classList.add('selected');
cell.onclick = ()=>selectCell(r,c);
cell.oncontextmenu = e=>{e.preventDefault(); showContextMenu(e, r,c);};
tr.appendChild(cell);
}
tbody.appendChild(tr);
}
// formula bar
state.formulaBarEl.innerHTML = state.formulas[state.selectedRow][state.selectedCol]||'';
document.getElementById('addCol').onclick = ()=>addColumn();
document.getElementById('delCol').onclick = ()=>delColumn();
document.getElementById('addRow').onclick = ()=>addRow();
document.getElementById('delRow').onclick = ()=>delRow();
// set initial selection
state.selectedRow=0; state.selectedCol=0;
render();
}
function selectCell(r,c) {
state.selectedRow=r; state.selectedCol=c;
render();
}
function showContextMenu(e, r, c) {
let menu = document.createElement('ul');
menu.style.position='absolute';menu.style.left=e.clientX+'px';menu.style.top=e.clientY+'px';menu.style.zIndex=2;menu.style.background=COLOR_BG;menu.style.border=`1px solid #d1e8ff`;menu.style.padding=0.25rem;
['Delete','Insert Row','Insert Column'].forEach(t=>{
let li=document.createElement('li'); li.textContent=t; li.onclick=()=>{ e.preventDefault(); if (t==='Delete') delRow(r); else if(t==='Insert Row') addRow(r+1); else if(t==='Insert Column') insertColumn(c+1); };
menu.appendChild(li);
});
document.body.appendChild(menu);
menu.onclick=e=>{document.body.removeChild(menu);};
}
/* === INPUT HANDLERS === */
function handleInput(e, r,c) {
if (e.key==='Enter'||e.key==='Tab') {
e.preventDefault();
let nextR = r+1, nextC = c;
if (e.key==='Tab') { nextR=r; nextC=(c%GRID_SIZE.cols)+1; }
if (nextR>=GRID_SIZE.rows) { nextR=0; nextC=c+1; if(nextC>=GRID_SIZE.cols) { alert('Column limit'); return; }
}
setCell(nextR,nextC,null); // clear cell
setCell(r,c,null);
state.selectedRow=nextR; state.selectedCol=nextC;
render();
} else if (e.key==='Escape') {
let v=getCell(r,c); setCell(r,c,v); render();
} else if (e.key==='Backspace'||e.key==='Delete') {
let v = getCell(r,c);
if (v===null) return;
let prev = getCell(r,c-1), prevVal = prev !== null ? prev : '';
if (prevVal==='') prevVal = null;
setCell(r,c,prev);
render();
} else if (e.key==='ArrowDown' || e.key==='ArrowUp') {
let dr=e.key==='ArrowDown'?1:-1, nc=c; nc=(c+dr+GRID_SIZE.cols)%GRID_SIZE.cols;
setCell(r+dr,nc,null); setCell(r,c,null); state.selectedRow=r+dr; state.selectedCol=nc; render();
} else if (e.key==='ArrowRight' || e.key==='Tab') {
let nr=r, nc=(c+1)%GRID_SIZE.cols;
setCell(nr,nc,null); setCell(r,c,null); state.selectedRow=nr; state.selectedCol=nc; render();
} else if (e.key==='ArrowLeft') {
let nr=r, nc=(c-1+GRID_SIZE.cols)%GRID_SIZE.cols;
setCell(nr,nc,null); setCell(r,c,null); state.selectedRow=nr; state.selectedCol=nc; render();
} else if (e.key==='ControlOrMeta' && e.key==='z') {
let v = getCell(r,c); setCell(r,c,null); render();
}
// handle formula bar
if (e.target.id==='formulaBarInput') {
let v = e.target.value;
let f = e.target.value;
if (f&&f[0]==='=') {
let newF = v.slice(1).trim();
setFormula(r,c,newF);
e.target.value=newF;
} else {
let nv = e.target.value;
if (nv===undefined||nv=='') setFormula(r,c,null);
else setFormula(r,c,nv);
e.target.value=nv;
}
}
}
document.getElementById('formulaBar').addEventListener('input',e=>{
if (e.target.id==='formulaBarInput') {
let v = e.target.value;
if (v&&v[0]==='=') setFormula(state.selectedRow,state.selectedCol,v.slice(1).trim());
}
});
/* === INIT === */
render();
window.addEventListener('load',()=>{
// init
buildDependencyMap();
// load from localStorage if any
try {
let s = localStorage.getItem('spreadsheet');
if (s) {
let data=JSON.parse(s);
for (let r=0;r<GRID_SIZE.rows;r++) {
for (left of data[r] || []) {
let v=r, c=data[r].indexOf(left);
if (v!==undefined&&c!==undefined) setCell(v,c,left);
}
}
}
} catch(e){}
// save on change
function save() {
let data=[];
for (let r=0;r<GRID_SIZE.rows;r++) {
let row=[];
for (let c=0;c<GRID_SIZE.cols;c++) {
let v = getCell(r,c);
let f = getFormula(r,c);
if (v===null) row.push(null); else row.push(JSON.stringify({val:v,formula:f}));
}
data[r]=row;
}
localStorage.setItem('spreadsheet',JSON.stringify(data));
}
// bind
document.getElementById('formulaBar').oninput = e=>handleInput(e,state.selectedRow,state.selectedCol);
window.addEventListener('keydown',e=>handleInput(e,state.selectedRow,state.selectedCol));
window.addEventListener('keyup',e=>handleInput(e,state.selectedRow,state.selectedCol));
window.addEventListener('blur',(()=>save()));
});
</script>
</body>
</html>
<!-- agent-meta {"model":"liquid/lfm2-24b-a2b","provider":"lmstudio","persona":"basic","sessionId":"31065c4c-aa12-4b4b-8e7a-da0cd8b4487b","tokensIn":1229,"tokensOut":5418,"tokensTotal":6647,"cost":0,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-08-01T04:49:18.324Z"} -->