---
epic: 1
story: 1.8
title: "Sheet Context"
status: draft
---

## Epic 1 — Core Calculation Engine (Rust Crate)
**Goal:** Build `calcpad-engine` as a standalone Rust crate that powers all platforms. This is the foundation.

### Story 1.8: Sheet Context

As a CalcPad engine consumer,
I want a `SheetContext` struct that holds all evaluation state including variables, line results, and dependency graphs,
So that multi-line sheets are evaluated correctly and undo/redo is cheap.

**Acceptance Criteria:**

**Given** a new `SheetContext` is created via `SheetContext::new()`
**When** it is inspected
**Then** it has no lines, no variables, and an empty dependency graph

**Given** a `SheetContext` with `ctx.set_line(0, "x = 10")` and `ctx.set_line(1, "x * 2")`
**When** `ctx.eval()` is called
**Then** it returns a `Vec<CalcResult>` of length 2
**And** the first result is `10` and the second result is `20`

**Given** a `SheetContext` where line 0 defines `x = 10` and line 2 references `x`
**When** line 0 is changed to `x = 20` and `ctx.eval()` is called
**Then** line 2 is re-evaluated and reflects the updated value `x = 20`

**Given** a `SheetContext` with established state
**When** it is cloned via `ctx.clone()`
**Then** the clone is a deep copy that can be modified independently
**And** the clone operation is cheap enough for undo/redo snapshots (sub-millisecond for 100-line sheets)

**Given** a `SheetContext` with a dependency graph where line 3 depends on line 1
**When** only line 1 is modified
**Then** the engine re-evaluates line 1 and line 3 but skips lines that are not in the dependency chain
**And** unchanged, independent lines are not recomputed

**Given** a `SheetContext` where a circular dependency is introduced (e.g., line 1 references line 2 and line 2 references line 1)
**When** `ctx.eval()` is called
**Then** both lines return `CalcResult::Error` with a circular dependency message
**And** the engine does not enter an infinite loop

**Given** a `SheetContext` with lines that include aggregator references such as `total` or `sum`
**When** `ctx.eval()` is called
**Then** the aggregators compute over the appropriate preceding lines
**And** the dependency graph correctly tracks which lines feed into each aggregator
