Skip to content

WhyAnswer.kt

src/jvmMain/kotlin/afield/document/context/WhyAnswer.kt · 114 lines

@file:afield.runtime.completion.CompletionCategory("Workspace")

package afield.document.context

import afield.data.table.div
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.runtime.completion.CompletionCategory
import afield.runtime.execution.plus
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.ui.notation.with
import afield.ui.visualization.h
import afield.ui.visualization.h2
import afield.ui.visualization.table

import afield.audio.synth.*
import afield.data.table.cell
import afield.data.table.get
import afield.document.graph.box
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.lifecycle.RunsAt
import afield.runtime.lifecycle.newRoot
import afield.ui.output.FieldMarkdown
import afield.ui.output.markdown
import afield.ui.output.md
import afield.ui.visualization.H2ValueSnapshot
import afield.ui.visualization.ToHTML
import afield.ui.visualization.registerH2Value

The value a box gets back when it asks where one of its inputs comes from.

ContextResolution.explain and why.<name> both return a WhyAnswer. Printing it gives a one-sentence summary, .h shows it as a card, and .h2 lays out its fields to explore. The full JSON answer stays available as WhyAnswer.json.

class WhyAnswer(val json: kotlinx.serialization.json.JsonObject) : ToHTML

One answer from ContextResolution.explain, with the fields a person reads first pulled out.

json is the whole answer, unchanged — the same object the backdoor's why, MCP field_why and the AI tool WhyDoesThisResolve return. .h shows markdown as a card; .h2 explores fields; toString() is the summary sentence, so print(ContextResolution.explain(thisBox, "speed")) reads as prose.

class WhyAnswer(val json: kotlinx.serialization.json.JsonObject) : ToHTML {
    private val plain = ContextWhy.plain(json) as Map<*, *>

    private fun name(o: Any?): String? = (o as? Map<*, *>)?.let { m ->
        val box = (m["supplier"] ?: m) as? Map<*, *>
        (box?.get("name") as? String)?.ifBlank { null } ?: box?.get("uid") as? String
    }

    private fun names(key: String): List<String> = (plain[key] as? List<*>).orEmpty().mapNotNull(::name)

val query: String get() = plain["query"] as String

What was asked: a name or a type.

val found: Boolean get() = plain["found"] == true

Whether anything up the graph supplies it.

val supplier: String? get() = name(plain["supplier"])

The box whose value is used, or null when nothing supplies it.

val matchKind: String? get() = plain["matchKind"] as? String

Where in the supplier it was found: PROPERTY, VARIABLE or SELF.

val value: String? get() = plain["value"] as? String

The value, as text.

val path: List<String> get() = names("path")

The boxes from this one to the supplier.

val competing: List<String> get() = names("competing")

Boxes as near as the supplier holding a different value — an ambiguity settled by parents order.

val shadowed: List<String> get() = names("shadowed")

Every other box that also answers, farther away or later in order.

val summary: String get() = plain["summary"] as String

The one-sentence explanation.

val markdown: String

The answer as a short Markdown table and the summary sentence.

    val markdown: String
        get() {
            fun cell(xs: List<String>) = if (xs.isEmpty()) "—" else xs.joinToString(", ") { it.md }
            val rows = if (!found) "| supplier | nothing supplies `${query.md}` |\n" else """
                | supplier | ${supplier.md} (${matchKind?.lowercase().md}) = ${value.md} |
                | path | ${path.joinToString(" → ") { it.md }} |
                | competing | ${cell(competing)} |
                | shadowed | ${cell(shadowed)} |
            """.trimIndent() + "\n"
            return "| why `${query.md}` | |\n| --- | --- |\n" + rows + "\n" + summaryMarkdown
        }

val summaryMarkdown: String

summary as Markdown: the sentence already writes names and code between backticks, so those spans are kept as inline code and only the text between them is escaped.

    val summaryMarkdown: String
        get() = summary.split('`').mapIndexed { i, part -> if (i % 2 == 1) "`$part`" else part.md }.joinToString("")

val fields: Map<String, Any?>

The fields markdown shows, for .h2 to page through; the whole answer is under json.

    val fields: Map<String, Any?>
        get() = linkedMapOf("query" to query, "supplier" to supplier, "matchKind" to matchKind, "value" to value,
            "path" to path, "competing" to competing, "shadowed" to shadowed, "summary" to summary, "json" to plain)

override fun toHTML(): String = FieldMarkdown.card(markdown, width = 560)

override fun toString(): String = summary

fun registerWhyH2()

The .h2 rendering of a WhyAnswer: its fields to explore, its summary sentence as the headline.

@RunsAt(newRoot)
fun registerWhyH2() {
    registerH2Value<WhyAnswer> { H2ValueSnapshot(value = it.fields, summary = it.summary, title = "why ${it.query}") }
}