Some numbers in a project don't belong to a task, a note or a board. Damage and cost across twenty weapon tiers. Yield per hour at four rates. Headcount per phase against the money that pays for it. They aren't a record of what happened; they're a set of dials, and the work is turning one and watching what follows it move.
Curves is a new project item for exactly that.
It is not a spreadsheet
Worth saying up front, because "numbers in a project" usually means a grid and a formula bar, and that's the thing we didn't build.
Watch someone balance a game and they never once think about a cell. They think about two things. The damage curve — is it smooth, does it kink at tier four, is the last third flat. And one weapon — everything about the Runed Sword at once, because that's the thing a player actually holds.
Neither of those is a cell. One is a whole column, the other is a whole row, and the cell is only where they happen to cross. It's a fact about the storage, not a unit of thought. A tool made out of cells makes you assemble both of the things you care about in your head, one number at a time.
So Curves is made out of those two things instead.
Lanes and a probe
Every attribute is a lane: one horizontal band, drawn as a single continuous curve across a shared entity axis. Damage is a lane. Cost is a lane. The entity names — the weapons — are lifted out of the data into a sticky ribbon across the top of the stack, so they belong to every lane at once rather than to a first column.
Lanes are deliberately not all the same height, and there are no vertical rules anywhere in the field. That sounds like styling and isn't. A stack of equal rows separated by lines is a table however you draw it; the uneven rhythm and the single uninterrupted plotting surface are what make it read as a set of progressions.
The probe is the other half. One line crosses every lane at a single entity, and each lane head reads out that entity's value. Move it and you're reading the Runed Sword — its damage, its speed, its cost, its DPS — without a row existing anywhere. Click anywhere in a lane to put the probe there; ← and → walk it. Every drag in the view has a keyboard equivalent, because a design where the only way to change a number is to drag isn't shippable.
One rule, not a copy per row
"DPS is damage times speed" is one sentence. The usual way to express it is to write =C2*D2 once and drag it down the column, which leaves the sentence in your head and a copy of its output in every row — and nothing that can tell you row 147 stopped agreeing with the rest.
When a lane's cells all share one formula shape, Curves reads that back as a rule and prints it at the head of the lane in the table's own words: Damage × Speed. Edit it once and every entity follows. Before it writes anything, the panel tells you what the rule becomes at the entity you're probing and how many cells it's about to change.
The cells that don't match are the reason this is safe to offer, because somebody broke the pattern at row 147 deliberately, for a reason they had at the time. Curves keeps two kinds of deviation, and never silently overwrites either:
- A hand-typed value in a ruled lane is an override. Its handle goes solid, and rewriting the rule leaves it alone.
- A cell carrying a different formula is an exception, and the lane head counts those out loud —
Damage × Speed · 3 differ. Overrides aren't in that count; they're values, not competing rules.
So a whole class of fill-down bug — the pasted cell that broke the pattern, the formula that stopped one row early — stops being invisible.
Underneath, nothing exotic is happening. A rule desugars to exactly the per-cell formula you would have typed yourself. There's one data model, and Curves is a view over it.
Drag it, and watch what follows
Grab a point on a lane and pull. The value changes as you move, and every lane derived from it redraws live — you watch the DPS curve reshape while you're still holding the damage point. Hold Shift and it snaps to round numbers.
The pre-drag curves stay on screen as dashed ghosts, so before and after are one picture rather than two things you compare from memory. Escape reverts the whole gesture, downstream lanes included. Release commits it as a single undo step, not one per pixel.
Cause above effect
An outlier gets a ring, annotated as a multiple of the entity below it — 7.7× the one below rather than a colour that means "unusual, somehow".
And because every lane shares one axis, the ring on the cost lane and the collapse it causes on the value lane sit in the same vertical line. The cause is directly above the effect. A table can't show you that. You have to already know which two columns to compare, and then compare them.
Errors work the same way round. A broken cell breaks the curve — a gap and a hollow marker at that entity — and the lane head says how many there are, rather than printing red into a cell you have to go and find. A lane that ends up referring to itself is caught and named rather than spun on.
One page, and dividers to organise it
A project gets one Curves page, not a folder of them, and opening the section drops you straight into it.
That's a decision rather than a missing feature. The shared axis is the whole design, and two attributes you want to compare landing in two documents puts them on two screens — which is exactly the comparison a shared axis exists to make possible. "Which document is that attribute in" is a filing question, and filing isn't what this is for.
Grouping happens inside the page instead. Any lane can carry a divider above it: a labelled separator that splits the stack into groups — Damage, then Economy, then Drop rates. A spacer is the same thing with no label, for when you want the break without the heading. Both belong to the lane beneath them, so they travel with it when you reorder.
The grid is still there
All of this is one toggle away from a plain cell grid, and the grid is a real grid: same data, same cells, same A1. =SUM(C2:C21) does exactly what you'd expect, and ⌗ in its corner strips it back to bare lettered columns.
Switching over lands the grid's selection on the entity the probe was reading, so the toggle keeps your place. That carry-over runs one way, deliberately — the lane view owns the probe, and reading the grid's selection back would mean two owners of one number.
Formulas are parsed, not run
Worth being explicit about, because it's the part that would be easy to get wrong and hard for you to notice.
There's a four-line shortcut to a formula engine: take the text after the = and hand it to the JavaScript engine. It works immediately. It also means a formula is a program, and a table a colleague sent you is a program that runs when you open it.
We didn't do that. There is no eval, no Function constructor and no dynamic code construction anywhere in that path. Formulas go through a parser we wrote, which understands arithmetic, comparisons, cell references, ranges and a fixed list of functions, and understands nothing else. Lane rules are translated into ordinary A1 text and handed to that same parser, so both views run on one engine over one set of cells.
Limits
A Curve's grid is 200 rows by 40 attributes — 199 entities once a header row is naming the attributes — with up to 8,000 filled cells and 300 KB of content, and a project holds one page. All enforced on the server, not just in the editor. Content is encrypted at rest, the same as your notes and pages.
One thing to know before you use it
Inserting a row or column in the grid view moves your cells, but it does not rewrite formulas that referred to them. A formula pointing at B5 still says B5 after you've pushed the old B5 down to B6. Adding rows or columns at the end is unaffected, and the menu items say so where you click them.
We'd rather name that than have you find it in a number that looks perfectly plausible. It's the next thing we're fixing.
Availability
Curves is on Pro and Max individual plans, and on Team org plans and above.