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}") }
}