Skip to content

spreadsheet.kt

src/jvmMain/kotlin/afield/data/table/spreadsheet.kt · 2947 lines

package and imports — 69 lines, from line 1
package afield.data.table

import afield.app.server.app
import afield.document.graph.introImports
import afield.runtime.lifecycle.pipe
import afield.ui.notation.bool
import afield.ui.notation.double
import afield.ui.notation.string
import afield.ui.notation.with
import afield.ui.output.column
import afield.ui.visualization.table

import afield.app.protocol.Handled
import afield.app.protocol.computeIfAbsent
import afield.app.protocol.handle
import afield.app.protocol.payload
import afield.app.protocol.rawHandlers
import afield.app.server.SessionInformation
import afield.app.server.documents
import afield.app.server.thisSession
import afield.app.server.thisSessionInfo
import afield.audio.synth.*
import afield.document.graph.Box
import afield.document.graph.box
import afield.document.graph.doResync
import afield.document.graph.isTrue
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.kotlin.KotlinInterface2
import afield.runtime.lifecycle.Runloop
import afield.runtime.lifecycle.RunsAt
import afield.runtime.lifecycle.end
import afield.runtime.lifecycle.inMain
import afield.runtime.lifecycle.installHandlers
import afield.runtime.lifecycle.map
import afield.tools.docs.format
import afield.ui.output.templates.filter
import afield.ui.output.update
import afield.ui.visualization.ValueVisualizer
import afield.ui.visualization.h

import afield.geometry.path.FLine
import afield.app.diagnostics.logging.lprintln
import afield.math.units.Quantity
import afield.math.units.autoScaleExpression
import afield.math.units.formatExpression
import afield.math.units.formatQuantityDisplay
import afield.math.units.formatUnitSuperscripts
import afield.math.linalg.Vec2
import afield.math.linalg.Vec3
import afield.math.linalg.Vec4
import afield.math.linalg.v3
import eu.sirotin.kotunil.core.*
import io.ktor.websocket.send
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.json.JSONObject
import kotlin.script.experimental.api.ResultValue
import kotlin.script.experimental.api.ScriptDiagnostic
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.math.round
import kotlin.math.sqrt

A spreadsheet that lives in a box property, shown in the box's output area, whose formulas are Kotlin expressions.

Box.spreadsheet (alias Box.sheet) stores the sheet as a SpreadsheetStoredState in a box property (spreadsheet by default) and displays an editor for it in an output channel. A cell whose input starts with = is a formula, compiled with the same K2 engine as box code; inside it, SpreadsheetEvalHelpers supplies cell("A1"), range("A1:A3"), box("name") and aggregates such as sum and avg. The sheet records which members of another box a formula reads through box(...), and open sheets are polled once per frame so they follow those values.

From box code, build or edit the stored state with spreadsheetState / Box.editSpreadsheet, and read evaluated values with Box.getSpreadsheetSnapshot and Box.getSpreadsheetCellValue.

The guide, with captured sheets and the editing keys, is Spreadsheets in a Box.

private const val SPREADSHEET_CHANNEL = "__spreadsheet__"
private const val DEFAULT_SHEET_ROWS = 20
private const val DEFAULT_SHEET_COLS = 12
private const val DEFAULT_SHEET_COL_WIDTH = 120
private const val DEFAULT_SHEET_ROW_HEIGHT = 32
private const val MAX_SHEET_ROWS = 512
private const val MAX_SHEET_COLS = 256
private const val SPREADSHEET_POLL_HOOK = "__spreadsheet_poll__"

data class SpreadsheetStoredCell

@Serializable
data class SpreadsheetStoredCell(
    val row: Int,
    val col: Int,
    val input: String
)

data class SpreadsheetStoredState

@Serializable
data class SpreadsheetStoredState(
    val rows: Int = DEFAULT_SHEET_ROWS,
    val cols: Int = DEFAULT_SHEET_COLS,
    val colWidths: List<Double> = emptyList(),
    val rowHeights: List<Double> = emptyList(),
    val cells: List<SpreadsheetStoredCell> = emptyList(),
    /**
     * The `=free(...)` cells' inputs as the last solve left them, by `row:col`. A variable whose input
     * differs from this was edited since, and the next solve holds it at edit strength. Stored, so an
     * edit made while the sheet was closed, or by `editSheet { }` before it opened, still counts.
     */
    val solved: Map<String, String> = emptyMap()
)

class SpreadsheetStateBuilder internal constructor(state: SpreadsheetStoredState? = null)

var rows: Int = state?.rows ?: DEFAULT_SHEET_ROWS

var cols: Int = state?.cols ?: DEFAULT_SHEET_COLS

    private val colWidths = linkedMapOf<Int, Int>()
    private val rowHeights = linkedMapOf<Int, Int>()
    private val cells = linkedMapOf<String, SpreadsheetStoredCell>()
    private val solved: Map<String, String> = state?.solved.orEmpty()

init

    init {
        state?.colWidths.orEmpty().forEachIndexed { index, width ->
            colWidths[index] = width.toInt()
        }
        state?.rowHeights.orEmpty().forEachIndexed { index, height ->
            rowHeights[index] = height.toInt()
        }
        state?.cells.orEmpty().forEach { cell ->
            cells["${cell.row}:${cell.col}"] = cell
        }
    }

fun cell(ref: String, input: Any?)

    fun cell(ref: String, input: Any?) {
        setCell(parseBuilderCellRef(ref), input)
    }

fun formula(ref: String, expression: String)

    fun formula(ref: String, expression: String) {
        val trimmed = expression.trim()
        cell(ref, if (trimmed.startsWith("=")) trimmed else "=$trimmed")
    }

fun clear(ref: String)

    fun clear(ref: String) {
        cells.remove(cellKey(parseBuilderCellRef(ref)))
    }

fun clearAll()

    fun clearAll() {
        cells.clear()
    }

fun columnWidth(label: String, width: Int)

    fun columnWidth(label: String, width: Int) {
        val col = columnLabelToIndex(label)
            ?: error("Unknown spreadsheet column '$label'")
        columnWidthAt(col, width)
    }

fun columnWidthAt(col: Int, width: Int)

    fun columnWidthAt(col: Int, width: Int) {
        colWidths[col] = width
    }

fun rowHeight(rowNumber: Int, height: Int)

    fun rowHeight(rowNumber: Int, height: Int) {
        rowHeightAt(rowNumber - 1, height)
    }

fun rowHeightAt(row: Int, height: Int)

    fun rowHeightAt(row: Int, height: Int) {
        rowHeights[row] = height
    }

operator fun set(ref: String, input: Any?)

    operator fun set(ref: String, input: Any?) {
        cell(ref, input)
    }

operator fun get(ref: String): String?

    operator fun get(ref: String): String? =
        cells[cellKey(parseBuilderCellRef(ref))]?.input

internal fun build(): SpreadsheetStoredState

    internal fun build(): SpreadsheetStoredState {
        val safeRows = rows.coerceIn(1, MAX_SHEET_ROWS)
        val safeCols = cols.coerceIn(1, MAX_SHEET_COLS)
        val state = SpreadsheetStoredState(
            rows = safeRows,
            cols = safeCols,
            colWidths = List(safeCols) { index -> (colWidths[index] ?: DEFAULT_SHEET_COL_WIDTH).toDouble() },
            rowHeights = List(safeRows) { index -> (rowHeights[index] ?: DEFAULT_SHEET_ROW_HEIGHT).toDouble() },
            cells = cells.values.toList(),
            solved = solved
        )
        return normalizeStoredState(state)
    }

    private fun parseBuilderCellRef(ref: String): CellCoord =
        cellRefToCoord(ref.trim()) ?: error("Unknown spreadsheet cell reference '$ref'")

    private fun setCell(coord: CellCoord, input: Any?) {
        val text = spreadsheetCellInputText(input)
        if (text.isBlank()) {
            cells.remove(cellKey(coord))
        } else {
            cells[cellKey(coord)] = SpreadsheetStoredCell(coord.row, coord.col, text)
        }
        rows = maxOf(rows, coord.row + 1)
        cols = maxOf(cols, coord.col + 1)
    }

    private fun cellKey(coord: CellCoord): String = "${coord.row}:${coord.col}"
}

fun spreadsheetState(init: SpreadsheetStateBuilder.() -> Unit): SpreadsheetStoredState =

fun spreadsheetState(init: SpreadsheetStateBuilder.() -> Unit): SpreadsheetStoredState =
    SpreadsheetStateBuilder().apply(init).build()

fun sheetState(init: SpreadsheetStateBuilder.() -> Unit): SpreadsheetStoredState =

fun sheetState(init: SpreadsheetStateBuilder.() -> Unit): SpreadsheetStoredState =
    spreadsheetState(init)

data class SpreadsheetCellSnapshot

@Serializable
data class SpreadsheetCellSnapshot(
    val row: Int,
    val col: Int,
    val input: String,
    val type: String,
    val display: String,
    val errorDetail: String? = null,
    val htmlDisplay: String? = null
)

val SpreadsheetCellSnapshot.ref: String

val SpreadsheetCellSnapshot.ref: String
    get() = cellLabel(row, col)

data class SpreadsheetSnapshot

@Serializable
data class SpreadsheetSnapshot(
    val uid: String,
    val property: String,
    val rows: Int,
    val cols: Int,
    val colWidths: List<Int>,
    val rowHeights: List<Int>,
    val cells: List<SpreadsheetCellSnapshot>,
    val version: Long,
    /** When the sheet has assertions over variable cells: what the last solve did, in one line. */
    val solve: String? = null
)

fun SpreadsheetStoredState.cell(ref: String): SpreadsheetStoredCell?

fun SpreadsheetStoredState.cell(ref: String): SpreadsheetStoredCell? {
    val coord = cellRefToCoord(ref.trim()) ?: return null
    return cells.firstOrNull { it.row == coord.row && it.col == coord.col }
}

operator fun SpreadsheetStoredState.get(ref: String): SpreadsheetStoredCell?

operator fun SpreadsheetStoredState.get(ref: String): SpreadsheetStoredCell? =
    cell(ref)

fun SpreadsheetSnapshot.cell(ref: String): SpreadsheetCellSnapshot?

fun SpreadsheetSnapshot.cell(ref: String): SpreadsheetCellSnapshot? {
    val coord = cellRefToCoord(ref.trim()) ?: return null
    return cells.firstOrNull { it.row == coord.row && it.col == coord.col }
}

operator fun SpreadsheetSnapshot.get(ref: String): SpreadsheetCellSnapshot?

operator fun SpreadsheetSnapshot.get(ref: String): SpreadsheetCellSnapshot? =
    cell(ref)

data class SpreadsheetCompletionItem

body — 74 lines, from line 264
@Serializable
data class SpreadsheetCompletionItem(
    val label: String,
    val insertText: String,
    val kind: String = "item",
    val detail: String = "",
    val documentation: String = "",
    val replaceStart: Int,
    val replaceEnd: Int,
    val cursorOffset: Int? = null,
    val selectInsertedText: Boolean = false
)

private object SpreadsheetProtocol {
    val json = Json {
        ignoreUnknownKeys = true
        encodeDefaults = true
    }

    @Serializable
    data class Request(
        val channel: String? = null,
        val reqId: String = "",
        val op: String,
        val uid: String? = null,
        val property: String = "spreadsheet",
        val watch: Boolean = true,
        val state: SpreadsheetStoredState? = null,
        val text: String? = null,
        val cursor: Int? = null
    )

    @Serializable
    data class SnapshotEnvelope(
        val reqId: String? = null,
        val ok: Boolean = true,
        val type: String = "sheet",
        val snapshot: SpreadsheetSnapshot
    )

    @Serializable
    data class CompletionEnvelope(
        val reqId: String,
        val ok: Boolean = true,
        val type: String = "completion",
        val items: List<SpreadsheetCompletionItem>
    )

    @Serializable
    data class ErrorEnvelope(
        val reqId: String? = null,
        val ok: Boolean = false,
        val error: String
    )
}

private data class SpreadsheetKey(
    val documentFilename: String,
    val uid: String,
    val property: String
)

private data class SpreadsheetClient(
    val key: Any,
    val send: (String) -> Unit
) {
    override fun equals(other: Any?): Boolean = other is SpreadsheetClient && other.key === key
    override fun hashCode(): Int = System.identityHashCode(key)
}

private data class SpreadsheetRef(
    val uid: String,
    val name: String
)

internal enum class SpreadsheetBoxMemberAccess

internal enum class SpreadsheetBoxMemberAccess {
    GET,
    PROP,
    VARIABLE
}

internal data class SpreadsheetBoxDependency

internal data class SpreadsheetBoxDependency(
    val boxId: String,
    val member: String,
    val access: SpreadsheetBoxMemberAccess
)

internal data class CellCoord

body — 47 lines, from line 351
internal data class CellCoord(
    val row: Int,
    val col: Int
)

private data class ParsedCellRef(
    val row: Int,
    val col: Int,
    val endExclusive: Int
)

private data class SheetWordFragment(
    val start: Int,
    val end: Int,
    val text: String
)

private data class EvalCell(
    val input: String,
    val type: String,
    val value: Any?,
    val display: String,
    val errorDetail: String?,
    val htmlDisplay: String? = null
)

private data class EvalResult(
    val value: Any?,
    val errorDetail: String? = null
)

private data class GeneralCompletion(
    val label: String,
    val insertText: String,
    val kind: String,
    val detail: String,
    val documentation: String = "",
    val cursorOffset: Int? = null,
    val selectInsertedText: Boolean = false
)

private sealed interface CompletionContext {
    data class BoxTarget(val replaceStart: Int, val replaceEnd: Int, val query: String) : CompletionContext
    data class BoxMember(val ref: String, val replaceStart: Int, val replaceEnd: Int, val query: String) : CompletionContext
    data class SelfMember(val replaceStart: Int, val replaceEnd: Int, val query: String) : CompletionContext
    data class General(val replaceStart: Int, val replaceEnd: Int, val query: String) : CompletionContext
}

object SpreadsheetEvalHelpers

internal val currentRuntime = ThreadLocal<SpreadsheetRuntime?>()

    private fun runtime(): SpreadsheetRuntime =
        currentRuntime.get() ?: error("Spreadsheet runtime is not available on this thread")

fun cell(ref: String): SpreadsheetScalar

    @JvmStatic
    fun cell(ref: String): SpreadsheetScalar = runtime().cell(ref)

fun ref(index: Int): String

The indexth reference written in the formula; see rewriteSpreadsheetFormula.

    @JvmStatic
    fun ref(index: Int): String = runtime().refs[index]

fun range(ref: String): List<SpreadsheetScalar>

    @JvmStatic
    fun range(ref: String): List<SpreadsheetScalar> = runtime().range(ref)

fun box(ref: String): SpreadsheetBoxBinding

    @JvmStatic
    fun box(ref: String): SpreadsheetBoxBinding = runtime().box(ref)

fun free(initial: Any?): SpreadsheetScalar

=free(3): this cell is a variable the sheet may solve for, currently 3. The number inside is its previous value, which the sheet prefers to keep; a solve writes the new value back into the formula.

    @JvmStatic
    fun free(initial: Any?): SpreadsheetScalar = SpreadsheetScalar(runtime().declareFree(num(initial)))

fun assertThat(relation: SpreadsheetRelation, left: Any?, right: Any?): SpreadsheetAssertion

An assertion between two values; see SpreadsheetScalar.eq.

    @JvmStatic
    fun assertThat(relation: SpreadsheetRelation, left: Any?, right: Any?): SpreadsheetAssertion {
        val (lv, le) = spreadsheetTracedOperand(left)
            ?: error("${relation.name.lowercase()} needs numbers, found '${SpreadsheetScalar(left)}'")
        val (rv, re) = spreadsheetTracedOperand(right)
            ?: error("${relation.name.lowercase()} needs numbers, found '${SpreadsheetScalar(right)}'")
        val assertion = SpreadsheetAssertion(relation, lv, rv, le, re)
        currentRuntime.get()?.registerAssertion(assertion)
        return assertion
    }

fun total(values: Any?): SpreadsheetScalar

sum, keeping the trace when any term comes from a variable cell.

    @JvmStatic
    fun total(values: Any?): SpreadsheetScalar {
        val flat = flattenAny(values).filterNot { it == null || it == "" }
        if (flat.none { unwrapSpreadsheetValue(it) is SpreadsheetTraced }) return SpreadsheetScalar(sum(values))
        return SpreadsheetScalar(flat.fold(0.0 as Any?) { acc, v -> SpreadsheetDynamicRegistry.binary(SpreadsheetBinaryOp.ADD, acc, v) })
    }

internal fun scalarOrNull(value: Any?): Double? = SpreadsheetDynamicRegistry.scalar(value)

@JvmStatic fun sqrt(x: Any?): SpreadsheetScalar

The math a formula can call. Each keeps the trace when its argument comes from a variable cell, so an assertion over sqrt(A1) can still move A1; over plain numbers it is plain arithmetic.

    @JvmStatic fun sqrt(x: Any?): SpreadsheetScalar = spreadsheetTracedUnary(x, { kotlin.math.sqrt(it) }, { afield.math.optimization.ceres.sqrt(it) })

@JvmStatic fun abs(x: Any?): SpreadsheetScalar

    @JvmStatic fun abs(x: Any?): SpreadsheetScalar = spreadsheetTracedUnary(x, { kotlin.math.abs(it) }, { afield.math.optimization.ceres.abs(it) })

@JvmStatic fun exp(x: Any?): SpreadsheetScalar

    @JvmStatic fun exp(x: Any?): SpreadsheetScalar = spreadsheetTracedUnary(x, { kotlin.math.exp(it) }, { afield.math.optimization.ceres.exp(it) })

@JvmStatic fun ln(x: Any?): SpreadsheetScalar

    @JvmStatic fun ln(x: Any?): SpreadsheetScalar = spreadsheetTracedUnary(x, { kotlin.math.ln(it) }, { afield.math.optimization.ceres.ln(it) })

@JvmStatic fun sin(x: Any?): SpreadsheetScalar

    @JvmStatic fun sin(x: Any?): SpreadsheetScalar = spreadsheetTracedUnary(x, { kotlin.math.sin(it) }, { afield.math.optimization.ceres.sin(it) })

@JvmStatic fun cos(x: Any?): SpreadsheetScalar

    @JvmStatic fun cos(x: Any?): SpreadsheetScalar = spreadsheetTracedUnary(x, { kotlin.math.cos(it) }, { afield.math.optimization.ceres.cos(it) })

@JvmStatic fun tan(x: Any?): SpreadsheetScalar

    @JvmStatic fun tan(x: Any?): SpreadsheetScalar = spreadsheetTracedUnary(x, { kotlin.math.tan(it) }, { afield.math.optimization.ceres.tan(it) })

@JvmStatic fun atan2(y: Any?, x: Any?): SpreadsheetScalar

    @JvmStatic fun atan2(y: Any?, x: Any?): SpreadsheetScalar =
        spreadsheetTracedBinaryFn(y, x, { a, b -> kotlin.math.atan2(a, b) }, { a, b -> afield.math.optimization.ceres.atan2(a, b) })

@JvmStatic fun pow(x: Any?, p: Any?): SpreadsheetScalar

    @JvmStatic fun pow(x: Any?, p: Any?): SpreadsheetScalar =
        spreadsheetTracedBinaryFn(x, p, { a, b -> Math.pow(a, b) }, { a, b -> afield.math.optimization.ceres.pow(a, b) })

val self: SpreadsheetBoxBinding

    @JvmStatic
    val self: SpreadsheetBoxBinding
        get() = runtime().self()

fun sum(values: Any?): Double

    @JvmStatic
    fun sum(values: Any?): Double = flattenNumeric(values).sum()

fun avg(values: Any?): Double

    @JvmStatic
    // Unlike min/max, an empty average has no sensible value (Excel's `#DIV/0!`), so the cell errors
    // rather than showing a 0 that looks like data.
    fun avg(values: Any?): Double {
        val flattened = flattenNumeric(values)
        if (flattened.isEmpty()) error("average of a range with no numbers")
        return flattened.sum() / flattened.size
    }

fun mean(values: Any?): Double

    @JvmStatic
    fun mean(values: Any?): Double = avg(values)

fun min(values: Any?): Double

An empty range is 0, as in Excel and Sheets, so max(A1:A3) + 1 is a formula rather than a compile error about adding to a Double?.

    @JvmStatic
    fun min(values: Any?): Double = flattenNumeric(values).minOrNull() ?: 0.0

fun max(values: Any?): Double

    @JvmStatic
    fun max(values: Any?): Double = flattenNumeric(values).maxOrNull() ?: 0.0

fun count(values: Any?): Int

    @JvmStatic
    fun count(values: Any?): Int = flattenAny(values).size

fun nonEmpty(values: Any?): List<Any?>

    @JvmStatic
    fun nonEmpty(values: Any?): List<Any?> = flattenAny(values).filterNot { it == null || it == "" }

class Failure internal constructor(val error: Throwable)

What a formula that threw evaluates to: the formula body runs inside a try, so the error that reaches the cell is the one the formula raised, not however the REPL reports a throw.

fun failed(error: Throwable): Any

    @JvmStatic
    fun failed(error: Throwable): Any = Failure(error)

fun product(values: Any?): Double

    @JvmStatic
    fun product(values: Any?): Double = flattenNumeric(values).fold(1.0) { a, b -> a * b }

fun num(value: Any?): Double

A cell, a number, a vector (its length) or a line (its diagonal) as a Double, for sqrt(A1).

    @JvmStatic
    fun num(value: Any?): Double = SpreadsheetDynamicRegistry.requireScalar(value)

internal fun flattenAny(value: Any?): List<Any?>

    internal fun flattenAny(value: Any?): List<Any?> = when (value) {
        null -> emptyList()
        is SpreadsheetScalar -> flattenAny(value.raw)
        is Iterable<*> -> value.flatMap { flattenAny(it) }
        is Array<*> -> value.flatMap { flattenAny(it) }
        else -> listOf(value)
    }

    private fun flattenNumeric(value: Any?): List<Double> =
        flattenAny(value).mapNotNull { numericValue(it) }
}

private fun <T> withSpreadsheetRuntime(runtime: SpreadsheetRuntime, block: () -> T): T {
    val previous = SpreadsheetEvalHelpers.currentRuntime.get()
    SpreadsheetEvalHelpers.currentRuntime.set(runtime)
    return try {
        block()
    } finally {
        SpreadsheetEvalHelpers.currentRuntime.set(previous)
    }
}

private fun unwrapSpreadsheetValue(value: Any?): Any? = when (value) {
    is SpreadsheetScalar -> value.raw
    else -> value
}

internal enum class SpreadsheetBinaryOp(val symbol: String)

declaration — 539 lines, from line 537
internal enum class SpreadsheetBinaryOp(val symbol: String) {
    ADD("+"),
    SUBTRACT("-"),
    MULTIPLY("*"),
    DIVIDE("/"),
    REMAINDER("%")
}

private data class SpreadsheetFLineSummary(
    val x: Double,
    val y: Double,
    val w: Double,
    val h: Double,
    val centerX: Double,
    val centerY: Double,
    val diagonal: Double
) {
    fun center2(): Vec2 = Vec2(centerX.toFloat(), centerY.toFloat())
    fun center3(): Vec3 = Vec3(centerX.toFloat(), centerY.toFloat(), 0f)
    fun bounds4(): Vec4 = Vec4(x.toFloat(), y.toFloat(), w.toFloat(), h.toFloat())
}

private val SPREADSHEET_COMPONENT_REGEX = Regex("""[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?""")

private fun summarizeFLine(line: FLine): SpreadsheetFLineSummary {
    val bounds = runCatching { line.fast2DBounds() }.getOrNull()
    if (bounds == null) {
        return SpreadsheetFLineSummary(
            x = 0.0,
            y = 0.0,
            w = 0.0,
            h = 0.0,
            centerX = 0.0,
            centerY = 0.0,
            diagonal = 0.0
        )
    }
    val centerX = bounds.x + bounds.w * 0.5
    val centerY = bounds.y + bounds.h * 0.5
    return SpreadsheetFLineSummary(
        x = bounds.x,
        y = bounds.y,
        w = bounds.w,
        h = bounds.h,
        centerX = centerX,
        centerY = centerY,
        diagonal = sqrt(bounds.w * bounds.w + bounds.h * bounds.h)
    )
}

private fun spreadsheetLooseNumericComponents(value: Any?): List<Double>? {
    val normalized = unwrapSpreadsheetValue(value)
    return when (normalized) {
        null -> emptyList()
        is Double -> listOf(normalized)
        is Float -> listOf(normalized.toDouble())
        is Int -> listOf(normalized.toDouble())
        is Long -> listOf(normalized.toDouble())
        is Short -> listOf(normalized.toDouble())
        is Byte -> listOf(normalized.toDouble())
        is Number -> listOf(normalized.toDouble())
        is Boolean -> listOf(if (normalized) 1.0 else 0.0)
        is String -> SPREADSHEET_COMPONENT_REGEX.findAll(normalized).mapNotNull { it.value.toDoubleOrNull() }.toList().ifEmpty { null }
        is Vec2 -> listOf(normalized.x.toDouble(), normalized.y.toDouble())
        is Vec3 -> listOf(normalized.x.toDouble(), normalized.y.toDouble(), normalized.z.toDouble())
        is Vec4 -> listOf(normalized.x.toDouble(), normalized.y.toDouble(), normalized.z.toDouble(), normalized.w.toDouble())
        is Iterable<*> -> normalized.flatMap { spreadsheetLooseNumericComponents(it).orEmpty() }
        is Array<*> -> normalized.flatMap { spreadsheetLooseNumericComponents(it).orEmpty() }
        is IntArray -> normalized.map { it.toDouble() }
        is LongArray -> normalized.map { it.toDouble() }
        is FloatArray -> normalized.map { it.toDouble() }
        is DoubleArray -> normalized.toList()
        is ShortArray -> normalized.map { it.toDouble() }
        is ByteArray -> normalized.map { it.toDouble() }
        is BooleanArray -> normalized.map { if (it) 1.0 else 0.0 }
        else -> null
    }
}

private fun spreadsheetVectorFromLooseComponents(value: Any?, size: Int): FloatArray? {
    val components = spreadsheetLooseNumericComponents(value) ?: return null
    if (components.isEmpty()) return FloatArray(size)
    if (components.size == 1) return FloatArray(size) { components.first().toFloat() }
    return FloatArray(size) { index ->
        if (index < components.size) components[index].toFloat() else 0f
    }
}

private fun spreadsheetActualVectorDimension(value: Any?): Int? = when (unwrapSpreadsheetValue(value)) {
    is Vec2 -> 2
    is Vec3 -> 3
    is Vec4 -> 4
    else -> null
}

private fun spreadsheetBinaryComponent(op: SpreadsheetBinaryOp, left: Double, right: Double): Double = when (op) {
    SpreadsheetBinaryOp.ADD -> left + right
    SpreadsheetBinaryOp.SUBTRACT -> left - right
    SpreadsheetBinaryOp.MULTIPLY -> left * right
    SpreadsheetBinaryOp.DIVIDE -> left / right
    SpreadsheetBinaryOp.REMAINDER -> left % right
}

private fun spreadsheetListElements(value: Any?): List<Any?>? {
    val normalized = unwrapSpreadsheetValue(value)
    return when (normalized) {
        is Iterable<*> -> normalized.map { unwrapSpreadsheetValue(it) }
        is Array<*> -> normalized.map { unwrapSpreadsheetValue(it) }
        is IntArray -> normalized.map { it }
        is LongArray -> normalized.map { it }
        is FloatArray -> normalized.map { it }
        is DoubleArray -> normalized.toList()
        is ShortArray -> normalized.map { it }
        is ByteArray -> normalized.map { it }
        is BooleanArray -> normalized.map { it }
        else -> null
    }
}

private fun spreadsheetBroadcastLists(
    left: List<Any?>,
    right: List<Any?>,
    op: (Any?, Any?) -> Any?
): List<Any?> {
    if (left.isEmpty() || right.isEmpty()) return emptyList()
    if (left.size == right.size) return left.indices.map { index -> unwrapSpreadsheetValue(op(left[index], right[index])) }
    if (left.size == 1) return right.map { unwrapSpreadsheetValue(op(left.first(), it)) }
    if (right.size == 1) return left.map { unwrapSpreadsheetValue(op(it, right.first())) }
    error("Cannot broadcast lists of size ${left.size} and ${right.size}")
}

private object SpreadsheetDynamicRegistry {
    private data class BinaryRule(
        val name: String,
        val matches: (Any?, Any?) -> Boolean,
        val apply: (Any?, Any?) -> Any?
    )

    private data class CoercionRule<T>(
        val name: String,
        val apply: (Any?) -> T?
    )

    private val binaryRules = SpreadsheetBinaryOp.entries.associateWith { mutableListOf<BinaryRule>() }
    private val scalarRules = mutableListOf<CoercionRule<Double>>()
    private val vec2Rules = mutableListOf<CoercionRule<Vec2>>()
    private val vec3Rules = mutableListOf<CoercionRule<Vec3>>()
    private val vec4Rules = mutableListOf<CoercionRule<Vec4>>()

    init {
        registerTracedRules()
        registerBuiltins()
    }

    // First, so a number computed from a variable cell keeps its trace through + - * /. A list or a
    // vector on either side is not a scalar operand, so it falls through to the broadcast rules, which
    // come back here per element.
    private fun registerTracedRules() {
        registerScalar("traced") { value -> (value as? SpreadsheetTraced)?.value }
        SpreadsheetBinaryOp.entries.forEach { op ->
            registerBinary(
                op,
                "traced",
                { left, right -> left is SpreadsheetTraced || right is SpreadsheetTraced },
                { left, right -> spreadsheetTracedBinary(op, left, right) ?: fallbackBinary(op, left, right) }
            )
        }
    }

    fun binary(op: SpreadsheetBinaryOp, left: Any?, right: Any?): Any? {
        val normalizedLeft = unwrapSpreadsheetValue(left)
        val normalizedRight = unwrapSpreadsheetValue(right)
        for (rule in binaryRules.getValue(op)) {
            if (!rule.matches(normalizedLeft, normalizedRight)) continue
            return unwrapSpreadsheetValue(rule.apply(normalizedLeft, normalizedRight))
        }
        return fallbackBinary(op, normalizedLeft, normalizedRight)
    }

    fun scalar(value: Any?): Double? = coerce(scalarRules, unwrapSpreadsheetValue(value))

    fun requireScalar(value: Any?): Double =
        scalar(value) ?: error("Expected a scalar-like value, found '${formatSpreadsheetValue(value)}'")

    fun vec2(value: Any?): Vec2? = coerce(vec2Rules, unwrapSpreadsheetValue(value))

    fun requireVec2(value: Any?): Vec2 =
        vec2(value) ?: error("Expected a Vec2-like value, found '${formatSpreadsheetValue(value)}'")

    fun vec3(value: Any?): Vec3? = coerce(vec3Rules, unwrapSpreadsheetValue(value))

    fun requireVec3(value: Any?): Vec3 =
        vec3(value) ?: error("Expected a Vec3-like value, found '${formatSpreadsheetValue(value)}'")

    fun vec4(value: Any?): Vec4? = coerce(vec4Rules, unwrapSpreadsheetValue(value))

    fun requireVec4(value: Any?): Vec4 =
        vec4(value) ?: error("Expected a Vec4-like value, found '${formatSpreadsheetValue(value)}'")

    fun component(value: Any?, index: Int): Any? {
        val normalized = unwrapSpreadsheetValue(value)
        return when (normalized) {
            null -> null
            is Vec2 -> listOf(normalized.x.toDouble(), normalized.y.toDouble()).getOrNull(index)
            is Vec3 -> listOf(normalized.x.toDouble(), normalized.y.toDouble(), normalized.z.toDouble()).getOrNull(index)
            is Vec4 -> listOf(normalized.x.toDouble(), normalized.y.toDouble(), normalized.z.toDouble(), normalized.w.toDouble()).getOrNull(index)
            is FLine -> {
                val summary = summarizeFLine(normalized)
                listOf(summary.x, summary.y, summary.w, summary.h).getOrNull(index)
            }

            else -> spreadsheetListElements(normalized)?.getOrNull(index)
                ?: spreadsheetLooseNumericComponents(normalized)?.getOrNull(index)
                ?: if (index == 0) scalar(normalized) else null
        }
    }

    private fun registerBuiltins() {
        // An empty cell is 0 in arithmetic, as in Excel and Sheets, so `A3 * 10` over a blank A3 is 0
        // rather than #ERR ("Expected a scalar-like value, found ''").
        registerScalar("empty") { value -> if (value == null || value == "") 0.0 else null }
        registerScalar("number") { value ->
            when (value) {
                is Double -> value
                is Float -> value.toDouble()
                is Int -> value.toDouble()
                is Long -> value.toDouble()
                is Short -> value.toDouble()
                is Byte -> value.toDouble()
                is Number -> value.toDouble()
                else -> null
            }
        }
        registerScalar("boolean") { value ->
            if (value is Boolean) {
                if (value) 1.0 else 0.0
            } else {
                null
            }
        }
        registerScalar("string-double") { value ->
            if (value is String) value.trim().toDoubleOrNull() else null
        }
        registerScalar("vector-length") { value ->
            when (value) {
                is Vec2 -> value.length().toDouble()
                is Vec3 -> value.length().toDouble()
                is Vec4 -> value.length().toDouble()
                else -> null
            }
        }
        registerScalar("fline-bounds") { value ->
            if (value is FLine) summarizeFLine(value).diagonal else null
        }
        registerScalar("numeric-components") { value ->
            val components = spreadsheetLooseNumericComponents(value) ?: return@registerScalar null
            when (components.size) {
                0 -> 0.0
                1 -> components.first()
                else -> sqrt(components.sumOf { it * it })
            }
        }

        registerVec2("vec2") { value ->
            when (value) {
                is Vec2 -> Vec2(value)
                is Vec3 -> Vec2(value.x, value.y)
                is Vec4 -> Vec2(value.x, value.y)
                is FLine -> summarizeFLine(value).center2()
                else -> spreadsheetVectorFromLooseComponents(value, 2)?.let { Vec2(it[0], it[1]) }
            }
        }
        registerVec3("vec3") { value ->
            when (value) {
                is Vec2 -> Vec3(value.x, value.y, 0f)
                is Vec3 -> Vec3(value)
                is Vec4 -> Vec3(value.x, value.y, value.z)
                is FLine -> summarizeFLine(value).center3()
                else -> spreadsheetVectorFromLooseComponents(value, 3)?.let { Vec3(it[0], it[1], it[2]) }
            }
        }
        registerVec4("vec4") { value ->
            when (value) {
                is Vec2 -> Vec4(value.x, value.y, 0f, 0f)
                is Vec3 -> Vec4(value.x, value.y, value.z, 0f)
                is Vec4 -> Vec4(value)
                is FLine -> summarizeFLine(value).bounds4()
                else -> spreadsheetVectorFromLooseComponents(value, 4)?.let { Vec4(it[0], it[1], it[2], it[3]) }
            }
        }

        SpreadsheetBinaryOp.entries.forEach { op ->
            registerBinary(
                op,
                "list-broadcast",
                { left, right -> spreadsheetListElements(left) != null || spreadsheetListElements(right) != null },
                { left, right ->
                val leftList = spreadsheetListElements(left)
                val rightList = spreadsheetListElements(right)
                when {
                    leftList != null && rightList != null -> spreadsheetBroadcastLists(leftList, rightList) { a, b -> binary(op, a, b) }
                    leftList != null -> leftList.map { unwrapSpreadsheetValue(binary(op, it, right)) }
                    rightList != null -> rightList.map { unwrapSpreadsheetValue(binary(op, left, it)) }
                    else -> emptyList<Any?>()
                }
                }
            )
        }

        registerBinary(SpreadsheetBinaryOp.ADD, "fline-left", { left, _ -> left is FLine }) { left, right ->
            applyFLineBinary(left as FLine, SpreadsheetBinaryOp.ADD, right)
        }
        registerBinary(SpreadsheetBinaryOp.SUBTRACT, "fline-left", { left, _ -> left is FLine }) { left, right ->
            applyFLineBinary(left as FLine, SpreadsheetBinaryOp.SUBTRACT, right)
        }
        registerBinary(SpreadsheetBinaryOp.MULTIPLY, "fline-left", { left, _ -> left is FLine }) { left, right ->
            applyFLineBinary(left as FLine, SpreadsheetBinaryOp.MULTIPLY, right)
        }
        registerBinary(SpreadsheetBinaryOp.DIVIDE, "fline-left", { left, _ -> left is FLine }) { left, right ->
            applyFLineBinary(left as FLine, SpreadsheetBinaryOp.DIVIDE, right)
        }
        registerBinary(SpreadsheetBinaryOp.REMAINDER, "fline-left", { left, _ -> left is FLine }) { left, right ->
            applyFLineBinary(left as FLine, SpreadsheetBinaryOp.REMAINDER, right)
        }
        registerBinary(SpreadsheetBinaryOp.ADD, "fline-right", { _, right -> right is FLine }) { left, right ->
            applyFLineBinary(right as FLine, SpreadsheetBinaryOp.ADD, left)
        }
        registerBinary(SpreadsheetBinaryOp.MULTIPLY, "fline-right", { _, right -> right is FLine }) { left, right ->
            applyFLineBinary(right as FLine, SpreadsheetBinaryOp.MULTIPLY, left)
        }

        SpreadsheetBinaryOp.entries.forEach { op ->
            registerBinary(
                op,
                "vector",
                { left, right -> spreadsheetActualVectorDimension(left) != null || spreadsheetActualVectorDimension(right) != null },
                { left, right -> applyVectorBinary(op, left, right) }
            )
        }

        registerScalar("quantity") { value ->
            when (value) {
                is Quantity -> value.displayValue
                is Expression -> value.value
                else -> null
            }
        }

        SpreadsheetBinaryOp.entries.forEach { op ->
            registerBinary(
                op,
                "quantity",
                { left, right -> left is Quantity || left is Expression || right is Quantity || right is Expression },
                { left, right -> applyQuantityBinary(op, left, right) }
            )
        }
    }

    private fun fallbackBinary(op: SpreadsheetBinaryOp, left: Any?, right: Any?): Any? = when (op) {
        SpreadsheetBinaryOp.ADD -> {
            val leftNumeric = scalar(left)
            val rightNumeric = scalar(right)
            when {
                leftNumeric != null && rightNumeric != null -> leftNumeric + rightNumeric
                left == null -> right
                right == null -> left
                else -> "${formatSpreadsheetValue(left)}${formatSpreadsheetValue(right)}"
            }
        }

        SpreadsheetBinaryOp.SUBTRACT -> requireScalar(left) - requireScalar(right)
        SpreadsheetBinaryOp.MULTIPLY -> requireScalar(left) * requireScalar(right)
        SpreadsheetBinaryOp.DIVIDE -> requireScalar(left) / requireScalar(right)
        SpreadsheetBinaryOp.REMAINDER -> requireScalar(left) % requireScalar(right)
    }

    private fun applyFLineBinary(line: FLine, op: SpreadsheetBinaryOp, other: Any?): Any {
        val vectorDimension = spreadsheetActualVectorDimension(other)
        val scalarValue = scalar(other)?.toFloat() ?: 0f
        return when (op) {
            SpreadsheetBinaryOp.ADD -> when (vectorDimension) {
                2 -> line + requireVec2(other)
                3, 4 -> line + requireVec3(other)
                else -> line + Vec3(scalarValue, scalarValue, scalarValue)
            }

            SpreadsheetBinaryOp.SUBTRACT -> when (vectorDimension) {
                2 -> line - requireVec2(other)
                3, 4 -> line - requireVec3(other)
                else -> line - Vec3(scalarValue, scalarValue, scalarValue)
            }

            SpreadsheetBinaryOp.MULTIPLY -> when (vectorDimension) {
                2 -> line * requireVec2(other)
                3, 4 -> line * requireVec3(other)
                else -> line * requireScalar(other)
            }

            SpreadsheetBinaryOp.DIVIDE -> when (vectorDimension) {
                2 -> {
                    val divisor = requireVec2(other)
                    line.transform { point -> Vec3(point.x / divisor.x, point.y / divisor.y, point.z) }
                }

                3, 4 -> {
                    val divisor = requireVec3(other)
                    line.transform { point -> Vec3(point.x / divisor.x, point.y / divisor.y, point.z / divisor.z) }
                }

                else -> {
                    val divisor = requireScalar(other).toFloat()
                    line.transform { point -> Vec3(point.x / divisor, point.y / divisor, point.z / divisor) }
                }
            }

            SpreadsheetBinaryOp.REMAINDER -> when (vectorDimension) {
                2 -> {
                    val divisor = requireVec2(other)
                    line.transform { point -> Vec3(point.x % divisor.x, point.y % divisor.y, point.z) }
                }

                3, 4 -> {
                    val divisor = requireVec3(other)
                    line.transform { point -> Vec3(point.x % divisor.x, point.y % divisor.y, point.z % divisor.z) }
                }

                else -> {
                    val divisor = requireScalar(other).toFloat()
                    line.transform { point -> Vec3(point.x % divisor, point.y % divisor, point.z % divisor) }
                }
            }
        }
    }

    private fun toExpression(value: Any?): Expression? = when (value) {
        is Quantity -> value.expression
        is Expression -> value
        is Number -> Expression(value.toDouble(), eu.sirotin.kotunil.core.Dimensions(emptySet()))
        else -> {
            val d = scalar(value)
            if (d != null) Expression(d, eu.sirotin.kotunil.core.Dimensions(emptySet())) else null
        }
    }

    private fun quantityDisplayUnit(value: Any?): Expression? = when (value) {
        is Quantity -> value.displayUnit
        else -> null
    }

    private fun isPlainNumber(value: Any?): Boolean = value !is Quantity && value !is Expression

    private fun scaledDisplayUnit(left: Any?, right: Any?): Expression? = when {
        isPlainNumber(right) -> quantityDisplayUnit(left)
        isPlainNumber(left) -> quantityDisplayUnit(right)
        else -> null
    }

    private fun applyQuantityBinary(op: SpreadsheetBinaryOp, left: Any?, right: Any?): Any? {
        val leftExpr = toExpression(left) ?: error("Cannot convert '${formatSpreadsheetValue(left)}' to a unit expression")
        val rightExpr = toExpression(right) ?: error("Cannot convert '${formatSpreadsheetValue(right)}' to a unit expression")
        return when (op) {
            SpreadsheetBinaryOp.ADD -> Quantity(leftExpr + rightExpr, quantityDisplayUnit(left) ?: quantityDisplayUnit(right))
            SpreadsheetBinaryOp.SUBTRACT -> Quantity(leftExpr - rightExpr, quantityDisplayUnit(left) ?: quantityDisplayUnit(right))
            // Scaling by a plain number keeps the unit it was written in: `(0.21 | m) * 3` is 0.63 m,
            // not the auto-scaled 6.3 dm.
            SpreadsheetBinaryOp.MULTIPLY -> Quantity(leftExpr * rightExpr, scaledDisplayUnit(left, right))
            SpreadsheetBinaryOp.DIVIDE -> Quantity(leftExpr / rightExpr, if (isPlainNumber(right)) quantityDisplayUnit(left) else null)
            SpreadsheetBinaryOp.REMAINDER -> Quantity(leftExpr % rightExpr)
        }
    }

    private fun applyVectorBinary(op: SpreadsheetBinaryOp, left: Any?, right: Any?): Any {
        val dimension = maxOf(spreadsheetActualVectorDimension(left) ?: 0, spreadsheetActualVectorDimension(right) ?: 0)
        return when (dimension) {
            2 -> {
                val a = requireVec2(left)
                val b = requireVec2(right)
                Vec2(
                    spreadsheetBinaryComponent(op, a.x.toDouble(), b.x.toDouble()).toFloat(),
                    spreadsheetBinaryComponent(op, a.y.toDouble(), b.y.toDouble()).toFloat()
                )
            }

            3 -> {
                val a = requireVec3(left)
                val b = requireVec3(right)
                Vec3(
                    spreadsheetBinaryComponent(op, a.x.toDouble(), b.x.toDouble()).toFloat(),
                    spreadsheetBinaryComponent(op, a.y.toDouble(), b.y.toDouble()).toFloat(),
                    spreadsheetBinaryComponent(op, a.z.toDouble(), b.z.toDouble()).toFloat()
                )
            }

            else -> {
                val a = requireVec4(left)
                val b = requireVec4(right)
                Vec4(
                    spreadsheetBinaryComponent(op, a.x.toDouble(), b.x.toDouble()).toFloat(),
                    spreadsheetBinaryComponent(op, a.y.toDouble(), b.y.toDouble()).toFloat(),
                    spreadsheetBinaryComponent(op, a.z.toDouble(), b.z.toDouble()).toFloat(),
                    spreadsheetBinaryComponent(op, a.w.toDouble(), b.w.toDouble()).toFloat()
                )
            }
        }
    }

    private fun registerBinary(
        op: SpreadsheetBinaryOp,
        name: String,
        matches: (Any?, Any?) -> Boolean,
        apply: (Any?, Any?) -> Any?
    ) {
        binaryRules.getValue(op) += BinaryRule(name, matches, apply)
    }

    private fun registerScalar(name: String, apply: (Any?) -> Double?) {
        scalarRules += CoercionRule(name, apply)
    }

    private fun registerVec2(name: String, apply: (Any?) -> Vec2?) {
        vec2Rules += CoercionRule(name, apply)
    }

    private fun registerVec3(name: String, apply: (Any?) -> Vec3?) {
        vec3Rules += CoercionRule(name, apply)
    }

    private fun registerVec4(name: String, apply: (Any?) -> Vec4?) {
        vec4Rules += CoercionRule(name, apply)
    }

    private fun <T> coerce(rules: List<CoercionRule<T>>, value: Any?): T? {
        for (rule in rules) {
            val result = runCatching { rule.apply(value) }.getOrNull()
            if (result != null) return result
        }
        return null
    }
}

class SpreadsheetScalar internal constructor(val raw: Any?) : Comparable<Any?>

class SpreadsheetScalar internal constructor(val raw: Any?) : Comparable<Any?> {
    private fun otherValue(other: Any?): Any? = unwrapSpreadsheetValue(other)

val d: Double

    val d: Double
        get() = SpreadsheetDynamicRegistry.requireScalar(raw)

val v2: Vec2

    val v2: Vec2
        get() = SpreadsheetDynamicRegistry.requireVec2(raw)

val v3: Vec3

    val v3: Vec3
        get() = SpreadsheetDynamicRegistry.requireVec3(raw)

val v4: Vec4

    val v4: Vec4
        get() = SpreadsheetDynamicRegistry.requireVec4(raw)

val x: SpreadsheetScalar

    val x: SpreadsheetScalar
        get() = SpreadsheetScalar(SpreadsheetDynamicRegistry.component(raw, 0))

val y: SpreadsheetScalar

    val y: SpreadsheetScalar
        get() = SpreadsheetScalar(SpreadsheetDynamicRegistry.component(raw, 1))

val z: SpreadsheetScalar

    val z: SpreadsheetScalar
        get() = SpreadsheetScalar(SpreadsheetDynamicRegistry.component(raw, 2))

val w: SpreadsheetScalar

    val w: SpreadsheetScalar
        get() = SpreadsheetScalar(SpreadsheetDynamicRegistry.component(raw, 3))

val width: SpreadsheetScalar

The width of a line's bounds. x, y, z, w of a line are its bounds as the components (x, y, width, height) — so .w is the height — which is why the two have names of their own.

    val width: SpreadsheetScalar
        get() = SpreadsheetScalar(boundsOf("width")?.w)

val height: SpreadsheetScalar

The height of a line's bounds; see width.

    val height: SpreadsheetScalar
        get() = SpreadsheetScalar(boundsOf("height")?.h)

    private fun boundsOf(what: String): SpreadsheetFLineSummary? = when (val value = unwrapSpreadsheetValue(raw)) {
        null -> null
        is FLine -> summarizeFLine(value)
        else -> error("$what needs a line, found '${formatSpreadsheetValue(value)}'")
    }

operator fun get(index: Int): SpreadsheetScalar

    operator fun get(index: Int): SpreadsheetScalar =
        SpreadsheetScalar(SpreadsheetDynamicRegistry.component(raw, index))

operator fun plus(other: Any?): SpreadsheetScalar

    operator fun plus(other: Any?): SpreadsheetScalar =
        SpreadsheetScalar(SpreadsheetDynamicRegistry.binary(SpreadsheetBinaryOp.ADD, raw, otherValue(other)))

operator fun minus(other: Any?): SpreadsheetScalar

    operator fun minus(other: Any?): SpreadsheetScalar =
        SpreadsheetScalar(SpreadsheetDynamicRegistry.binary(SpreadsheetBinaryOp.SUBTRACT, raw, otherValue(other)))

operator fun times(other: Any?): SpreadsheetScalar

    operator fun times(other: Any?): SpreadsheetScalar =
        SpreadsheetScalar(SpreadsheetDynamicRegistry.binary(SpreadsheetBinaryOp.MULTIPLY, raw, otherValue(other)))

operator fun div(other: Any?): SpreadsheetScalar

    operator fun div(other: Any?): SpreadsheetScalar =
        SpreadsheetScalar(SpreadsheetDynamicRegistry.binary(SpreadsheetBinaryOp.DIVIDE, raw, otherValue(other)))

operator fun rem(other: Any?): SpreadsheetScalar

    operator fun rem(other: Any?): SpreadsheetScalar =
        SpreadsheetScalar(SpreadsheetDynamicRegistry.binary(SpreadsheetBinaryOp.REMAINDER, raw, otherValue(other)))

operator fun unaryMinus(): SpreadsheetScalar

    operator fun unaryMinus(): SpreadsheetScalar {
        val value = unwrapSpreadsheetValue(raw)
        if (value is SpreadsheetTraced) return SpreadsheetScalar(SpreadsheetTraced(-value.value, -value.expr))
        return when (spreadsheetActualVectorDimension(value)) {
            2 -> SpreadsheetScalar(Vec2(SpreadsheetDynamicRegistry.requireVec2(value)).mul(-1f))
            3 -> SpreadsheetScalar(Vec3(SpreadsheetDynamicRegistry.requireVec3(value)).mul(-1f))
            4 -> SpreadsheetScalar(Vec4(SpreadsheetDynamicRegistry.requireVec4(value)).mul(-1f))
            else -> SpreadsheetScalar(-SpreadsheetDynamicRegistry.requireScalar(value))
        }
    }

operator fun unaryPlus(): SpreadsheetScalar = this

infix fun eq(other: Any?): SpreadsheetAssertion

Assert this == other. In a cell: =A3 eq 10. Over a variable cell, the sheet solves for it.

    infix fun eq(other: Any?): SpreadsheetAssertion = SpreadsheetEvalHelpers.assertThat(SpreadsheetRelation.EQ, raw, otherValue(other))

infix fun le(other: Any?): SpreadsheetAssertion

Assert this <= other; see eq.

    infix fun le(other: Any?): SpreadsheetAssertion = SpreadsheetEvalHelpers.assertThat(SpreadsheetRelation.LE, raw, otherValue(other))

infix fun ge(other: Any?): SpreadsheetAssertion

Assert this >= other; see eq.

    infix fun ge(other: Any?): SpreadsheetAssertion = SpreadsheetEvalHelpers.assertThat(SpreadsheetRelation.GE, raw, otherValue(other))

fun toDouble(): Double = d

fun toInt(): Int = d.toInt()

fun toLong(): Long = d.toLong()

override fun compareTo(other: Any?): Int

    override fun compareTo(other: Any?): Int {
        val left = unwrapSpreadsheetValue(raw)
        val right = otherValue(other)
        val leftNumeric = SpreadsheetDynamicRegistry.scalar(left)
        val rightNumeric = SpreadsheetDynamicRegistry.scalar(right)
        return if (leftNumeric != null && rightNumeric != null) {
            leftNumeric.compareTo(rightNumeric)
        } else {
            formatSpreadsheetValue(left).compareTo(formatSpreadsheetValue(right))
        }
    }

override fun equals(other: Any?): Boolean = unwrapSpreadsheetValue(raw) == otherValue(other)

override fun hashCode(): Int = unwrapSpreadsheetValue(raw)?.hashCode() ?: 0

override fun toString(): String = formatSpreadsheetValue(raw)

operator fun Number.plus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Number.plus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).plus(other)

operator fun Number.minus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Number.minus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).minus(other)

operator fun Number.times(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Number.times(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).times(other)

operator fun Number.div(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Number.div(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).div(other)

operator fun Number.rem(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Number.rem(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).rem(other)

operator fun Vec2.plus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec2.plus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).plus(other)

operator fun Vec2.minus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec2.minus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).minus(other)

operator fun Vec2.times(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec2.times(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).times(other)

operator fun Vec2.div(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec2.div(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).div(other)

operator fun Vec2.rem(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec2.rem(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).rem(other)

operator fun Vec3.plus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec3.plus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).plus(other)

operator fun Vec3.minus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec3.minus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).minus(other)

operator fun Vec3.times(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec3.times(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).times(other)

operator fun Vec3.div(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec3.div(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).div(other)

operator fun Vec3.rem(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec3.rem(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).rem(other)

operator fun Vec4.plus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec4.plus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).plus(other)

operator fun Vec4.minus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec4.minus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).minus(other)

operator fun Vec4.times(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec4.times(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).times(other)

operator fun Vec4.div(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec4.div(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).div(other)

operator fun Vec4.rem(other: SpreadsheetScalar): SpreadsheetScalar

operator fun Vec4.rem(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).rem(other)

operator fun FLine.plus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun FLine.plus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).plus(other)

operator fun FLine.minus(other: SpreadsheetScalar): SpreadsheetScalar

operator fun FLine.minus(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).minus(other)

operator fun FLine.times(other: SpreadsheetScalar): SpreadsheetScalar

operator fun FLine.times(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).times(other)

operator fun FLine.div(other: SpreadsheetScalar): SpreadsheetScalar

operator fun FLine.div(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).div(other)

operator fun FLine.rem(other: SpreadsheetScalar): SpreadsheetScalar

operator fun FLine.rem(other: SpreadsheetScalar): SpreadsheetScalar = SpreadsheetScalar(this).rem(other)

fun spreadsheetValue(value: Any?): SpreadsheetScalar = SpreadsheetScalar(value)

fun sheetValue(value: Any?): SpreadsheetScalar = spreadsheetValue(value)

class SpreadsheetBoxBinding internal constructor

class SpreadsheetBoxBinding internal constructor(
    private val touchMember: (String, SpreadsheetBoxMemberAccess) -> Unit,
    private val memberNamesProvider: () -> List<String>,
    private val target: Box
) {

val id: String

    val id: String
        get() = target.id

val name: String

    val name: String
        get() = target.name.ifBlank { target.id }

operator fun get(member: String): SpreadsheetScalar

    operator fun get(member: String): SpreadsheetScalar {
        touchMember(member, SpreadsheetBoxMemberAccess.GET)
        target.ki.context.vars[member]?.value?.let { return SpreadsheetScalar(unwrapSpreadsheetValue(it)) }
        return SpreadsheetScalar(unwrapSpreadsheetValue(target.getPropertyRaw(member, null)))
    }

fun prop(member: String): SpreadsheetScalar

    fun prop(member: String): SpreadsheetScalar {
        touchMember(member, SpreadsheetBoxMemberAccess.PROP)
        return SpreadsheetScalar(unwrapSpreadsheetValue(target.getPropertyRaw(member, null)))
    }

fun variable(member: String): SpreadsheetScalar

    fun variable(member: String): SpreadsheetScalar {
        touchMember(member, SpreadsheetBoxMemberAccess.VARIABLE)
        return SpreadsheetScalar(unwrapSpreadsheetValue(target.ki.context.vars[member]?.value))
    }

fun keys(): List<String> = memberNamesProvider()

override fun toString(): String = "box(${name.ifBlank { id }})"

internal class SpreadsheetRuntime

internal class SpreadsheetRuntime(
    private val info: SessionInformation,
    private val currentBox: Box,
    /** The cell and range references the formula's compiled template reads, by position. */
    internal val refs: List<String> = emptyList(),
    /** The cell being evaluated, `row:col`, and its input; with [constraints], what `free(...)` needs. */
    private val cellKey: String? = null,
    private val cellInput: String? = null,
    private val constraints: SheetConstraintCollector? = null,
    private val evaluateRef: (CellCoord) -> Any?
) {
    private val boxDependencies = linkedSetOf<SpreadsheetBoxDependency>()

fun declareFree(initial: Double): Any

    fun declareFree(initial: Double): Any {
        val collector = constraints ?: return initial
        return collector.declare(cellKey ?: error("free(...) needs a cell"), cellInput.orEmpty(), initial)
    }

fun registerAssertion(assertion: SpreadsheetAssertion)

    fun registerAssertion(assertion: SpreadsheetAssertion) {
        constraints?.assert(cellKey, assertion)
    }

fun cell(ref: String): SpreadsheetScalar

    fun cell(ref: String): SpreadsheetScalar {
        val coord = cellRefToCoord(ref) ?: error("Unknown cell reference '$ref'")
        return SpreadsheetScalar(unwrapSpreadsheetValue(evaluateRef(coord)))
    }

fun range(ref: String): List<SpreadsheetScalar>

    fun range(ref: String): List<SpreadsheetScalar> {
        val parts = ref.split(':', limit = 2)
        if (parts.size != 2) error("Malformed range '$ref'")
        val start = cellRefToCoord(parts[0]) ?: error("Unknown cell reference '${parts[0]}'")
        val end = cellRefToCoord(parts[1]) ?: error("Unknown cell reference '${parts[1]}'")
        val rowStart = minOf(start.row, end.row)
        val rowEnd = maxOf(start.row, end.row)
        val colStart = minOf(start.col, end.col)
        val colEnd = maxOf(start.col, end.col)
        val values = mutableListOf<SpreadsheetScalar>()
        for (row in rowStart..rowEnd) {
            for (col in colStart..colEnd) {
                val value = unwrapSpreadsheetValue(evaluateRef(CellCoord(row, col)))
                if (!(value == null || value == "")) {
                    values += SpreadsheetScalar(value)
                }
            }
        }
        return values
    }

fun box(ref: String): SpreadsheetBoxBinding

    fun box(ref: String): SpreadsheetBoxBinding {
        val box = resolveBoxReference(ref)
            ?: error("Unknown box reference '$ref'")
        return bindSpreadsheetBox(this, box)
    }

fun self(): SpreadsheetBoxBinding = bindSpreadsheetBox(this, currentBox)

fun touchBoxDependency(box: Box, member: String, access: SpreadsheetBoxMemberAccess)

    fun touchBoxDependency(box: Box, member: String, access: SpreadsheetBoxMemberAccess) {
        boxDependencies += SpreadsheetBoxDependency(boxId = box.id, member = member, access = access)
    }

fun memberNamesFor(box: Box): List<String>

    fun memberNamesFor(box: Box): List<String> =
        buildSet {
            addAll(box.properties.keys)
            addAll(box.ki.context.vars.keys)
        }.sorted()

fun boxDependencies(): Set<SpreadsheetBoxDependency> = boxDependencies.toSet()

source — 947 lines, from line 1317
    private fun resolveBoxReference(ref: String): Box? {
        val trimmed = ref.trim()
        if (trimmed.isEmpty()) return null
        if (trimmed == "self") return currentBox
        info.boxes[trimmed]?.let { return it }
        if (trimmed == "root") return info.root
        val matches = info.boxes.values.filter { it.name.trim() == trimmed }
        return if (matches.size == 1) matches.first() else null
    }
}

private fun bindSpreadsheetBox(runtime: SpreadsheetRuntime, target: Box): SpreadsheetBoxBinding =
    SpreadsheetBoxBinding(
        touchMember = { member, access -> runtime.touchBoxDependency(target, member, access) },
        memberNamesProvider = { runtime.memberNamesFor(target) },
        target = target
    )

private class SpreadsheetEvaluator(private val cachePrefix: String) {
    private val lock = Any()
    // One compiled formula *function* per template. A snippet is evaluated once, to make the function;
    // each recompute then calls it. Until 2026-10-26 each recompute re-evaluated the snippet, and the
    // K2 REPL handed back the previous result when the same snippet was evaluated twice in a row — so a
    // sheet with one formula never followed the box it read, and a template shared by a filled-down
    // column would have shown its first row everywhere (SpreadsheetTest pins both).
    private val compiled = ConcurrentHashMap<String, () -> Any?>()
    // K2, like box code. Formulas moved off the legacy K1 engine on 2026-09-28, with no change a sheet
    // can see (examples.spreadsheet.formulas pins that); K1's REPL compiler is an error-level
    // deprecation from Kotlin 2.5.
    private val compiler = KotlinInterface2(filePrefix = cachePrefix)
    private var bootstrapped = false

    // The K2 REPL defines its `ReplState` class with the first snippet it evaluates. Until 2026-10-26
    // that was the sheet's first formula, so a sheet whose first formula did not compile never got
    // one, and every later formula on it failed with `NoClassDefFoundError: ReplState` until Field
    // restarted. Box code bootstraps its engine with the intro imports; the sheet now does the same.
    private fun bootstrapLocked() {
        if (bootstrapped) return
        bootstrapped = true
        runBlocking {
            compiler.bootstrap(
                "${introImports.joinToString("\n")}\nUnit",
                implicits = emptyList(),
                onCompileError = { lprintln("warning,spreadsheet", "formula engine bootstrap did not compile: ${it.firstOrNull()?.message}") },
                onEvalError = {},
                onEvalException = { lprintln("warning,spreadsheet", "formula engine bootstrap failed: ${it.message}") },
            )
        }
    }

    fun evaluate(expression: String, runtime: SpreadsheetRuntime): EvalResult {
        val source = buildSource(expression)
        var failure: String? = null
        val formula = synchronized(lock) {
            bootstrapLocked()
            compiled[source] ?: compileLocked(source) { failure = it }?.also { compiled[source] = it }
        } ?: return EvalResult(value = null, errorDetail = failure ?: "Formula compilation failed")

        return withSpreadsheetRuntime(runtime) {
            try {
                when (val value = formula()) {
                    is SpreadsheetEvalHelpers.Failure -> EvalResult(null, value.error.message ?: value.error.toString())
                    // Stored unwrapped, so `getSpreadsheetCellValue("C3") as? FLine` finds the line
                    // a formula like `C1 + B1` made, not the `SpreadsheetScalar` wrapped around it.
                    else -> EvalResult(unwrapSpreadsheetValue(value))
                }
            } catch (t: Throwable) {
                EvalResult(null, t.message ?: t.toString())
            }
        }
    }

    @Suppress("UNCHECKED_CAST")
    private fun compileLocked(source: String, onFailure: (String) -> Unit): (() -> Any?)? {
        var compileError: String? = null
        val snippet = runBlocking {
            compiler.compileSnippet(source, implicits = emptyList(), onCompileError = { diagnostics ->
                compileError = diagnostics.firstOrNull {
                    it.severity.ordinal >= ScriptDiagnostic.Severity.ERROR.ordinal
                }?.message
            })
        }
        if (snippet == null) {
            onFailure(compileError?.let { "Formula compilation failed: $it" } ?: "Formula compilation failed")
            return null
        }
        return when (val result = snippet.evalAndWait(updateContext = false, implicits = emptyList(), onEvalError = {}, onEvalException = {})) {
            is ResultValue.Value -> (result.value as? () -> Any?) ?: run { onFailure("Formula did not compile to a function"); null }
            is ResultValue.Error -> { onFailure(result.error.message ?: result.error.toString()); null }
            else -> { onFailure("Formula was not evaluated"); null }
        }
    }

    private fun buildSource(expression: String): String = """
        ${introImports.joinToString("\n")}
        import kotlin.math.*
        import eu.sirotin.kotunil.core.*
        import eu.sirotin.kotunil.base.*
        import eu.sirotin.kotunil.derived.*
        import eu.sirotin.kotunil.specialunits.*
        run {
            fun cell(ref: String) = afield.data.table.SpreadsheetEvalHelpers.cell(ref)
            fun range(ref: String) = afield.data.table.SpreadsheetEvalHelpers.range(ref)
            fun box(ref: String) = afield.data.table.SpreadsheetEvalHelpers.box(ref)
            fun __ref(index: Int) = afield.data.table.SpreadsheetEvalHelpers.ref(index)
            fun sum(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.total(values.toList())
            fun product(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.product(values.toList())
            fun avg(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.avg(values.toList())
            fun mean(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.mean(values.toList())
            fun min(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.min(values.toList())
            fun max(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.max(values.toList())
            fun count(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.count(values.toList())
            fun nonEmpty(vararg values: Any?) = afield.data.table.SpreadsheetEvalHelpers.nonEmpty(values.toList())
            fun num(value: Any?) = afield.data.table.SpreadsheetEvalHelpers.num(value)
            fun free(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.free(x)
            fun sqrt(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.sqrt(x)
            fun abs(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.abs(x)
            fun round(x: Any?) = kotlin.math.round(num(x))
            fun floor(x: Any?) = kotlin.math.floor(num(x))
            fun ceil(x: Any?) = kotlin.math.ceil(num(x))
            fun exp(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.exp(x)
            fun ln(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.ln(x)
            fun log10(x: Any?) = kotlin.math.log10(num(x))
            fun sin(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.sin(x)
            fun cos(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.cos(x)
            fun tan(x: Any?) = afield.data.table.SpreadsheetEvalHelpers.tan(x)
            fun atan2(y: Any?, x: Any?) = afield.data.table.SpreadsheetEvalHelpers.atan2(y, x)
            fun pow(x: Any?, p: Any?) = afield.data.table.SpreadsheetEvalHelpers.pow(x, p)
            infix fun Number.eq(other: Any?) = afield.data.table.spreadsheetValue(this).eq(other)
            infix fun Number.le(other: Any?) = afield.data.table.spreadsheetValue(this).le(other)
            infix fun Number.ge(other: Any?) = afield.data.table.spreadsheetValue(this).ge(other)
            val formula: () -> Any? = {
                val self = afield.data.table.SpreadsheetEvalHelpers.self
                try {
                    $expression
                } catch (t: Throwable) {
                    afield.data.table.SpreadsheetEvalHelpers.failed(t)
                }
            }
            formula
        }
    """.trimIndent()
}

private class SpreadsheetDocument(
    private val key: SpreadsheetKey
) {
    private val evaluator = SpreadsheetEvaluator(
        cachePrefix = "sheet_${safeForId(key.uid)}_${safeForId(key.property)}"
    )
    private val lock = Any()
    private var storedState: SpreadsheetStoredState = normalizeStoredState(null)
    private var persistedJson: String? = null
    private var version: Long = 0
    private var cachedSnapshot: SpreadsheetSnapshot? = null
    private var cachedEvalCells: Map<String, EvalCell> = emptyMap()
    private var lastExternalBoxDeps: Set<SpreadsheetBoxDependency> = emptySet()
    private var lastObservedExternalValues: Map<SpreadsheetBoxDependency, String> = emptyMap()
    val subscribers: MutableSet<SpreadsheetClient> = CopyOnWriteArraySet()

    fun hasSubscribers(): Boolean = subscribers.isNotEmpty()

    fun removeClient(clientKey: Any) {
        val matches = subscribers.filter { it.key === clientKey }.toSet()
        if (matches.isNotEmpty()) {
            subscribers.removeAll(matches)
        }
    }

    fun snapshot(box: Box, info: SessionInformation, forceReload: Boolean = false, forceRecompute: Boolean = false): SpreadsheetSnapshot {
        synchronized(lock) {
            reloadFromBox(box, forceReload)
            if (cachedSnapshot == null || forceRecompute) {
                if (forceRecompute) version += 1
                cachedSnapshot = computeSnapshot(box, info)
            }
            return cachedSnapshot!!
        }
    }

    fun cellValue(
        box: Box,
        info: SessionInformation,
        ref: String,
        forceReload: Boolean = false,
        forceRecompute: Boolean = true
    ): Any? {
        synchronized(lock) {
            reloadFromBox(box, forceReload)
            val coord = cellRefToCoord(ref.trim()) ?: error("Unknown spreadsheet cell reference '$ref'")
            if (coord.row !in 0 until storedState.rows || coord.col !in 0 until storedState.cols) {
                error("Cell ${cellLabel(coord.row, coord.col)} is outside the sheet")
            }
            if (cachedSnapshot == null || forceRecompute) {
                if (forceRecompute) version += 1
                cachedSnapshot = computeSnapshot(box, info)
            }
            // A variable cell, or a formula over one, reads as its number from outside the sheet.
            return when (val value = cachedEvalCells["${coord.row}:${coord.col}"]?.value) {
                null -> ""
                is SpreadsheetTraced -> value.value
                else -> value
            }
        }
    }

    fun update(box: Box, info: SessionInformation, nextState: SpreadsheetStoredState): SpreadsheetSnapshot {
        synchronized(lock) {
            // The editor sends cells and sizes only; keep what the last solve recorded.
            val carried = if (nextState.solved.isEmpty()) nextState.copy(solved = storedState.solved) else nextState
            storedState = writeStoredSpreadsheetState(box, key.property, carried)
            persistedJson = encodeStoredSpreadsheetState(storedState)
            version += 1
            cachedSnapshot = computeSnapshot(box, info)
            return cachedSnapshot!!
        }
    }

    fun pollExternalDependencies(box: Box, info: SessionInformation): SpreadsheetSnapshot? {
        synchronized(lock) {
            if (lastExternalBoxDeps.isEmpty()) {
                lastObservedExternalValues = emptyMap()
                return null
            }
            val observedNow = captureExternalDependencyFingerprints(info)
            if (observedNow == lastObservedExternalValues) return null
            version += 1
            cachedSnapshot = computeSnapshot(box, info)
            return cachedSnapshot!!
        }
    }

    private fun reloadFromBox(box: Box, forceReload: Boolean) {
        val raw = box.getPropertyRaw(key.property, null) as? String
        if (!forceReload && raw == persistedJson) return
        persistedJson = raw
        storedState = decodeStoredState(raw)
        cachedSnapshot = null
        cachedEvalCells = emptyMap()
        version += 1
    }

    private fun computeSnapshot(box: Box, info: SessionInformation, allowSolve: Boolean = true, solved: SheetSolveOutcome? = null): SpreadsheetSnapshot {
        val cellInputs = mutableMapOf<String, String>()
        storedState.cells.forEach { cell ->
            if (cell.row in 0 until storedState.rows && cell.col in 0 until storedState.cols) {
                cellInputs["${cell.row}:${cell.col}"] = cell.input
            }
        }
        // Typing a plain number over a variable cell edits the variable rather than replacing it.
        if (allowSolve) {
            val retyped = cellInputs.filter { (k, input) ->
                storedState.solved[k] != null && input.trim().toDoubleOrNull() != null
            }
            if (retyped.isNotEmpty()) {
                retyped.forEach { (k, input) -> cellInputs[k] = freeCellInput(input.trim().toDouble()) }
                storedState = writeStoredSpreadsheetState(box, key.property, storedState.copy(cells = storedState.cells.map { c ->
                    cellInputs["${c.row}:${c.col}"]?.let { c.copy(input = it) } ?: c
                }))
                persistedJson = encodeStoredSpreadsheetState(storedState)
            }
        }
        val constraints = SheetConstraintCollector()

        val memo = mutableMapOf<String, EvalCell>()
        // The chain of formulas being evaluated, innermost last, so a cycle can name every cell in it.
        val visiting = mutableListOf<String>()
        val cyclic = mutableSetOf<String>()
        val externalDeps = linkedSetOf<SpreadsheetBoxDependency>()

        fun labelOf(keyString: String): String =
            keyString.split(':').let { cellLabel(it[0].toInt(), it[1].toInt()) }

        fun evaluateCell(row: Int, col: Int): EvalCell {
            val keyString = "$row:$col"
            memo[keyString]?.let { return it }
            if (keyString in visiting) {
                // Until 2026-10-26 this returned an empty value and the cell that closed the loop then
                // overwrote its own #CYCLE, so a cycle showed as blank cells. Now every cell on the
                // loop is marked and the reference throws, so readers outside it see an error.
                val loop = visiting.subList(visiting.indexOf(keyString), visiting.size)
                cyclic += loop
                throw IllegalStateException("Circular reference: ${(loop + keyString).joinToString(" -> ") { labelOf(it) }}")
            }

            val input = cellInputs[keyString].orEmpty()
            val type = classifyCellInput(input)
            if (type == "empty") {
                return EvalCell(input, type, "", "", null).also { memo[keyString] = it }
            }
            if (type == "text") {
                return EvalCell(input, type, input, input, null).also { memo[keyString] = it }
            }
            if (type == "number") {
                val number = input.trim().toDoubleOrNull()
                val display = formatSpreadsheetValue(number)
                return EvalCell(input, type, number, display, null).also { memo[keyString] = it }
            }

            visiting += keyString
            return try {
                val expr = input.removePrefix("=").trim()
                if (expr.isEmpty()) {
                    EvalCell(input, "formula", "", "", null)
                } else {
                    val refs = mutableListOf<String>()
                    val rewritten = rewriteSpreadsheetFormula(expr, refs)
                    val runtime = SpreadsheetRuntime(info, box, refs, keyString, input, constraints) { coord ->
                        if (coord.row !in 0 until storedState.rows || coord.col !in 0 until storedState.cols) {
                            error("Cell ${cellLabel(coord.row, coord.col)} is outside the sheet")
                        }
                        val referenced = evaluateCell(coord.row, coord.col)
                        // A formula that reads a failed cell fails too, as in Excel, instead of reading
                        // the failure as empty (which made `A1 + 1` show 1 when A1 was #ERR).
                        if (referenced.errorDetail != null) {
                            error("${cellLabel(coord.row, coord.col)} is ${referenced.display}: ${referenced.errorDetail}")
                        }
                        referenced.value
                    }
                    val evalResult = evaluator.evaluate(rewritten, runtime)
                    externalDeps += runtime.boxDependencies()
                    if (keyString in cyclic) {
                        EvalCell(input, "formula", null, "#CYCLE", evalResult.errorDetail ?: "Circular reference at ${cellLabel(row, col)}")
                    } else if (evalResult.errorDetail != null) {
                        EvalCell(input, "formula", null, "#ERR", evalResult.errorDetail)
                    } else {
                        EvalCell(
                            input = input,
                            type = when {
                                constraints.isVariable(keyString) -> "variable"
                                evalResult.value is SpreadsheetAssertion -> "assertion"
                                else -> "formula"
                            },
                            value = evalResult.value,
                            display = formatSpreadsheetValue(evalResult.value),
                            errorDetail = null,
                            htmlDisplay = spreadsheetHtmlDisplay(evalResult.value)
                        )
                    }
                }
            } catch (t: Throwable) {
                EvalCell(input, "formula", null, if (keyString in cyclic) "#CYCLE" else "#ERR", t.message ?: t.toString())
            }.also {
                memo[keyString] = it
                visiting.removeAt(visiting.lastIndex)
            }
        }

        for (row in 0 until storedState.rows) {
            for (col in 0 until storedState.cols) {
                val maybeInput = cellInputs["$row:$col"]
                if (!maybeInput.isNullOrBlank()) {
                    evaluateCell(row, col)
                }
            }
        }

        if (allowSolve) {
            // Solved values are written into the variable cells; evaluate again so every cell downstream,
            // and every assertion's verdict, reads them.
            solveConstraints(box, constraints)?.let { return computeSnapshot(box, info, allowSolve = false, solved = it) }
        }

        lastExternalBoxDeps = externalDeps.toSet()
        lastObservedExternalValues = captureExternalDependencyFingerprints(info)
        cachedEvalCells = memo.toMap()

        val cells = memo.entries
            .mapNotNull { (keyString, cell) ->
                val parts = keyString.split(':', limit = 2)
                val row = parts.getOrNull(0)?.toIntOrNull() ?: return@mapNotNull null
                val col = parts.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
                if (cell.type == "empty" && cell.display.isEmpty()) return@mapNotNull null
                SpreadsheetCellSnapshot(
                    row = row,
                    col = col,
                    input = cell.input,
                    type = cell.type,
                    display = cell.display,
                    errorDetail = cell.errorDetail,
                    htmlDisplay = cell.htmlDisplay
                )
            }
            .sortedWith(compareBy<SpreadsheetCellSnapshot> { it.row }.thenBy { it.col })

        return SpreadsheetSnapshot(
            uid = key.uid,
            property = key.property,
            rows = storedState.rows,
            cols = storedState.cols,
            colWidths = storedState.colWidths.map { it.toInt() },
            rowHeights = storedState.rowHeights.map { it.toInt() },
            cells = cells,
            version = version,
            solve = solved?.let { outcome ->
                if (!outcome.ok) "solve failed: ${outcome.note ?: "unknown"}"
                else outcome.summary(
                    constraints.variables.size,
                    constraints.assertions.size,
                    constraints.assertions.count { !it.second.satisfied },
                )
            }
        )
    }

    /**
     * Solve the sheet's assertions for its variable cells and write the answers back into their
     * `=free(...)` formulas. Null when there is nothing to solve (no variable is read by an assertion);
     * otherwise the outcome, which the re-evaluation turns into the sheet header's one-line summary.
     */
    private fun solveConstraints(box: Box, constraints: SheetConstraintCollector): SheetSolveOutcome? {
        val variables = constraints.variables
        val assertions = constraints.assertions.map { it.second }
        val inputs = storedState.cells.associate { "${it.row}:${it.col}" to it.input }
        val currentVariableInputs = variables.associate { it.cellKey to inputs[it.cellKey].orEmpty() }
        if (variables.isEmpty() || assertions.none { it.traced }) {
            recordSolved(box, currentVariableInputs, emptyMap())
            return null
        }
        val previouslySolved = storedState.solved
        val edited = variables.indices.filter { i ->
            val k = variables[i].cellKey
            previouslySolved.isNotEmpty() && previouslySolved[k] != currentVariableInputs[k]
        }.toSet()
        val outcome = SheetConstraintSolver.solve(variables.map { it.previous }.toDoubleArray(), edited, assertions)
        if (!outcome.ok) {
            recordSolved(box, currentVariableInputs, emptyMap())
            return outcome
        }
        val rewritten = mutableMapOf<String, String>()
        variables.forEachIndexed { i, v ->
            val next = freeCellInput(outcome.values[i])
            val current = currentVariableInputs[v.cellKey].orEmpty()
            val currentValue = FREE_CELL_PATTERN.find(current)?.groupValues?.get(1)?.toDoubleOrNull()
            if (currentValue == null || freeLiteral(currentValue) != freeLiteral(outcome.values[i])) rewritten[v.cellKey] = next
        }
        recordSolved(box, currentVariableInputs, rewritten)
        return outcome
    }

    /** Write solved values into their cells and record the variables' inputs as solved; no write if nothing changed. */
    private fun recordSolved(box: Box, variableInputs: Map<String, String>, rewritten: Map<String, String>) {
        val solved = variableInputs + rewritten
        if (rewritten.isEmpty() && solved == storedState.solved) return
        storedState = writeStoredSpreadsheetState(box, key.property, storedState.copy(
            cells = storedState.cells.map { c -> rewritten["${c.row}:${c.col}"]?.let { c.copy(input = it) } ?: c },
            solved = solved,
        ))
        persistedJson = encodeStoredSpreadsheetState(storedState)
    }

    private fun captureExternalDependencyFingerprints(info: SessionInformation): Map<SpreadsheetBoxDependency, String> =
        lastExternalBoxDeps.associateWith { dependency ->
            val target = when (dependency.boxId) {
                "root" -> info.root
                else -> info.boxes[dependency.boxId]
            }
            externalDependencyFingerprint(target, dependency)
        }

    private fun externalDependencyFingerprint(target: Box?, dependency: SpreadsheetBoxDependency): String {
        if (target == null) {
            return "missing:${dependency.boxId}:${dependency.member}:${dependency.access}"
        }
        if (target.getPropertyRaw("_tombstone").isTrue()) {
            return "tombstone:${dependency.boxId}:${dependency.member}:${dependency.access}"
        }
        val value = when (dependency.access) {
            SpreadsheetBoxMemberAccess.GET -> target.ki.context.vars[dependency.member]?.value
                ?: target.getPropertyRaw(dependency.member, null)

            SpreadsheetBoxMemberAccess.PROP -> target.getPropertyRaw(dependency.member, null)
            SpreadsheetBoxMemberAccess.VARIABLE -> target.ki.context.vars[dependency.member]?.value
        }
        return fingerprintSpreadsheetDependencyValue(value)
    }
}

private object SpreadsheetService {
    private val documents = ConcurrentHashMap<SpreadsheetKey, SpreadsheetDocument>()

    fun detachClient(clientKey: Any) {
        documents.values.forEach { it.removeClient(clientKey) }
    }

    fun currentState(box: Box, property: String): SpreadsheetStoredState =
        decodeStoredState(box.getPropertyRaw(normalizedSpreadsheetProperty(property), null) as? String)

    fun snapshot(
        box: Box,
        info: SessionInformation,
        property: String,
        forceReload: Boolean = false,
        forceRecompute: Boolean = true
    ): SpreadsheetSnapshot {
        val normalizedProperty = normalizedSpreadsheetProperty(property)
        registerSpreadsheetProperty(normalizedProperty)
        val key = SpreadsheetKey(info.filename, box.id, normalizedProperty)
        val document = documents.computeIfAbsent(key) { SpreadsheetDocument(key) }
        return document.snapshot(box, info, forceReload = forceReload, forceRecompute = forceRecompute)
    }

    fun cellValue(
        box: Box,
        info: SessionInformation,
        property: String,
        ref: String,
        forceReload: Boolean = false,
        forceRecompute: Boolean = true
    ): Any? {
        val normalizedProperty = normalizedSpreadsheetProperty(property)
        registerSpreadsheetProperty(normalizedProperty)
        val key = SpreadsheetKey(info.filename, box.id, normalizedProperty)
        val document = documents.computeIfAbsent(key) { SpreadsheetDocument(key) }
        return document.cellValue(box, info, ref, forceReload = forceReload, forceRecompute = forceRecompute)
    }

    fun applyProgrammaticState(
        box: Box,
        info: SessionInformation,
        property: String,
        nextState: SpreadsheetStoredState,
        broadcast: Boolean = true
    ): SpreadsheetStoredState {
        val normalizedProperty = normalizedSpreadsheetProperty(property)
        registerSpreadsheetProperty(normalizedProperty)
        val normalizedState = normalizeStoredState(nextState)
        val key = SpreadsheetKey(info.filename, box.id, normalizedProperty)
        val document = documents[key]
        if (document == null) {
            return writeStoredSpreadsheetState(box, normalizedProperty, normalizedState)
        }
        val snapshot = document.update(box, info, normalizedState)
        if (broadcast) {
            broadcast(document, snapshot, key)
        }
        return normalizedState
    }

    fun handle(text: String, info: SessionInformation?, clientKey: Any, push: (String) -> Unit): String {
        val req = try {
            SpreadsheetProtocol.json.decodeFromString(SpreadsheetProtocol.Request.serializer(), text)
        } catch (t: Throwable) {
            lprintln("warning,spreadsheet,ws", "[sheet-handle] failed to decode request: ${t.message}")
            return ""
        }
        if (req.channel != null && req.channel != SPREADSHEET_CHANNEL) return ""

        lprintln("debug,spreadsheet,ws", "[sheet-handle] op=${req.op} reqId=${req.reqId} uid=${req.uid} property=${req.property}")

        val sessionInfo = info
            ?: return error(req.reqId, "Spreadsheet session is not attached yet. Wait for hello to complete and retry.")
        val uid = req.uid?.trim().orEmpty()
        if (uid.isEmpty()) {
            return error(req.reqId, "Missing spreadsheet box uid")
        }

        val normalizedProperty = normalizedSpreadsheetProperty(req.property)
        registerSpreadsheetProperty(normalizedProperty)

        val box = runCatching { box(uid, createIfMissing = false, root = sessionInfo) }
            .getOrElse { return error(req.reqId, "Unknown spreadsheet box '$uid'") }
        val key = SpreadsheetKey(sessionInfo.filename, uid, normalizedProperty)
        val document = documents.computeIfAbsent(key) { SpreadsheetDocument(key) }
        val client = SpreadsheetClient(clientKey, send = push)

        return try {
            val startMs = System.currentTimeMillis()
            val result = when (req.op) {
                "open" -> {
                    if (req.watch) document.subscribers += client
                    val snapshot = document.snapshot(box, sessionInfo, forceRecompute = true)
                    SpreadsheetProtocol.json.encodeToString(
                        SpreadsheetProtocol.SnapshotEnvelope.serializer(),
                        SpreadsheetProtocol.SnapshotEnvelope(reqId = req.reqId, snapshot = snapshot)
                    )
                }

                "update" -> {
                    if (req.watch) document.subscribers += client
                    val nextState = req.state ?: normalizeStoredState(null)
                    val snapshot = document.update(box, sessionInfo, nextState)
                    broadcast(document, snapshot, key)
                    SpreadsheetProtocol.json.encodeToString(
                        SpreadsheetProtocol.SnapshotEnvelope.serializer(),
                        SpreadsheetProtocol.SnapshotEnvelope(reqId = req.reqId, snapshot = snapshot)
                    )
                }

                "refresh" -> {
                    if (req.watch) document.subscribers += client
                    val snapshot = document.snapshot(box, sessionInfo, forceReload = true, forceRecompute = true)
                    broadcast(document, snapshot, key)
                    SpreadsheetProtocol.json.encodeToString(
                        SpreadsheetProtocol.SnapshotEnvelope.serializer(),
                        SpreadsheetProtocol.SnapshotEnvelope(reqId = req.reqId, snapshot = snapshot)
                    )
                }

                "complete" -> {
                    val formula = req.text ?: ""
                    val cursor = (req.cursor ?: formula.length).coerceIn(0, formula.length)
                    val items = completionsFor(box, sessionInfo, formula, cursor)
                    SpreadsheetProtocol.json.encodeToString(
                        SpreadsheetProtocol.CompletionEnvelope.serializer(),
                        SpreadsheetProtocol.CompletionEnvelope(reqId = req.reqId, items = items)
                    )
                }

                "unsubscribe" -> {
                    document.subscribers -= client
                    SpreadsheetProtocol.json.encodeToString(
                        SpreadsheetProtocol.CompletionEnvelope.serializer(),
                        SpreadsheetProtocol.CompletionEnvelope(reqId = req.reqId, items = emptyList())
                    )
                }

                else -> error(req.reqId, "Unknown spreadsheet op '${req.op}'")
            }
            val elapsed = System.currentTimeMillis() - startMs
            lprintln("debug,spreadsheet,ws", "[sheet-handle] op=${req.op} reqId=${req.reqId} completed in ${elapsed}ms, response=${result.length} chars")
            result
        } catch (t: Throwable) {
            lprintln("error,spreadsheet,ws", "[sheet-handle] op=${req.op} reqId=${req.reqId} threw ${t::class.simpleName}: ${t.message}")
            error(req.reqId, t.message ?: t.toString())
        }
    }

    fun pollLiveSheets() {
        val broadcasts = mutableListOf<Triple<SpreadsheetKey, SpreadsheetDocument, SpreadsheetSnapshot>>()
        val removals = mutableListOf<SpreadsheetKey>()

        documents.forEach { (key, document) ->
            if (!document.hasSubscribers()) return@forEach

            val info = afield.app.server.documents[key.documentFilename]
            if (info == null) {
                removals += key
                return@forEach
            }

            val box = when (key.uid) {
                "root" -> info.root
                else -> info.boxes[key.uid]
            }
            if (box == null || box.getPropertyRaw("_tombstone").isTrue()) {
                removals += key
                return@forEach
            }

            val snapshot = runCatching { document.pollExternalDependencies(box, info) }
                .onFailure { lprintln("warning,spreadsheet", "live spreadsheet polling failed for ${key.uid}/${key.property}: ${it.message}") }
                .getOrNull()
                ?: return@forEach

            broadcasts += Triple(key, document, snapshot)
        }

        broadcasts.forEach { (key, document, snapshot) ->
            broadcast(document, snapshot, key)
        }
        removals.forEach { key ->
            documents.remove(key)
            lprintln("info,spreadsheet", "stopped live polling for ${key.uid}/${key.property}")
        }
    }

    private fun broadcast(document: SpreadsheetDocument, snapshot: SpreadsheetSnapshot, key: SpreadsheetKey) {
        val payload = SpreadsheetProtocol.json.encodeToString(
            SpreadsheetProtocol.SnapshotEnvelope.serializer(),
            SpreadsheetProtocol.SnapshotEnvelope(snapshot = snapshot)
        )
        val deadClients = mutableListOf<SpreadsheetClient>()
        document.subscribers.forEach { client ->
            runCatching { client.send(payload) }
                .onFailure { deadClients += client }
        }
        if (deadClients.isNotEmpty()) {
            document.subscribers.removeAll(deadClients.toSet())
        }
        lprintln(
            "info,spreadsheet",
            "broadcast sheet ${key.uid}/${key.property} v${snapshot.version} to ${document.subscribers.size} subscribers"
        )
    }

    private fun error(reqId: String?, message: String): String =
        SpreadsheetProtocol.json.encodeToString(
            SpreadsheetProtocol.ErrorEnvelope.serializer(),
            SpreadsheetProtocol.ErrorEnvelope(reqId = reqId, error = message)
        )

    private fun completionsFor(box: Box, info: SessionInformation, formula: String, cursor: Int): List<SpreadsheetCompletionItem> {
        val context = completionContextFor(formula, cursor)
        return when (context) {
            is CompletionContext.BoxTarget -> {
                val suggestions = availableBoxRefs(info)
                    .filter { matchesCompletionQuery(it.name, context.query) || matchesCompletionQuery(it.uid, context.query) }
                    .distinctBy { it.uid }
                    .sortedWith(compareBy<SpreadsheetRef> { if (it.name == it.uid) 1 else 0 }.thenBy { it.name.lowercase() })
                    .map { ref ->
                        SpreadsheetCompletionItem(
                            label = ref.name,
                            insertText = ref.name,
                            kind = "box",
                            detail = ref.uid,
                            documentation = "Box reference",
                            replaceStart = context.replaceStart,
                            replaceEnd = context.replaceEnd
                        )
                    }
                suggestions
            }

            is CompletionContext.BoxMember -> {
                val target = resolveBox(info, box, context.ref)
                val names = memberNamesFor(target)
                names.filter { matchesCompletionQuery(it, context.query) }
                    .sorted()
                    .map { name ->
                        SpreadsheetCompletionItem(
                            label = name,
                            insertText = name,
                            kind = "member",
                            detail = "Box value",
                            documentation = "Value from ${target?.name?.ifBlank { target.id } ?: context.ref}",
                            replaceStart = context.replaceStart,
                            replaceEnd = context.replaceEnd
                        )
                    }
            }

            is CompletionContext.SelfMember -> {
                memberNamesFor(box)
                    .filter { matchesCompletionQuery(it, context.query) }
                    .sorted()
                    .map { name ->
                        SpreadsheetCompletionItem(
                            label = name,
                            insertText = name,
                            kind = "member",
                            detail = "Current box value",
                            documentation = "Current box variable or property",
                            replaceStart = context.replaceStart,
                            replaceEnd = context.replaceEnd
                        )
                    }
            }

            is CompletionContext.General -> generalCompletions()
                .filter { matchesCompletionQuery("${it.label} ${it.detail}", context.query) }
                .map { item ->
                    SpreadsheetCompletionItem(
                        label = item.label,
                        insertText = item.insertText,
                        kind = item.kind,
                        detail = item.detail,
                        documentation = item.documentation,
                        replaceStart = context.replaceStart,
                        replaceEnd = context.replaceEnd,
                        cursorOffset = item.cursorOffset,
                        selectInsertedText = item.selectInsertedText
                    )
                }
        }.take(40)
    }

    private fun availableBoxRefs(info: SessionInformation): List<SpreadsheetRef> =
        info.boxes.values.map { box ->
            val displayName = box.name.trim().ifBlank { box.id }
            SpreadsheetRef(uid = box.id, name = displayName)
        }

    private fun memberNamesFor(box: Box?): List<String> {
        if (box == null) return emptyList()
        return buildSet {
            addAll(box.properties.keys)
            addAll(box.ki.context.vars.keys)
        }.sorted()
    }

    private fun resolveBox(info: SessionInformation, currentBox: Box, ref: String): Box? {
        val trimmed = ref.trim()
        if (trimmed.isEmpty()) return null
        if (trimmed == "self") return currentBox
        info.boxes[trimmed]?.let { return it }
        if (trimmed == "root") return info.root
        val matches = info.boxes.values.filter { it.name.trim() == trimmed }
        return if (matches.size == 1) matches.first() else null
    }

    private fun completionContextFor(formula: String, cursor: Int): CompletionContext {
        val beforeCursor = formula.take(cursor)

        val boxMemberPattern = Regex("""box\s*\(\s*(['"])([^'"]*)\1\s*\)\s*\[\s*(['"])([^'"]*)$""")
        boxMemberPattern.find(beforeCursor)?.let { match ->
            val query = match.groupValues[4]
            return CompletionContext.BoxMember(
                ref = match.groupValues[2],
                replaceStart = cursor - query.length,
                replaceEnd = cursor,
                query = query
            )
        }

        val selfMemberPattern = Regex("""self\s*\[\s*(['"])([^'"]*)$""")
        selfMemberPattern.find(beforeCursor)?.let { match ->
            val query = match.groupValues[2]
            return CompletionContext.SelfMember(
                replaceStart = cursor - query.length,
                replaceEnd = cursor,
                query = query
            )
        }

        val boxTargetPattern = Regex("""box\s*\(\s*(['"])([^'"]*)$""")
        boxTargetPattern.find(beforeCursor)?.let { match ->
            val query = match.groupValues[2]
            return CompletionContext.BoxTarget(
                replaceStart = cursor - query.length,
                replaceEnd = cursor,
                query = query
            )
        }

        val fragment = wordFragmentAround(formula, cursor)
        return CompletionContext.General(
            replaceStart = fragment.start,
            replaceEnd = fragment.end,
            query = fragment.text
        )
    }

    private fun generalCompletions(): List<GeneralCompletion> = listOf(
        GeneralCompletion(
            label = "sum",
            insertText = "sum(range(\"A1:A4\"))",
            kind = "function",
            detail = "Sum numeric values",
            documentation = "Adds every numeric value in a range or list.",
            cursorOffset = 11
        ),
        GeneralCompletion(
            label = "avg",
            insertText = "avg(range(\"A1:A4\"))",
            kind = "function",
            detail = "Average numeric values",
            documentation = "Returns the arithmetic mean of numeric values; an error if there are none.",
            cursorOffset = 11
        ),
        GeneralCompletion(
            label = "min",
            insertText = "min(range(\"A1:A4\"))",
            kind = "function",
            detail = "Minimum numeric value",
            documentation = "Returns the smallest numeric value in a range or list, or 0 if it has none.",
            cursorOffset = 11
        ),
        GeneralCompletion(
            label = "max",
            insertText = "max(range(\"A1:A4\"))",
            kind = "function",
            detail = "Maximum numeric value",
            documentation = "Returns the largest numeric value in a range or list, or 0 if it has none.",
            cursorOffset = 11
        ),
        GeneralCompletion(
            label = "count",
            insertText = "count(range(\"A1:A4\"))",
            kind = "function",
            detail = "Count non-empty values",
            documentation = "Counts non-empty values in a range or list.",
            cursorOffset = 13
        ),
        GeneralCompletion(
            label = "box",
            insertText = "box(\"Box Name\")[\"value\"]",
            kind = "box",
            detail = "Reference another Field box",
            documentation = "References a variable or property on another box by unique name or uid.",
            cursorOffset = 5
        ),
        GeneralCompletion(
            label = "self",
            insertText = "self[\"value\"]",
            kind = "box",
            detail = "Reference the current box",
            documentation = "References a variable or property on the box that owns this spreadsheet.",
            cursorOffset = 6
        ),
        GeneralCompletion(
            label = "cell",
            insertText = "cell(\"A1\")",
            kind = "function",
            detail = "Explicit single-cell lookup",
            documentation = "Equivalent to writing A1 directly inside a formula.",
            cursorOffset = 6
        ),
        GeneralCompletion(
            label = "range",
            insertText = "range(\"A1:B8\")",
            kind = "function",
            detail = "Explicit range lookup",
            documentation = "Returns a row-major list of non-empty values.",
            cursorOffset = 7
        ),
        GeneralCompletion(
            label = "if",
            insertText = "if (true) 1 else 0",
            kind = "snippet",
            detail = "Kotlin conditional expression",
            documentation = "Use Kotlin's normal conditional expression syntax.",
            cursorOffset = 4
        ),
        GeneralCompletion(
            label = "listOf",
            insertText = "listOf(A1, A2, A3)",
            kind = "function",
            detail = "Build a Kotlin list",
            documentation = "Constructs a Kotlin list from cell values or literals.",
            cursorOffset = 7
        ),
        // ── Units (via KotUniL + pipe syntax) ────────────────────────
        GeneralCompletion(label = "| m", insertText = "| m", kind = "unit", detail = "metres", documentation = "Tag value with metres (SI length)."),
        GeneralCompletion(label = "| mm", insertText = "| mm", kind = "unit", detail = "millimetres", documentation = "Tag value with millimetres."),
        GeneralCompletion(label = "| cm", insertText = "| cm", kind = "unit", detail = "centimetres", documentation = "Tag value with centimetres."),
        GeneralCompletion(label = "| km", insertText = "| km", kind = "unit", detail = "kilometres", documentation = "Tag value with kilometres."),
        GeneralCompletion(label = "| kg", insertText = "| kg", kind = "unit", detail = "kilograms", documentation = "Tag value with kilograms (SI mass)."),
        GeneralCompletion(label = "| g", insertText = "| g", kind = "unit", detail = "grams", documentation = "Tag value with grams."),
        GeneralCompletion(label = "| mg", insertText = "| mg", kind = "unit", detail = "milligrams", documentation = "Tag value with milligrams."),
        GeneralCompletion(label = "| t", insertText = "| t", kind = "unit", detail = "metric tons", documentation = "Tag value with metric tons (1000 kg)."),
        GeneralCompletion(label = "| s", insertText = "| s", kind = "unit", detail = "seconds", documentation = "Tag value with seconds (SI time)."),
        GeneralCompletion(label = "| ms", insertText = "| ms", kind = "unit", detail = "milliseconds", documentation = "Tag value with milliseconds."),
        GeneralCompletion(label = "| min", insertText = "| min", kind = "unit", detail = "minutes", documentation = "Tag value with minutes (60 s)."),
        GeneralCompletion(label = "| h", insertText = "| h", kind = "unit", detail = "hours", documentation = "Tag value with hours (3600 s)."),
        GeneralCompletion(label = "| N", insertText = "| N", kind = "unit", detail = "newtons", documentation = "Tag value with newtons (SI force)."),
        GeneralCompletion(label = "| Pa", insertText = "| Pa", kind = "unit", detail = "pascals", documentation = "Tag value with pascals (SI pressure)."),
        GeneralCompletion(label = "| J", insertText = "| J", kind = "unit", detail = "joules", documentation = "Tag value with joules (SI energy)."),
        GeneralCompletion(label = "| W", insertText = "| W", kind = "unit", detail = "watts", documentation = "Tag value with watts (SI power)."),
        GeneralCompletion(label = "| V", insertText = "| V", kind = "unit", detail = "volts", documentation = "Tag value with volts (SI voltage)."),
        GeneralCompletion(label = "| A", insertText = "| A", kind = "unit", detail = "amperes", documentation = "Tag value with amperes (SI current)."),
        GeneralCompletion(label = "| Hz", insertText = "| Hz", kind = "unit", detail = "hertz", documentation = "Tag value with hertz (SI frequency)."),
        GeneralCompletion(label = "| l", insertText = "| l", kind = "unit", detail = "litres", documentation = "Tag value with litres."),
        GeneralCompletion(label = "| ha", insertText = "| ha", kind = "unit", detail = "hectares", documentation = "Tag value with hectares."),
        GeneralCompletion(label = "| K", insertText = "| K", kind = "unit", detail = "kelvin", documentation = "Tag value with kelvin (SI temperature).")
    )
}

internal fun detachSpreadsheetClient(clientKey: Any)

internal fun detachSpreadsheetClient(clientKey: Any) {
    SpreadsheetService.detachClient(clientKey)
}

internal fun handleSpreadsheetRequest(text: String, info: SessionInformation?, clientKey: Any, push: (String) -> Unit): String =

The websocket request handler, for tests: what an open spreadsheet.html iframe sends.

internal fun handleSpreadsheetRequest(text: String, info: SessionInformation?, clientKey: Any, push: (String) -> Unit): String =
    SpreadsheetService.handle(text, info = info, clientKey = clientKey, push = push)

internal fun pollSpreadsheetsOnce() = SpreadsheetService.pollLiveSheets()

One frame of the live sweep the Runloop runs in preRun, for tests.

source — 69 lines, from line 2276
private fun normalizeStoredState(state: SpreadsheetStoredState?): SpreadsheetStoredState {
    val rows = (state?.rows ?: DEFAULT_SHEET_ROWS).coerceIn(1, MAX_SHEET_ROWS)
    val cols = (state?.cols ?: DEFAULT_SHEET_COLS).coerceIn(1, MAX_SHEET_COLS)
    val colWidths = MutableList<Double>(cols) { index ->
        (state?.colWidths?.getOrNull(index)?.toInt()?.coerceIn(48, 480) ?: DEFAULT_SHEET_COL_WIDTH).toDouble()
    }
    val rowHeights = MutableList<Double>(rows) { index ->
        (state?.rowHeights?.getOrNull(index)?.toInt()?.coerceIn(22, 240) ?: DEFAULT_SHEET_ROW_HEIGHT).toDouble()
    }
    val cells = linkedMapOf<String, SpreadsheetStoredCell>()
    state?.cells.orEmpty().forEach { cell ->
        if (cell.row !in 0 until rows || cell.col !in 0 until cols) return@forEach
        if (cell.input.isBlank()) return@forEach
        cells["${cell.row}:${cell.col}"] = SpreadsheetStoredCell(cell.row, cell.col, cell.input)
    }
    return SpreadsheetStoredState(
        rows = rows,
        cols = cols,
        colWidths = colWidths,
        rowHeights = rowHeights,
        cells = cells.values.toList(),
        solved = state?.solved.orEmpty().filterKeys { k ->
            k.split(':').let { it.size == 2 && (it[0].toIntOrNull() ?: -1) in 0 until rows && (it[1].toIntOrNull() ?: -1) in 0 until cols }
        }
    )
}

private fun normalizedSpreadsheetProperty(property: String): String =
    property.trim().ifBlank { "spreadsheet" }

private fun encodeStoredSpreadsheetState(state: SpreadsheetStoredState): String =
    SpreadsheetProtocol.json.encodeToString(normalizeStoredState(state))

private fun writeStoredSpreadsheetState(box: Box, property: String, state: SpreadsheetStoredState): SpreadsheetStoredState {
    val normalizedProperty = normalizedSpreadsheetProperty(property)
    val normalizedState = normalizeStoredState(state)
    box.setPropertyRaw(normalizedProperty, encodeStoredSpreadsheetState(normalizedState), doResync = false)
    if (Box.ioSeparateProperties.containsKey(normalizedProperty) && box.isInsideInitialized() && !box.inside.ephemeral) {
        box.inside.markSidecarPending(box.id, normalizedProperty)
    }
    return normalizedState
}

private fun decodeStoredState(raw: String?): SpreadsheetStoredState {
    if (raw.isNullOrBlank()) return normalizeStoredState(null)
    return runCatching {
        SpreadsheetProtocol.json.decodeFromString(SpreadsheetStoredState.serializer(), raw)
    }.getOrElse {
        lprintln("warning,spreadsheet", "failed to decode stored spreadsheet state: ${it.message}")
        normalizeStoredState(null)
    }
}

private fun classifyCellInput(input: String): String {
    val trimmed = input.trim()
    if (trimmed.isEmpty()) return "empty"
    if (trimmed.startsWith("=")) return "formula"
    if (trimmed.toDoubleOrNull() != null) return "number"
    return "text"
}

private fun spreadsheetCellInputText(input: Any?): String = when (input) {
    null -> ""
    is SpreadsheetScalar -> formatSpreadsheetValue(input.raw)
    is String -> input
    is CharSequence -> input.toString()
    is Number, is Boolean -> formatSpreadsheetValue(input)
    else -> input.toString()
}

internal fun rewriteSpreadsheetFormula(input: String, refs: MutableList<String> = mutableListOf()): String

Turn the cell references in a formula into calls: B2 * C2 becomes cell(__ref(0)) * cell(__ref(1)) with refs = [B2, C2]. The references are passed in rather than written into the source so that a formula filled down a column — B2 * C2, B3 * C3, ... — is one template, compiled once. Until 2026-10-26 each row was its own snippet, and opening a sheet compiled every formula on it in turn (about 0.3 to 2 s each), so a ten-formula sheet took seconds to appear.

internal fun rewriteSpreadsheetFormula(input: String, refs: MutableList<String> = mutableListOf()): String =
    rewritePipeUnits(rewriteCellRefs(input, refs))

Where the ${'$'}{ that opens a string template at open is closed, or -1.

source — 303 lines, from line 2357
private fun templateEnd(input: String, open: Int): Int {
    var depth = 1
    var index = open + 1
    while (index < input.length) {
        when (input[index]) {
            '{' -> depth += 1
            '}' -> { depth -= 1; if (depth == 0) return index }
        }
        index += 1
    }
    return -1
}

private fun rewriteCellRefs(input: String, refs: MutableList<String>): String {
    val out = StringBuilder(input.length + 32)
    var index = 0
    var quote: Char? = null
    while (index < input.length) {
        val ch = input[index]
        if (quote != null) {
            // The code inside a string template is code: `"${'$'}{min(A2:A4)}"` reads the range.
            if (quote == '"' && ch == '$' && index + 1 < input.length && input[index + 1] == '{') {
                val close = templateEnd(input, index + 1)
                if (close > 0) {
                    out.append("${'$'}{").append(rewriteCellRefs(input.substring(index + 2, close), refs)).append('}')
                    index = close + 1
                    continue
                }
            }
            out.append(ch)
            if (ch == '\\' && quote != '`' && index + 1 < input.length) {
                out.append(input[index + 1])
                index += 2
                continue
            }
            if (ch == quote) quote = null
            index += 1
            continue
        }
        if (ch == '\'' || ch == '"' || ch == '`') {
            quote = ch
            out.append(ch)
            index += 1
            continue
        }
        val parsedFirst = parseCellRefAt(input, index)
        if (parsedFirst != null && isRefBoundaryBefore(input, index)) {
            val colonIndex = parsedFirst.endExclusive
            if (colonIndex < input.length && input[colonIndex] == ':') {
                val parsedSecond = parseCellRefAt(input, colonIndex + 1)
                if (parsedSecond != null && isRefBoundaryAfter(input, parsedSecond.endExclusive)) {
                    refs += "${cellLabel(parsedFirst.row, parsedFirst.col)}:${cellLabel(parsedSecond.row, parsedSecond.col)}"
                    out.append("range(__ref(${refs.lastIndex}))")
                    index = parsedSecond.endExclusive
                    continue
                }
            }
            if (isRefBoundaryAfter(input, parsedFirst.endExclusive)) {
                refs += cellLabel(parsedFirst.row, parsedFirst.col)
                out.append("cell(__ref(${refs.lastIndex}))")
                index = parsedFirst.endExclusive
                continue
            }
        }
        out.append(ch)
        index += 1
    }
    return out.toString()
}

private val PIPE_UNIT_PATTERN = Regex("""\|\s*([A-Za-z_`][A-Za-z0-9_`]*)""")

private fun rewritePipeUnits(input: String): String {
    val out = StringBuilder(input.length + 32)
    var index = 0
    var quote: Char? = null
    while (index < input.length) {
        val ch = input[index]
        if (quote != null) {
            out.append(ch)
            if (ch == '\\' && quote != '`' && index + 1 < input.length) {
                out.append(input[index + 1])
                index += 2
                continue
            }
            if (ch == quote) quote = null
            index += 1
            continue
        }
        if (ch == '\'' || ch == '"' || ch == '`') {
            quote = ch
            out.append(ch)
            index += 1
            continue
        }
        if (ch == '|') {
            val remaining = input.substring(index)
            val match = PIPE_UNIT_PATTERN.matchAt(remaining, 0)
            if (match != null) {
                val unitIdent = match.groupValues[1]
                out.append(".withUnit($unitIdent)")
                index += match.value.length
                continue
            }
        }
        out.append(ch)
        index += 1
    }
    return out.toString()
}

private fun parseCellRefAt(source: String, start: Int): ParsedCellRef? {
    var index = start
    if (index >= source.length) return null
    if (source[index] == '$') index += 1
    val colStart = index
    while (index < source.length && source[index].isLetter()) index += 1
    if (index == colStart) return null
    if (index < source.length && source[index] == '$') index += 1
    val rowStart = index
    while (index < source.length && source[index].isDigit()) index += 1
    if (index == rowStart) return null

    val colEnd = if (rowStart > colStart && source[rowStart - 1] == '$') rowStart - 1 else rowStart
    val colText = source.substring(colStart, colEnd)
    val rowDigits = source.substring(rowStart, index)
    val row = rowDigits.toIntOrNull()?.minus(1) ?: return null
    if (row < 0) return null
    var col = 0
    colText.uppercase().forEach { letter ->
        col = col * 26 + (letter.code - 'A'.code + 1)
    }
    return ParsedCellRef(row = row, col = col - 1, endExclusive = index)
}

private fun isRefBoundaryBefore(source: String, index: Int): Boolean {
    if (index <= 0) return true
    val prev = source[index - 1]
    return !(prev.isLetterOrDigit() || prev == '_' || prev == ']')
}

private fun isRefBoundaryAfter(source: String, index: Int): Boolean {
    if (index >= source.length) return true
    val next = source[index]
    return !(next.isLetterOrDigit() || next == '_' || next == '[')
}

private fun cellRefToCoord(ref: String): CellCoord? {
    val parsed = parseCellRefAt(ref.trim(), 0) ?: return null
    if (parsed.endExclusive != ref.trim().length) return null
    return CellCoord(parsed.row, parsed.col)
}

private fun columnLabelToIndex(label: String): Int? {
    val text = label.trim().uppercase()
    if (text.isEmpty() || text.any { !it.isLetter() }) return null
    var col = 0
    text.forEach { letter ->
        col = col * 26 + (letter.code - 'A'.code + 1)
    }
    return col - 1
}

private fun cellLabel(row: Int, col: Int): String {
    var label = ""
    var current = col
    while (current >= 0) {
        label = ('A'.code + (current % 26)).toChar() + label
        current = current / 26 - 1
    }
    return "$label${row + 1}"
}

private fun fingerprintSpreadsheetDependencyValue(value: Any?): String = when (value) {
    is SpreadsheetScalar -> fingerprintSpreadsheetDependencyValue(value.raw)
    null -> "null"
    is Unit -> "unit"
    is String -> "string:${value.length}:$value"
    is Boolean -> "bool:$value"
    is Vec2 -> "vec2:${value.x},${value.y}"
    is Vec3 -> "vec3:${value.x},${value.y},${value.z}"
    is Vec4 -> "vec4:${value.x},${value.y},${value.z},${value.w}"
    is FLine -> summarizeFLine(value).let { "fline:${it.x},${it.y},${it.w},${it.h}" }
    is ByteArray -> value.joinToString(prefix = "bytes[", postfix = "]")
    is ShortArray -> value.joinToString(prefix = "shorts[", postfix = "]")
    is IntArray -> value.joinToString(prefix = "ints[", postfix = "]")
    is LongArray -> value.joinToString(prefix = "longs[", postfix = "]")
    is FloatArray -> value.joinToString(prefix = "floats[", postfix = "]")
    is DoubleArray -> value.joinToString(prefix = "doubles[", postfix = "]")
    is BooleanArray -> value.joinToString(prefix = "bools[", postfix = "]")
    is CharArray -> "chars[${value.concatToString()}]"
    is Array<*> -> value.joinToString(prefix = "array[", postfix = "]") { fingerprintSpreadsheetDependencyValue(it) }
    is Iterable<*> -> value.joinToString(prefix = "iter[", postfix = "]") { fingerprintSpreadsheetDependencyValue(it) }
    is Map<*, *> -> value.entries
        .sortedBy { fingerprintSpreadsheetDependencyValue(it.key) }
        .joinToString(prefix = "map{", postfix = "}") {
            "${fingerprintSpreadsheetDependencyValue(it.key)}=${fingerprintSpreadsheetDependencyValue(it.value)}"
        }

    is Quantity -> "quantity:${value.value}:${value.unitSymbols}:${value.displaySymbol}"
    is Expression -> "expression:${value.value}:${value.unitSymbols()}"
    is Number -> "number:${value.javaClass.name}:${value}"
    else -> "${value.javaClass.name}:${value}"
}

private fun formatSpreadsheetValue(value: Any?): String = when (value) {
    is SpreadsheetScalar -> formatSpreadsheetValue(value.raw)
    is SpreadsheetTraced -> formatNumber(value.value)
    is SpreadsheetAssertion -> formatAssertion(value)
    null -> ""
    is Unit -> ""
    is Quantity -> {
        val (v, sym) = formatQuantityDisplay(value)
        if (sym.isNotEmpty()) "${formatNumber(v)} $sym" else formatNumber(v)
    }
    is Expression -> {
        val (v, sym) = formatExpression(value)
        if (sym.isNotEmpty()) "${formatNumber(v)} $sym" else formatNumber(value.value)
    }
    is Double -> formatNumber(value)
    is Float -> formatNumber(value.toDouble())
    is Number -> value.toString()
    is Boolean -> value.toString()
    is String -> value
    is Vec2 -> "vec(${formatNumber(value.x.toDouble())}, ${formatNumber(value.y.toDouble())})"
    is Vec3 -> "vec(${formatNumber(value.x.toDouble())}, ${formatNumber(value.y.toDouble())}, ${formatNumber(value.z.toDouble())})"
    is Vec4 -> "vec(${formatNumber(value.x.toDouble())}, ${formatNumber(value.y.toDouble())}, ${formatNumber(value.z.toDouble())}, ${formatNumber(value.w.toDouble())})"
    is FLine -> summarizeFLine(value).let {
        "FLine(${formatNumber(it.x)}, ${formatNumber(it.y)}, ${formatNumber(it.w)}, ${formatNumber(it.h)})"
    }
    is SpreadsheetBoxBinding -> value.name
    is ByteArray -> value.joinToString(prefix = "[", postfix = "]")
    is ShortArray -> value.joinToString(prefix = "[", postfix = "]")
    is IntArray -> value.joinToString(prefix = "[", postfix = "]")
    is LongArray -> value.joinToString(prefix = "[", postfix = "]")
    is FloatArray -> value.joinToString(prefix = "[", postfix = "]") { formatNumber(it.toDouble()) }
    is DoubleArray -> value.joinToString(prefix = "[", postfix = "]") { formatNumber(it) }
    is BooleanArray -> value.joinToString(prefix = "[", postfix = "]")
    is Iterable<*> -> value.joinToString(prefix = "[", postfix = "]") { formatSpreadsheetValue(it) }
    is Array<*> -> value.joinToString(prefix = "[", postfix = "]") { formatSpreadsheetValue(it) }
    else -> value.toString()
}

private fun isSpreadsheetScalarDisplay(value: Any?): Boolean {
    val raw = unwrapSpreadsheetValue(value)
    return when (raw) {
        null, is Unit -> true
        is Number, is Boolean, is SpreadsheetTraced -> true
        is Quantity, is Expression -> true
        is String -> raw.length <= 120
        else -> false
    }
}

private fun formatAssertion(a: SpreadsheetAssertion): String =
    if (a.satisfied) "✓ ${formatNumber(a.left)} ${a.relation.symbol} ${formatNumber(a.right)}"
    else "✗ ${formatNumber(a.left)} ${a.relation.symbol} ${formatNumber(a.right)} (off by ${formatNumber(a.violation).let { if (it == "0") "%.2g".format(a.violation) else it }})"

private fun spreadsheetHtmlDisplay(value: Any?): String? {
    val raw = unwrapSpreadsheetValue(value)
    if (raw is SpreadsheetAssertion) {
        val state = if (raw.satisfied) "ok" else "violated"
        return "<span class=\"sheet-assertion\" data-assertion=\"$state\">${formatAssertion(raw)}</span>"
    }
    if (isSpreadsheetScalarDisplay(value)) return null
    return try {
        ValueVisualizer.visualize(unwrapSpreadsheetValue(value))
    } catch (_: Throwable) {
        null
    }
}

private fun formatNumber(value: Double): String {
    val rounded = round(value * 10_000.0) / 10_000.0
    val safe = if (rounded == -0.0) 0.0 else rounded
    val fixed = "%,.4f".format(java.util.Locale.US, safe)
    return fixed
        .replace(",", "")
        .replace(Regex("""\.?0+$"""), "")
}

private fun numericValue(value: Any?): Double? = SpreadsheetDynamicRegistry.scalar(value)

private fun safeForId(value: String): String =
    value.replace(Regex("""[^A-Za-z0-9_]+"""), "_").trim('_').ifBlank { UUID.randomUUID().toString().replace("-", "_") }

private fun wordFragmentAround(text: String, cursor: Int): SheetWordFragment {
    var start = cursor
    var end = cursor
    while (start > 0 && isCompletionWordChar(text[start - 1])) start -= 1
    while (end < text.length && isCompletionWordChar(text[end])) end += 1
    return SheetWordFragment(start, end, text.substring(start, cursor))
}

private fun isCompletionWordChar(ch: Char): Boolean =
    ch.isLetterOrDigit() || ch == '_' || ch == '.'

private fun matchesCompletionQuery(label: String, query: String): Boolean {
    if (query.isBlank()) return true
    val haystack = label.lowercase()
    val needle = query.lowercase()
    return haystack.contains(needle)
}

fun Box.spreadsheet

Show the sheet stored in property in output channel, first filling an empty sheet from defaultState. width defaults to the sheet's own width — its columns plus the row header, up to 960 px, beyond which the grid scrolls — so a four-column sheet is not squeezed into a narrow output or stretched across a wide one.

fun Box.spreadsheet(
    channel: String = "spreadsheet",
    property: String = "spreadsheet",
    height: Int = 360,
    defaultState: SpreadsheetStoredState? = null,
    width: Int? = null
) {
    val normalizedProperty = normalizedSpreadsheetProperty(property)
    registerSpreadsheetProperty(normalizedProperty)
    defaultState?.let { seedSpreadsheet(property = normalizedProperty, state = it) }
    val sheetWidth = width ?: naturalSheetWidth(getSpreadsheetState(normalizedProperty))
    val info = inside
    val params = mutableListOf(
        "uid=${urlEncode(id)}",
        "property=${urlEncode(normalizedProperty)}",
        "file=${urlEncode(info.documentName)}",
        "user=${urlEncode("spreadsheet")}"
    )
    if (info.ephemeral) {
        params += "scratch=1"
        params += "sessionKey=${urlEncode(info.filename)}"
    }
    val displayChannel = if (channel == normalizedProperty) channel else "$channel/$normalizedProperty"
    // Not `loading="lazy"`: a lazy iframe in a closed output panel has no size, so it did not load until
    // the panel opened, and the sheet then sat empty for the seconds its formulas took to compile.
    // `data-afield-autosize="off"`: the sheet's size is known here, so the panel takes it from the
    // inline pixels; measured from inside, a frame that loaded in a closed panel reported its own
    // collapsed width and the sheet was clipped to a column and a half.
    display(
        displayChannel,
        """
        <iframe src="/spreadsheet.html?${params.joinToString("&")}" data-afield-autosize="off"
            style="border:none;padding:0;margin:0;width:${sheetWidth}px;max-width:none;height:${height}px;display:block;background:transparent;"></iframe>
        """.trimIndent()
    )
}

The editor's row header (HEADER_WIDTH in spreadsheet.js), the root's 8 px padding each side, the grid's border, and room for a vertical scrollbar.

private const val SHEET_CHROME_WIDTH = 34 + 2 * 8 + 2 + 10
private const val MAX_NATURAL_SHEET_WIDTH = 960

private fun naturalSheetWidth(state: SpreadsheetStoredState): Int =
    (SHEET_CHROME_WIDTH + state.colWidths.sumOf { it }.toInt()).coerceIn(320, MAX_NATURAL_SHEET_WIDTH)

fun Box.spreadsheet

fun Box.spreadsheet(
    channel: String = "spreadsheet",
    property: String = "spreadsheet",
    height: Int = 360,
    width: Int? = null,
    defaultState: SpreadsheetStateBuilder.() -> Unit
) = spreadsheet(channel = channel, property = property, height = height, defaultState = spreadsheetState(defaultState), width = width)

fun Box.sheet

fun Box.sheet(
    channel: String = "spreadsheet",
    property: String = "spreadsheet",
    height: Int = 360,
    defaultState: SpreadsheetStoredState? = null,
    width: Int? = null
) = spreadsheet(channel = channel, property = property, height = height, defaultState = defaultState, width = width)

fun Box.sheet

fun Box.sheet(
    channel: String = "spreadsheet",
    property: String = "spreadsheet",
    height: Int = 360,
    width: Int? = null,
    defaultState: SpreadsheetStateBuilder.() -> Unit
) = spreadsheet(channel = channel, property = property, height = height, width = width, defaultState = defaultState)

fun Box.getSpreadsheetState(property: String = "spreadsheet"): SpreadsheetStoredState

fun Box.getSpreadsheetState(property: String = "spreadsheet"): SpreadsheetStoredState {
    val normalizedProperty = normalizedSpreadsheetProperty(property)
    registerSpreadsheetProperty(normalizedProperty)
    return if (isInsideInitialized()) {
        SpreadsheetService.currentState(this, normalizedProperty)
    } else {
        decodeStoredState(getPropertyRaw(normalizedProperty, null) as? String)
    }
}

fun Box.sheetState(property: String = "spreadsheet"): SpreadsheetStoredState

fun Box.sheetState(property: String = "spreadsheet"): SpreadsheetStoredState =
    getSpreadsheetState(property)

fun Box.getSpreadsheetSnapshot

fun Box.getSpreadsheetSnapshot(
    property: String = "spreadsheet",
    forceReload: Boolean = false,
    forceRecompute: Boolean = true
): SpreadsheetSnapshot {
    val normalizedProperty = normalizedSpreadsheetProperty(property)
    registerSpreadsheetProperty(normalizedProperty)
    if (!isInsideInitialized()) error("Spreadsheet snapshot requires an initialized box")
    return SpreadsheetService.snapshot(this, inside, normalizedProperty, forceReload = forceReload, forceRecompute = forceRecompute)
}

fun Box.sheetSnapshot

fun Box.sheetSnapshot(
    property: String = "spreadsheet",
    forceReload: Boolean = false,
    forceRecompute: Boolean = true
): SpreadsheetSnapshot =
    getSpreadsheetSnapshot(property = property, forceReload = forceReload, forceRecompute = forceRecompute)

fun Box.getSpreadsheetCellValue

fun Box.getSpreadsheetCellValue(
    ref: String,
    property: String = "spreadsheet",
    forceReload: Boolean = false,
    forceRecompute: Boolean = true
): Any? {
    val normalizedProperty = normalizedSpreadsheetProperty(property)
    registerSpreadsheetProperty(normalizedProperty)
    if (!isInsideInitialized()) error("Spreadsheet cell lookup requires an initialized box")
    return SpreadsheetService.cellValue(
        this,
        inside,
        normalizedProperty,
        ref,
        forceReload = forceReload,
        forceRecompute = forceRecompute
    )
}

fun Box.getSpreadsheetCell

fun Box.getSpreadsheetCell(
    ref: String,
    property: String = "spreadsheet",
    forceReload: Boolean = false,
    forceRecompute: Boolean = true
): SpreadsheetScalar =
    SpreadsheetScalar(
        getSpreadsheetCellValue(
            ref,
            property = property,
            forceReload = forceReload,
            forceRecompute = forceRecompute
        )
    )

fun Box.sheetCellValue

fun Box.sheetCellValue(
    ref: String,
    property: String = "spreadsheet",
    forceReload: Boolean = false,
    forceRecompute: Boolean = true
): Any? =
    getSpreadsheetCellValue(
        ref,
        property = property,
        forceReload = forceReload,
        forceRecompute = forceRecompute
    )

fun Box.sheetCell

fun Box.sheetCell(
    ref: String,
    property: String = "spreadsheet",
    forceReload: Boolean = false,
    forceRecompute: Boolean = true
): SpreadsheetScalar =
    getSpreadsheetCell(
        ref,
        property = property,
        forceReload = forceReload,
        forceRecompute = forceRecompute
    )

fun Box.setSpreadsheetState

fun Box.setSpreadsheetState(
    state: SpreadsheetStoredState,
    property: String = "spreadsheet",
    broadcast: Boolean = true
): SpreadsheetStoredState {
    val normalizedProperty = normalizedSpreadsheetProperty(property)
    registerSpreadsheetProperty(normalizedProperty)
    return if (isInsideInitialized()) {
        SpreadsheetService.applyProgrammaticState(this, inside, normalizedProperty, state, broadcast = broadcast)
    } else {
        writeStoredSpreadsheetState(this, normalizedProperty, state)
    }
}

fun Box.setSpreadsheetState

fun Box.setSpreadsheetState(
    property: String = "spreadsheet",
    broadcast: Boolean = true,
    init: SpreadsheetStateBuilder.() -> Unit
): SpreadsheetStoredState =
    setSpreadsheetState(spreadsheetState(init), property = property, broadcast = broadcast)

fun Box.seedSpreadsheet

fun Box.seedSpreadsheet(
    property: String = "spreadsheet",
    state: SpreadsheetStoredState,
    broadcast: Boolean = true
): SpreadsheetStoredState {
    val normalizedProperty = normalizedSpreadsheetProperty(property)
    registerSpreadsheetProperty(normalizedProperty)
    val raw = getPropertyRaw(normalizedProperty, null) as? String
    if (!raw.isNullOrBlank()) {
        return decodeStoredState(raw)
    }
    return setSpreadsheetState(state, property = normalizedProperty, broadcast = broadcast)
}

fun Box.seedSpreadsheet

fun Box.seedSpreadsheet(
    property: String = "spreadsheet",
    broadcast: Boolean = true,
    init: SpreadsheetStateBuilder.() -> Unit
): SpreadsheetStoredState =
    seedSpreadsheet(property = property, state = spreadsheetState(init), broadcast = broadcast)

fun Box.seedSheet

fun Box.seedSheet(
    property: String = "spreadsheet",
    state: SpreadsheetStoredState,
    broadcast: Boolean = true
): SpreadsheetStoredState = seedSpreadsheet(property = property, state = state, broadcast = broadcast)

fun Box.editSpreadsheet

fun Box.editSpreadsheet(
    property: String = "spreadsheet",
    broadcast: Boolean = true,
    edit: SpreadsheetStateBuilder.() -> Unit
): SpreadsheetStoredState {
    val builder = SpreadsheetStateBuilder(getSpreadsheetState(property))
    builder.edit()
    return setSpreadsheetState(builder.build(), property = property, broadcast = broadcast)
}

fun Box.editSheet

fun Box.editSheet(
    property: String = "spreadsheet",
    broadcast: Boolean = true,
    edit: SpreadsheetStateBuilder.() -> Unit
): SpreadsheetStoredState = editSpreadsheet(property = property, broadcast = broadcast, edit = edit)

private fun urlEncode(value: String): String = URLEncoder.encode(value, StandardCharsets.UTF_8)

private fun registerSpreadsheetProperty(property: String) {
    val normalized = normalizedSpreadsheetProperty(property)
    Box.ioPersistentProperties += normalized
    Box.ioSeparateProperties.putIfAbsent(normalized, "json")
}

fun installSpreadsheetSupport()

body — 48 lines, from line 2899
@RunsAt(installHandlers)
fun installSpreadsheetSupport() {
    registerSpreadsheetProperty("spreadsheet")
    Runloop.runLoop.preRun[SPREADSHEET_POLL_HOOK] = sequence {
        while (true) {
            runCatching { SpreadsheetService.pollLiveSheets() }
                .onFailure { lprintln("warning,spreadsheet", "spreadsheet live polling failed: ${it.message}") }
            yield(Unit)
        }
    }

    rawHandlers.getOrPut(SPREADSHEET_CHANNEL) { mutableSetOf() }.add spreadsheet@{ text ->
        val envelope = runCatching { JSONObject(text) }.getOrNull()
        if (envelope == null || (!envelope.has("op") && envelope.optString("channel") != SPREADSHEET_CHANNEL)) {
            return@spreadsheet Handled.no
        }
        val ws = thisSession.get()
        val info = thisSessionInfo.get()
        val reqSnippet = text.take(120)
        lprintln("debug,spreadsheet,ws", "[sheet-ws] raw handler received (${text.length} chars): $reqSnippet")
        inMain {
            lprintln("debug,spreadsheet,ws", "[sheet-ws] inMain entered for: $reqSnippet")
            try {
                val response = SpreadsheetService.handle(text, info = info, clientKey = ws) { payload ->
                    lprintln("debug,spreadsheet,ws", "[sheet-ws] broadcast push (${payload.length} chars)")
                    inMain {
                        try {
                            ws.send(payload)
                        } catch (t: Throwable) {
                            lprintln("warning,spreadsheet,ws", "[sheet-ws] broadcast send failed: ${t.message}")
                        }
                    }
                }
                if (response.isNotEmpty()) {
                    lprintln("debug,spreadsheet,ws", "[sheet-ws] sending response (${response.length} chars)")
                    ws.send(response)
                    lprintln("debug,spreadsheet,ws", "[sheet-ws] response sent OK")
                } else {
                    lprintln("debug,spreadsheet,ws", "[sheet-ws] handle returned empty response")
                }
            } catch (t: Throwable) {
                lprintln("error,spreadsheet,ws", "[sheet-ws] inMain block failed: ${t::class.simpleName}: ${t.message}")
                t.printStackTrace()
            }
        }
        Handled.yes
    }
}