Restore typed SSD client baseline

ober

03e6a0d14d2f932dae8006100ccbc3c46c0f12ab

diff --git a/jandroid.ss b/jandroid.ss
index b3deb3c..37a1a36 100644
--- a/jandroid.ss
+++ b/jandroid.ss
@@ -41,7 +41,7 @@
             #f))
       (error 'find-app "expected `(def app '(...))` in app spec")))
 
-(def (find-fragment forms path)
+(def (find-fragment-data forms path)
   (or (for/or ((form forms))
         (if (and (pair? form)
                  (eq? (car form) 'def)
@@ -52,6 +52,31 @@
             #f))
       (error 'find-fragment "expected `(def fragment '(...))` in fragment file" path)))
 
+(def (fragment-include? item)
+  (and (pair? item)
+       (eq? (car item) 'include-fragment)
+       (= (length item) 2)
+       (string? (cadr item))))
+
+(def (find-fragment forms path (ancestors '()))
+  (when (member path ancestors)
+    (error 'find-fragment "fragment include cycle" (reverse (cons path ancestors))))
+  (let ((fragment (find-fragment-data forms path))
+        (base-dir (path-directory path))
+        (next-ancestors (cons path ancestors)))
+    (apply append
+      (map (lambda (item)
+             (if (fragment-include? item)
+                 (let ((included-path
+                         (resolve-local-source-path
+                           base-dir (cadr item) '(".ss"))))
+                   (find-fragment
+                     (read-all-forms included-path)
+                     included-path
+                     next-ancestors))
+                 (list item)))
+           fragment))))
+
 (def (entries spec)
   (if (and (pair? spec) (eq? (car spec) 'android-app))
       (cdr spec)
diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index aa18857..24dcece 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -2,7073 +2,18793 @@
 
 (def fragment
   '(
-    (kotlin-file-lines "com/sfb/ssdreview/BoxTypes.kt"
-      (
-       "package com.sfb.ssdreview"
-       ""
-       "import android.content.Context"
-       ""
-       "data class BoxType(val id: String, val name: String) {"
-       "    val display: String get() = \"$id $name\""
-       "}"
-       ""
-       "object BoxTypes {"
-       "    private var cache: List<BoxType>? = null"
-       "    private val weaponTerms = listOf("
-       "        \"add\","
-       "        \"antiproton\","
-       "        \"atomicmissile\","
-       "        \"axiontorpedo\","
-       "        \"bioelectricbolt\","
-       "        \"bombthrower\","
-       "        \"bosondrill\","
-       "        \"cannon\","
-       "        \"clusterbomb\","
-       "        \"deathboltrack\","
-       "        \"disruptor\","
-       "        \"drone\","
-       "        \"energyhowitzer\","
-       "        \"esg\","
-       "        \"fireball\","
-       "        \"fusion\","
-       "        \"gausscannon\","
-       "        \"hellbore\","
-       "        \"hellgun\","
-       "        \"hypercannon\","
-       "        \"hyperdrone\","
-       "        \"implosionbolt\","
-       "        \"implosiontorpedo\","
-       "        \"ioncannon\","
-       "        \"ionpulsecannon\","
-       "        \"kineticcannon\","
-       "        \"kineticwave\","
-       "        \"laser\","
-       "        \"massdriver\","
-       "        \"megaphaser\","
-       "        \"missile\","
-       "        \"mine\","
-       "        \"morter\","
-       "        \"mortar\","
-       "        \"neutronbeam\","
-       "        \"neutrongun\","
-       "        \"novacannon\","
-       "        \"optionmount\","
-       "        \"particlebeam\","
-       "        \"particlecannon\","
-       "        \"phaser\","
-       "        \"photon\","
-       "        \"plasma\","
-       "        \"positronlancet\","
-       "        \"ppd\","
-       "        \"prospectingcannon\","
-       "        \"proton\","
-       "        \"pulsecannon\","
-       "        \"pulseemitter\","
-       "        \"quantumcannon\","
-       "        \"railgun\","
-       "        \"rocket\","
-       "        \"sfg\","
-       "        \"shortrangecannon\","
-       "        \"sonicpulser\","
-       "        \"spaceauger\","
-       "        \"stingtorpedo\","
-       "        \"subspacerocket\","
-       "        \"tachyonbeam\","
-       "        \"tachyongun\","
-       "        \"tachyonmissile\","
-       "        \"torp\","
-       "        \"torpedo\","
-       "        \"trh\","
-       "        \"trl\","
-       "        \"webbreaker\","
-       "        \"webcaster\","
-       "        \"websnare\""
-       "    )"
-       "    private val nonWeaponTerms = listOf("
-       "        \"charge\","
-       "        \"degradation\","
-       "        \"fighterbox\","
-       "        \"hangar\","
-       "        \"hit\","
-       "        \"internalweaponbay\","
-       "        \"link\","
-       "        \"mechlink\","
-       "        \"rail\","
-       "        \"round\","
-       "        \"stabilizer\","
-       "        \"targetaccentuator\","
-       "        \"targetacquisitiongear\","
-       "        \"targetacquisitionguide\""
-       "    )"
-       ""
-       "    fun load(context: Context): List<BoxType> {"
-       "        cache?.let { return it }"
-       "        val items = mutableListOf<BoxType>()"
-       "        context.assets.open(\"box_types.csv\").bufferedReader().useLines { lines ->"
-       "            lines.drop(1).forEach { line ->"
-       "                val comma = line.indexOf(',')"
-       "                if (comma > 0) {"
-       "                    val id = line.substring(0, comma).trim()"
-       "                    val name = line.substring(comma + 1).trim()"
-       "                    if (id.isNotBlank() && name.isNotBlank()) {"
-       "                        items.add(BoxType(id, name))"
-       "                    }"
-       "                }"
-       "            }"
-       "        }"
-       "        cache = items"
-       "        return items"
-       "    }"
-       ""
-       "    fun resolve(context: Context, raw: String): BoxType? {"
-       "        val text = raw.trim()"
-       "        if (text.isBlank()) return null"
-       "        val normalized = normalize(text)"
-       "        val lowered = text.lowercase()"
-       "        val items = load(context)"
-       "        items.firstOrNull {"
-       "            it.id == text ||"
-       "                it.display.lowercase() == lowered ||"
-       "                it.name.lowercase() == lowered ||"
-       "                normalize(it.name) == normalized"
-       "        }?.let { return it }"
-       "        items.mapNotNull { item ->"
-       "            val displayIndex = lowered.lastIndexOf(item.display.lowercase())"
-       "            val nameIndex = lowered.lastIndexOf(item.name.lowercase())"
-       "            val normalizedIndex = normalized.lastIndexOf(normalize(item.name))"
-       "            val index = maxOf(displayIndex, nameIndex, normalizedIndex)"
-       "            if (index >= 0) index to item else null"
-       "        }.maxWithOrNull(compareBy<Pair<Int, BoxType>> { it.first }.thenBy { it.second.display.length })?.let { return it.second }"
-       "        val matches = items.filter {"
-       "            it.display.lowercase().contains(lowered) ||"
-       "                normalize(it.name).contains(normalized)"
-       "        }"
-       "        return matches.singleOrNull()"
-       "    }"
-       ""
-       "    fun isWeapon(context: Context, raw: String): Boolean {"
-       "        val text = raw.trim()"
-       "        if (text.isBlank()) return false"
-       "        val resolved = resolve(context, text)"
-       "        val normalized = normalize(resolved?.name ?: text)"
-       "        if (nonWeaponTerms.any { normalized.contains(it) }) return false"
-       "        return weaponTerms.any { normalized.contains(it) }"
-       "    }"
-       ""
-       "    private fun normalize(value: String): String ="
-       "        value.lowercase().filter { it.isLetterOrDigit() }"
-       "}"
-       ))
-    (kotlin-file-lines "com/sfb/ssdreview/Guessing.kt"
-      (
-       "package com.sfb.ssdreview"
-       ""
-       "import android.content.Context"
-       ""
-       "object Guessing {"
-       "    fun applyInitialGuesses(context: Context, session: SsdSession) {"
-       "        val race = raceKey(session.sourceName + \" \" + session.sourceUri)"
-       "        session.groups.forEach { group ->"
-       "            if (!shouldReplaceLabel(group)) return@forEach"
-       "            val guess = guessForGroup(group, session.imageWidth, session.imageHeight, race)"
-       "            val boxType = BoxTypes.resolve(context, guess.boxType)"
-       "            group.label = \"${guess.label}?\""
-       "            group.boxTypeId = boxType?.id ?: group.boxTypeId"
-       "            group.notes = \"Guess: ${guess.label}, ${guess.reason}\""
-       "        }"
-       "    }"
-       ""
-       "    private fun shouldReplaceLabel(group: SsdGroup): Boolean {"
-       "        val label = group.label.trim()"
-       "        if (group.status == \"reviewed\" || group.status == \"approved\") return false"
-       "        if (label.isBlank()) return true"
-       "        if (label.endsWith(\"?\")) return true"
-       "        return Regex(\"\"\"^\\d+\\s+boxes$\"\"\").matches(label)"
-       "    }"
-       ""
-       "    private fun guessForGroup(group: SsdGroup, imageWidth: Int, imageHeight: Int, race: String): Guess {"
-       "        val x1 = group.bbox[0]"
-       "        val y1 = group.bbox[1]"
-       "        val x2 = group.bbox[2]"
-       "        val y2 = group.bbox[3]"
-       "        val width = (x2 - x1).coerceAtLeast(1f)"
-       "        val height = (y2 - y1).coerceAtLeast(1f)"
-       "        val count = group.count"
-       "        val cx = (x1 + x2) / 2f"
-       "        val cy = (y1 + y2) / 2f"
-       "        val nearEdge = y1 < imageHeight * 0.22f ||"
-       "            y2 > imageHeight * 0.78f ||"
-       "            x1 < imageWidth * 0.16f ||"
-       "            x2 > imageWidth * 0.84f"
-       "        val lineLike = width >= height * 1.8f || height >= width * 1.8f"
-       "        val compactBank = width * height < imageWidth * imageHeight * 0.035f"
-       ""
-       "        if (count >= 3 && nearEdge && (lineLike || compactBank || count >= 5)) {"
-       "            return if (race == \"andromedan\") {"
-       "                Guess(\"pa panel\", \"PA Panel\", \"Andromedan outer-edge bank geometry\")"
-       "            } else {"
-       "                Guess("
-       "                    shieldLabel(cx / imageWidth.coerceAtLeast(1), cy / imageHeight.coerceAtLeast(1)),"
-       "                    \"Shield\","
-       "                    \"outer-edge bank geometry\""
-       "                )"
-       "            }"
-       "        }"
-       "        if (count >= 10 && cy < imageHeight * 0.34f && width >= height * 1.4f) {"
-       "            return if (race == \"andromedan\") {"
-       "                Guess(\"pa panel\", \"PA Panel\", \"upper bank geometry\")"
-       "            } else {"
-       "                Guess(\"shield-1\", \"Shield\", \"upper horizontal bank geometry\")"
-       "            }"
-       "        }"
-       "        if (count <= 4 && width * height < imageWidth * imageHeight * 0.006f) {"
-       "            if (nearEdge && count >= 2) {"
-       "                return if (race == \"andromedan\") {"
-       "                    Guess(\"pa panel\", \"PA Panel\", \"small Andromedan outer-edge bank geometry\")"
-       "                } else {"
-       "                    Guess("
-       "                        shieldLabel(cx / imageWidth.coerceAtLeast(1), cy / imageHeight.coerceAtLeast(1)),"
-       "                        \"Shield\","
-       "                        \"small outer-edge bank geometry\""
-       "                    )"
-       "                }"
-       "            }"
-       "            return Guess(if (race == \"andromedan\") \"phaser-2\" else \"phaser\", \"Phaser\", \"compact weapon-size group\")"
-       "        }"
-       "        if (count in 1..3 && cy < imageHeight * 0.34f && cx > imageWidth * 0.35f && cx < imageWidth * 0.65f) {"
-       "            return Guess(\"bridge\", \"Bridge\", \"upper center command position\")"
-       "        }"
-       "        if (count in 3..12 && cy > imageHeight * 0.32f && cy < imageHeight * 0.72f) {"
-       "            return Guess(\"hull\", \"Forward Hull\", \"central internal bank\")"
-       "        }"
-       "        return Guess(\"${count} boxes\", \"\", \"unclassified detected group\")"
-       "    }"
-       ""
-       "    private fun shieldLabel(rx: Float, ry: Float): String = when {"
-       "        ry < 0.26f -> \"shield-1\""
-       "        rx > 0.70f && ry < 0.58f -> \"shield-2\""
-       "        rx > 0.58f && ry >= 0.58f -> \"shield-3\""
-       "        ry > 0.72f -> \"shield-4\""
-       "        rx < 0.42f && ry >= 0.58f -> \"shield-5\""
-       "        rx < 0.30f -> \"shield-6\""
-       "        else -> \"shield\""
-       "    }"
-       ""
-       "    private fun raceKey(text: String): String {"
-       "        val lower = text.lowercase()"
-       "        return when {"
-       "            lower.contains(\"andromedan\") || lower.contains(\"module_c3\") || lower.contains(\"module c3\") -> \"andromedan\""
-       "            lower.contains(\"federation\") -> \"federation\""
-       "            lower.contains(\"klingon\") -> \"klingon\""
-       "            lower.contains(\"romulan\") -> \"romulan\""
-       "            lower.contains(\"gorn\") -> \"gorn\""
-       "            lower.contains(\"kzinti\") -> \"kzinti\""
-       "            lower.contains(\"lyran\") -> \"lyran\""
-       "            lower.contains(\"tholian\") -> \"tholian\""
-       "            lower.contains(\"hydran\") -> \"hydran\""
-       "            else -> \"\""
-       "        }"
-       "    }"
-       ""
-       "    private data class Guess(val label: String, val boxType: String, val reason: String)"
-       "}"
-       ""
-       ))
-    (kotlin-file-lines "com/sfb/ssdreview/MainActivity.kt"
-      (
-       "package com.sfb.ssdreview"
-       ""
-       "import android.Manifest"
-       "import android.app.Activity"
-       "import android.app.AlertDialog"
-       "import android.content.ContentUris"
-       "import android.content.Intent"
-       "import android.content.res.Configuration"
-	       "import android.content.pm.PackageManager"
-	       "import android.graphics.Bitmap"
-	       "import android.graphics.Canvas"
-	       "import android.graphics.Color"
-	       "import android.graphics.Matrix"
-	       "import android.graphics.BitmapFactory"
-	       "import android.graphics.Paint"
-	       "import android.graphics.Typeface"
-	       "import android.graphics.drawable.GradientDrawable"
-	       "import android.graphics.pdf.PdfRenderer"
-       "import android.net.Uri"
-       "import android.os.Build"
-       "import android.os.Bundle"
-       "import android.os.Environment"
-       "import android.os.ParcelFileDescriptor"
-       "import android.os.SystemClock"
-       "import android.provider.DocumentsContract"
-       "import android.provider.MediaStore"
-       "import android.provider.OpenableColumns"
-       "import android.provider.Settings"
-       "import android.text.Editable"
-       "import android.text.InputType"
-       "import android.text.TextWatcher"
-	       "import android.util.Base64"
-       "import android.view.Gravity"
-       "import android.view.HapticFeedbackConstants"
-       "import android.view.View"
-       "import android.widget.ArrayAdapter"
-       "import android.widget.AutoCompleteTextView"
-       "import android.widget.Button"
-       "import android.widget.EditText"
-       "import android.widget.LinearLayout"
-       "import android.widget.ScrollView"
-       "import android.widget.TextView"
-	       "import android.widget.Toast"
-	       "import androidx.documentfile.provider.DocumentFile"
-       "import androidx.core.content.FileProvider"
-       "import org.json.JSONArray"
-       "import org.json.JSONObject"
-       "import java.io.File"
-	       "import java.io.ByteArrayOutputStream"
-       "import java.io.PrintWriter"
-       "import java.io.StringWriter"
-       "import java.net.HttpURLConnection"
-       "import java.net.Inet4Address"
-       "import java.net.NetworkInterface"
-       "import java.net.URL"
-       "import java.util.UUID"
-       "import java.util.concurrent.atomic.AtomicReference"
-       "import kotlin.concurrent.thread"
-       "import kotlin.system.exitProcess"
-       "import kotlin.math.ceil"
-       "import kotlin.math.max"
-       "import kotlin.math.min"
-       ""
-       "class MainActivity : Activity() {"
-       "    private lateinit var truthStore: TruthStore"
-       "    private lateinit var reviewView: SsdReviewView"
-       "    private lateinit var status: TextView"
-       "    private lateinit var activityStatus: TextView"
-       ""
-       "    private var pdfUri: Uri? = null"
-       "    private var pdfName: String = \"\""
-	       "    private var pdfSha1: String = \"\""
-	       "    private var serverPdfId: String = \"\""
-       "    private var pageIndex: Int = 0"
-       "    private var pageCount: Int = 0"
-       "    private var currentBitmap: Bitmap? = null"
-	       "    private var currentSession: SsdSession? = null"
-	       "    private var selectedGroupId: String? = null"
-	       "    private var appendMode: Boolean = false"
-	       "    private var ssdAreaMode: Boolean = false"
-       "    private var selfTestMode: Boolean = false"
-       "    private var selfTestOcrMode: Boolean = false"
-       "    private var selfTestServerLabelsMode: Boolean = false"
-	       "    private var reopenSsdBrowserOnResume: Boolean = false"
-       "    @Volatile private var aiPollingJobId: String = \"\""
-       "    @Volatile private var loadGeneration: Int = 0"
-       "    @Volatile private var activeActivityId: Int = 0"
-       "    @Volatile private var pagePipelineActive: Boolean = false"
-       "    private val clientLogQueue = ArrayDeque<JSONObject>()"
-       "    private val clientLogQueueLock = Any()"
-       "    @Volatile private var clientLogWorkerRunning: Boolean = false"
-       "    private val boxTypeDisplays: List<String> by lazy { BoxTypes.load(this).map { it.display } }"
-       "    private val firingArcDisplays = listOf("
-       "        \"FA\","
-       "        \"FH\","
-       "        \"FX\","
-       "        \"RX\","
-       "        \"LS\","
-       "        \"RS\","
-       "        \"L\","
-       "        \"R\","
-       "        \"LF\","
-       "        \"RF\","
-       "        \"LR\","
-       "        \"RR\","
-       "        \"A\","
-       "        \"LA\","
-       "        \"RA\","
-       "        \"360\""
-       "    )"
-       ""
-       "    override fun onCreate(savedInstanceState: Bundle?) {"
-       "        super.onCreate(savedInstanceState)"
-       "        installClientCrashLogger()"
-       "        logClientEvent(\"ssd-app-start\")"
-       "        selfTestMode = intent.getBooleanExtra(EXTRA_SELF_TEST, false)"
-       "        selfTestOcrMode = intent.getBooleanExtra(EXTRA_SELF_TEST_OCR, false)"
-       "        selfTestServerLabelsMode = intent.getBooleanExtra(EXTRA_SELF_TEST_SERVER_LABELS, false)"
-       "        truthStore = TruthStore(this)"
-       "        refreshServerEndpointAsync(\"start\", allowLoopback = selfTestMode)"
-       "        setContentView(buildUi())"
-       "        setStatus(\"Open an SSD PDF. ${truthStore.storageSummary()}\")"
-       "        if (selfTestMode) {"
-       "            if (selfTestServerLabelsMode) loadSelfTestServerLabelsFixture() else if (selfTestOcrMode) loadSelfTestOcrFixture() else loadSelfTestFixture()"
-       "        } else {"
-       "            restoreLastPdf()"
-       "        }"
-       "        val seedActivity = beginActivity(\"Loading bundled truth seed\")"
-       "        truthStore.seedBundledTruthAsync { imported ->"
-       "            if (imported > 0) {"
-       "                runOnUiThread {"
-       "                    finishActivity(seedActivity, \"Merged truth seed: $imported files\")"
-       "                    if (selfTestMode) {"
-       "                        logSelfTestState(\"seed-merged\")"
-       "                    } else {"
-       "                        setStatus(\"Truth seed merged $imported files\")"
-       "                        if (pdfUri != null && currentBitmap == null && currentSession == null && !pagePipelineActive) renderCurrentPage()"
-       "                    }"
-       "                }"
-       "            } else {"
-       "                finishActivity(seedActivity, \"Bundled truth seed already current\")"
-       "            }"
-       "        }"
-       "    }"
-       ""
-       "    override fun onResume() {"
-       "        super.onResume()"
-       "        refreshServerEndpointAsync(\"resume\", allowLoopback = selfTestMode)"
-       "        resumePendingAiReport()"
-       "        resumePendingApkInstall()"
-       "        if (reopenSsdBrowserOnResume && hasDirectDownloadAccess()) {"
-       "            reopenSsdBrowserOnResume = false"
-       "            openPdf()"
-       "        }"
-       "    }"
-       ""
-       "    private fun refreshServerEndpointAsync(reason: String, allowLoopback: Boolean = false) {"
-       "        thread {"
-       "            try {"
-       "                truthStore.ensureReachableServerEndpoint(allowLoopback = allowLoopback)"
-       "                truthStore.flushPendingRemotePosts()"
-       "                logClientEvent(\"server-endpoint-refreshed\", JSONObject().put(\"reason\", reason))"
-       "            } catch (error: Exception) {"
-       "                logClientEvent(\"server-endpoint-refresh-failed\", JSONObject().put(\"reason\", reason).put(\"error\", throwableLog(error)))"
-       "            }"
-       "        }"
-       "    }"
-       ""
-       "    private fun findServerAsync() {"
-       "        val activity = beginActivity(\"Finding sfb-server\")"
-       "        thread {"
-       "            try {"
-       "                activityStep(activity, \"Scanning local network\")"
-       "                val info = truthStore.discoverServerEndpoint()"
-       "                truthStore.flushPendingRemotePosts()"
-       "                runOnUiThread {"
-       "                    finishActivity(activity, \"Found server\")"
-       "                    setStatus(\"Server selected: ${truthStore.remoteEndpointLabel()}\")"
-       "                    logClientEvent(\"server-discovery-succeeded\", JSONObject().put(\"info\", info))"
-       "                }"
-       "            } catch (error: Exception) {"
-       "                runOnUiThread {"
-       "                    finishActivity(activity, \"Find server failed: ${error.message}\", success = false)"
-       "                    setStatus(\"Find server failed: ${error.message}\")"
-       "                    logClientEvent(\"server-discovery-failed\", JSONObject().put(\"error\", throwableLog(error)))"
-       "                }"
-       "            }"
-       "        }"
-       "    }"
-       ""
-       "    private fun showServerConfigDialog() {"
-       "        val input = EditText(this).apply {"
-       "            inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI"
-       "            setSingleLine(true)"
-       "            setText(truthStore.remoteEndpointLabel())"
-       "            selectAll()"
-       "        }"
-       "        AlertDialog.Builder(this)"
-       "            .setTitle(\"sfb-server URL\")"
-       "            .setView(input)"
-       "            .setPositiveButton(\"Save\") { _, _ ->"
-       "                val value = input.text.toString().trim()"
-       "                val activity = beginActivity(\"Checking server URL\")"
-       "                thread {"
-       "                    try {"
-       "                        truthStore.setRemoteEndpoint(value)"
-       "                        truthStore.refreshServerEndpoint()"
-       "                        truthStore.flushPendingRemotePosts()"
-       "                        runOnUiThread {"
-       "                            finishActivity(activity, \"Server URL saved\")"
-       "                            setStatus(\"Server URL saved: ${truthStore.remoteEndpointLabel()}\")"
-       "                        }"
-       "                    } catch (error: Exception) {"
-       "                        runOnUiThread {"
-       "                            finishActivity(activity, \"Server URL failed: ${error.message}\", success = false)"
-       "                            setStatus(\"Server URL failed: ${error.message}\")"
-       "                        }"
-       "                    }"
-       "                }"
-       "            }"
-       "            .setNegativeButton(\"Cancel\", null)"
-       "            .show()"
-       "    }"
-       ""
-       "    override fun onPause() {"
-       "        super.onPause()"
-       "        persistCurrentPdfState(includeViewport = true)"
-       "    }"
-       ""
-       "    override fun onConfigurationChanged(newConfig: Configuration) {"
-       "        super.onConfigurationChanged(newConfig)"
-       "        val viewport = reviewView.captureViewport()"
-       "        reviewView.post {"
-       "            reviewView.restoreViewport(viewport)"
-       "            reviewView.invalidate()"
-       "        }"
-       "        logClientEvent(\"ssd-config-changed\", JSONObject().put(\"pipeline_active\", pagePipelineActive).put(\"generation\", loadGeneration))"
-       "    }"
-       ""
-       "    private fun loadSelfTestFixture() {"
-       "        pdfName = \"SFB SSD Self Test\""
-       "        pdfSha1 = \"ssdselftestfixture\""
-       "        pageIndex = 0"
-       "        pageCount = 1"
-       "        pdfUri = Uri.parse(\"selftest://ssd-review\")"
-       "        val bitmap = Bitmap.createBitmap(900, 1200, Bitmap.Config.ARGB_8888).apply { eraseColor(Color.WHITE) }"
-       "        val cells = mutableListOf("
-       "            selfTestCell(\"c0001\", 170f, 120f), selfTestCell(\"c0002\", 205f, 120f),"
-       "            selfTestCell(\"c0003\", 240f, 120f), selfTestCell(\"c0004\", 275f, 120f),"
-       "            selfTestCell(\"c0005\", 430f, 390f), selfTestCell(\"c0006\", 430f, 425f),"
-       "            selfTestCell(\"c0007\", 610f, 410f), selfTestCell(\"c0008\", 645f, 410f),"
-       "            selfTestCell(\"c0009\", 330f, 720f), selfTestCell(\"c0010\", 365f, 720f)"
-       "        )"
-       "        val session = SsdSession("
-       "            sessionId = \"ssd-selftest-session\","
-       "            sourceKey = SELF_TEST_SOURCE_KEY,"
-       "            sourceName = pdfName,"
-       "            sourceUri = pdfUri.toString(),"
-       "            page = 1,"
-       "            pageCount = 1,"
-       "            dpi = DPI,"
-       "            imageWidth = bitmap.width,"
-       "            imageHeight = bitmap.height,"
-       "            cells = cells,"
-       "            groups = mutableListOf("
-       "                selfTestGroup(\"g0001\", \"shield-1?\", \"26\", \"c0001\", \"c0002\", \"c0003\", \"c0004\"),"
-       "                selfTestGroup(\"g0002\", \"excess damage?\", \"13\", \"c0005\", \"c0006\"),"
-	       "                selfTestGroup(\"g0003\", \"phaser-1\", \"33\", \"c0007\", \"c0008\").apply { status = \"reviewed\" },"
-       "                selfTestGroup(\"g0004\", \"battery?\", \"18\", \"c0009\", \"c0010\"),"
-       "                selfTestGroup(\"g0005\", \"shield-1?\", \"26\", \"c0001\", \"c0002\", \"c0003\", \"c0004\")"
-       "            )"
-       "        )"
-       "        session.groups.forEach { session.recomputeGroup(it) }"
-       "        truthStore.applyTruth(session)"
-       "        currentBitmap = bitmap"
-       "        currentSession = session"
-       "        selectedGroupId = session.groups.firstOrNull()?.id"
-       "        reviewView.bitmap = bitmap"
-       "        reviewView.session = session"
-       "        reviewView.selectedGroupId = selectedGroupId"
-       "        reviewView.ssdArea = session.ssdArea"
-       "        setStatus(\"SELFTEST_READY groups=${session.groups.size} labels=${selfTestLabelCount(session)} selected=${selectedGroupId ?: \"\"}\")"
-       "        logSelfTestState(\"loaded\")"
-       "    }"
-       ""
-       "    private fun loadSelfTestOcrFixture() {"
-       "        pdfName = \"SFB SSD OCR Self Test\""
-       "        pdfSha1 = \"ssdocrselftestfixture\""
-       "        pageIndex = 0"
-       "        pageCount = 1"
-       "        pdfUri = Uri.parse(\"selftest://ssd-review-ocr\")"
-       "        val bitmap = Bitmap.createBitmap(900, 1200, Bitmap.Config.ARGB_8888).apply { eraseColor(Color.WHITE) }"
-       "        drawSelfTestOcrPage(bitmap)"
-       "        val session = SsdSession("
-       "            sessionId = \"ssd-selftest-ocr-session\","
-       "            sourceKey = sourceKey(pdfSha1, 1, DPI),"
-       "            sourceName = pdfName,"
-       "            sourceUri = pdfUri.toString(),"
-       "            page = 1,"
-       "            pageCount = 1,"
-       "            dpi = DPI,"
-       "            imageWidth = bitmap.width,"
-       "            imageHeight = bitmap.height,"
-       "            cells = mutableListOf(),"
-       "            groups = mutableListOf()"
-       "        )"
-       "        installSelfTestProposalFixture(session)"
-       "        currentBitmap = bitmap"
-       "        currentSession = session"
-       "        selectedGroupId = null"
-       "        reviewView.bitmap = bitmap"
-       "        reviewView.session = session"
-       "        reviewView.selectedGroupId = null"
-       "        reviewView.ssdArea = null"
-       "        setStatus(\"SELFTEST_OCR_READY\")"
-       "        logSelfTestState(\"ocr-loaded\")"
-       "    }"
-       ""
-       "    private fun loadSelfTestServerLabelsFixture() {"
-       "        pdfName = \"omega-1-ssd-front.pdf\""
-       "        pdfSha1 = \"ssdserverlabelsfixture\""
-       "        pageIndex = 1"
-       "        pageCount = 2"
-       "        pdfUri = Uri.parse(\"selftest://server-labels\")"
-       "        val renderGeneration = ++loadGeneration"
-       "        val bitmap = Bitmap.createBitmap(900, 1200, Bitmap.Config.ARGB_8888).apply { eraseColor(Color.WHITE) }"
-       "        val session = SsdSession("
-       "            sessionId = \"ssd-selftest-server-labels-session\","
-       "            sourceKey = sourceKey(pdfSha1, pageIndex + 1, DPI),"
-       "            sourceName = pdfName,"
-       "            sourceUri = pdfUri.toString(),"
-       "            page = pageIndex + 1,"
-       "            pageCount = pageCount,"
-       "            dpi = DPI,"
-       "            imageWidth = bitmap.width,"
-       "            imageHeight = bitmap.height,"
-       "            cells = mutableListOf(),"
-       "            groups = mutableListOf()"
-       "        )"
-       "        currentBitmap = bitmap"
-       "        currentSession = session"
-       "        selectedGroupId = null"
-       "        reviewView.bitmap = bitmap"
-       "        reviewView.session = session"
-       "        reviewView.selectedGroupId = null"
-       "        reviewView.ssdArea = null"
-       "        setStatus(\"SELFTEST_SERVER_LABELS_READY\")"
-       "        logSelfTestState(\"server-labels-loaded\")"
-       "        thread {"
-       "            try {"
-       "                truthStore.ensureReachableServerEndpoint(allowLoopback = true)"
-       "                val serverPdf = truthStore.findServerPdfByName(pdfName)"
-       "                    ?: throw IllegalStateException(\"Self-test server PDF was not found\")"
-       "                val pdfId = serverPdf.optString(\"pdf_id\", serverPdf.optString(\"id\"))"
-       "                if (pdfId.isBlank()) throw IllegalStateException(\"Self-test server PDF has no id\")"
-       "                runOnUiThread {"
-       "                    if (renderGeneration != loadGeneration) return@runOnUiThread"
-       "                    serverPdfId = pdfId"
-       "                    pdfName = serverPdf.optString(\"name\", pdfName)"
-       "                    pdfSha1 = pdfId"
-       "                    pageCount = serverPdf.optInt(\"page_count\", 1).coerceAtLeast(1)"
-       "                    pageIndex = 1.coerceAtMost(pageCount - 1)"
-       "                    pdfUri = Uri.parse(\"sfbserver:$pdfId\")"
-       "                    renderCurrentPage()"
-       "                }"
-       "            } catch (error: Exception) {"
-       "                runOnUiThread { setStatus(\"SELFTEST_SERVER_PAGE_FAILED ${error.message}\") }"
-       "            }"
-       "        }"
-       "    }"
-       ""
-       "    private fun installSelfTestProposalFixture(session: SsdSession) {"
-       "        val cells = JSONArray()"
-       "            .put(JSONObject().put(\"id\", \"pc0001\").put(\"x\", 240).put(\"y\", 238).put(\"w\", 30).put(\"h\", 30).put(\"detector\", \"self-test-proposal\"))"
-       "            .put(JSONObject().put(\"id\", \"pc0002\").put(\"x\", 276).put(\"y\", 238).put(\"w\", 30).put(\"h\", 30).put(\"detector\", \"self-test-proposal\"))"
-       "            .put(JSONObject().put(\"id\", \"pc0003\").put(\"x\", 240).put(\"y\", 274).put(\"w\", 30).put(\"h\", 30).put(\"detector\", \"self-test-proposal\"))"
-       "            .put(JSONObject().put(\"id\", \"pc0004\").put(\"x\", 276).put(\"y\", 274).put(\"w\", 30).put(\"h\", 30).put(\"detector\", \"self-test-proposal\"))"
-       "        val group = JSONObject()"
-       "            .put(\"id\", \"vg0001\")"
-       "            .put(\"label\", \"lab?\")"
-       "            .put(\"box_type_id\", \"\")"
-       "            .put(\"status\", \"candidate\")"
-       "            .put(\"count\", 4)"
-       "            .put(\"bbox\", JSONArray(listOf(240, 238, 306, 304)))"
-       "            .put(\"cell_ids\", JSONArray(listOf(\"pc0001\", \"pc0002\", \"pc0003\", \"pc0004\")))"
-       "            .put(\"proposal\", JSONObject()"
-       "                .put(\"label\", \"lab\")"
-       "                .put(\"box_type_id\", \"\")"
-       "                .put(\"confidence\", 0.98)"
-       "                .put(\"reason\", \"self-test bundled proposal\"))"
-       "            .put(\"vision_review\", JSONObject()"
-       "                .put(\"accepted\", true)"
-       "                .put(\"label\", \"lab\")"
-       "                .put(\"box_type_id\", \"\")"
-       "                .put(\"confidence\", 0.99)"
-       "                .put(\"reason\", \"self-test accepted visual proposal\"))"
-       "        val rawNoiseGroup = JSONObject()"
-       "            .put(\"id\", \"rawbad0001\")"
-       "            .put(\"label\", \"sensor?\")"
-       "            .put(\"box_type_id\", \"22\")"
-       "            .put(\"status\", \"candidate\")"
-       "            .put(\"count\", 1)"
-       "            .put(\"bbox\", JSONArray(listOf(390, 238, 430, 304)))"
-       "            .put(\"cell_ids\", JSONArray())"
-       "            .put(\"proposal\", JSONObject()"
-       "                .put(\"label\", \"sensor\")"
-       "                .put(\"box_type_id\", \"22\")"
-       "                .put(\"confidence\", 0.99)"
-       "                .put(\"reason\", \"self-test unreviewed raw proposal must be hidden\"))"
-       "        val proposal = JSONObject()"
-       "            .put(\"schema_version\", 1)"
-       "            .put(\"proposal_schema\", \"ssd-proposal-v1\")"
-       "            .put(\"source_key\", session.sourceKey)"
-       "            .put(\"source\", JSONObject()"
-       "                .put(\"kind\", \"pdf\")"
-       "                .put(\"path\", session.sourceUri)"
-       "                .put(\"name\", session.sourceName)"
-       "                .put(\"page\", session.page)"
-       "                .put(\"page_count\", session.pageCount)"
-       "                .put(\"dpi\", session.dpi))"
-       "            .put(\"image\", JSONObject().put(\"width\", session.imageWidth).put(\"height\", session.imageHeight))"
-       "            .put(\"cells\", cells)"
-       "            .put(\"groups\", JSONArray().put(group).put(rawNoiseGroup))"
-       "        truthStore.saveProposalSnapshotForSelfTest(proposal)"
-       "    }"
-       ""
-       "    private fun drawSelfTestOcrPage(bitmap: Bitmap) {"
-       "        val canvas = Canvas(bitmap)"
-       "        val fill = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-       "            style = Paint.Style.FILL"
-       "            color = Color.rgb(238, 206, 96)"
-       "        }"
-       "        val border = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-       "            style = Paint.Style.STROKE"
-       "            strokeWidth = 3.5f"
-       "            color = Color.rgb(20, 20, 20)"
-       "        }"
-       "        val text = Paint(Paint.ANTI_ALIAS_FLAG).apply {"
-       "            color = Color.BLACK"
-       "            textSize = 52f"
-       "            typeface = Typeface.DEFAULT_BOLD"
-       "        }"
-       "        fun cell(x: Float, y: Float) {"
-       "            canvas.drawRect(x, y, x + 30f, y + 30f, fill)"
-       "            canvas.drawRect(x, y, x + 30f, y + 30f, border)"
-       "        }"
-       "        fun label(value: String, x: Float, y: Float) {"
-       "            canvas.drawText(value, x, y, text)"
-       "        }"
-       "        label(\"LAB\", 235f, 220f)"
-       "        cell(240f, 238f); cell(276f, 238f); cell(240f, 274f); cell(276f, 274f)"
-       "        label(\"TRAN\", 545f, 410f)"
-       "        cell(560f, 430f); cell(560f, 466f)"
-       "        label(\"PH-1\", 250f, 700f)"
-       "        cell(258f, 720f); cell(294f, 720f)"
-       "    }"
-       ""
-       "    private fun selfTestCell(id: String, x: Float, y: Float): SsdCell ="
-       "        SsdCell(id = id, x = x, y = y, w = 28f, h = 28f, detector = \"self-test\")"
-       ""
-       "    private fun selfTestGroup(id: String, label: String, boxTypeId: String, vararg cellIds: String): SsdGroup ="
-       "        SsdGroup(id = id, label = label, boxTypeId = boxTypeId, status = \"candidate\", cellIds = cellIds.toMutableList(), notes = \"Self-test fixture\")"
-       ""
-       "    private fun selfTestLabelCount(session: SsdSession): Int ="
-       "        session.groups.count { it.label.isNotBlank() }"
-       ""
-       "    private fun truthRawGroupCount(truth: JSONObject?): Int ="
-       "        truth?.optJSONArray(\"groups\")?.length() ?: 0"
-       ""
-       "    private fun truthSuppressedGroupCount(truth: JSONObject?): Int ="
-       "        truth?.optJSONArray(\"suppressed_groups\")?.length() ?: 0"
-       ""
-       "    private fun truthUsefulGroupCount(truth: JSONObject?): Int {"
-       "        val groups = truth?.optJSONArray(\"groups\") ?: return 0"
-       "        var count = 0"
-       "        for (i in 0 until groups.length()) {"
-       "            val group = groups.optJSONObject(i) ?: continue"
-       "            val label = group.optString(\"label\").trim()"
-       "            val status = group.optString(\"status\").trim().lowercase()"
-       "            if (label.isBlank() || label.endsWith(\"?\")) continue"
-       "            if (status == \"reviewed\" || status == \"approved\") count += 1"
-       "        }"
-       "        return count"
-       "    }"
-       ""
-       "    private fun logSelfTestState(stage: String) {"
-       "        if (!selfTestMode) return"
-       "        val session = currentSession ?: return"
-       "        val labels = session.groups.joinToString(\"|\") { \"${it.id}:${it.label}:${it.status}:${it.boxTypeId}\" }"
-       "        val ocrText = session.ocrWords.joinToString(\" \") { it.text }.take(300)"
-       "        logClientEvent(\"ssd-selftest-state\", JSONObject()"
-       "            .put(\"stage\", stage)"
-       "            .put(\"groups\", session.groups.size)"
-       "            .put(\"label_count\", selfTestLabelCount(session))"
-       "            .put(\"question_label_count\", session.groups.count { it.label.trim().endsWith(\"?\") })"
-       "            .put(\"reviewed_count\", session.groups.count { it.status == \"reviewed\" || it.status == \"approved\" })"
-       "            .put(\"ocr_word_count\", session.ocrWords.size)"
-       "            .put(\"ocr_group_count\", session.groups.count { it.notes.contains(\"OCR:\") })"
-       "            .put(\"proposal_group_count\", session.groups.count { it.notes.startsWith(\"Proposal\") || it.notes.startsWith(\"AI proposal\") })"
-       "            .put(\"ocr_text\", ocrText)"
-       "            .put(\"selected\", selectedGroupId ?: \"\")"
-       "            .put(\"select_mode\", reviewView.selectMode)"
-       "            .put(\"append_mode\", appendMode)"
-       "            .put(\"ssd_area_mode\", ssdAreaMode)"
-       "            .put(\"viewport_scale\", reviewView.captureViewport()?.scale ?: 0f)"
-       "            .put(\"viewport_offset_x\", reviewView.captureViewport()?.offsetX ?: 0f)"
-       "            .put(\"viewport_offset_y\", reviewView.captureViewport()?.offsetY ?: 0f)"
-       "            .put(\"viewport_rotation\", reviewView.captureViewport()?.rotationDegrees ?: 0)"
-       "            .put(\"manual_grid_horizontal\", manualCellGridDimensions(floatArrayOf(0f, 0f, 160f, 40f), 4).let { \"${it.first}x${it.second}\" })"
-       "            .put(\"manual_grid_vertical\", manualCellGridDimensions(floatArrayOf(0f, 0f, 40f, 160f), 4).let { \"${it.first}x${it.second}\" })"
-       "            .put(\"manual_grid_square\", manualCellGridDimensions(floatArrayOf(0f, 0f, 80f, 80f), 4).let { \"${it.first}x${it.second}\" })"
-       "            .put(\"bitmap_nonwhite_samples\", selfTestBitmapNonwhiteSamples())"
-       "            .put(\"labels\", labels))"
-       "    }"
-       ""
-       "    private fun selfTestBitmapNonwhiteSamples(): Int {"
-       "        val bitmap = currentBitmap ?: return 0"
-       "        val stepX = max(1, bitmap.width / 40)"
-       "        val stepY = max(1, bitmap.height / 40)"
-       "        var count = 0"
-       "        var y = 0"
-       "        while (y < bitmap.height) {"
-       "            var x = 0"
-       "            while (x < bitmap.width) {"
-       "                val pixel = bitmap.getPixel(x, y)"
-       "                if (Color.red(pixel) < 245 || Color.green(pixel) < 245 || Color.blue(pixel) < 245) count += 1"
-       "                x += stepX"
-       "            }"
-       "            y += stepY"
-       "        }"
-       "        return count"
-       "    }"
-       ""
-       "    private fun buildUi(): View {"
-       "        val root = LinearLayout(this).apply {"
-       "            orientation = LinearLayout.VERTICAL"
-       "            setPadding(12, 12, 12, 12)"
-       "        }"
-       "        status = TextView(this).apply {"
-       "            textSize = 14f"
-       "            setPadding(8, 8, 8, 10)"
-       "        }"
-       "        root.addView(status)"
-       "        activityStatus = TextView(this).apply {"
-       "            textSize = 13f"
-       "            setTextColor(Color.rgb(15, 23, 42))"
-       "            setPadding(10, 7, 10, 7)"
-       "            visibility = View.GONE"
-       "            setBackgroundColor(Color.rgb(226, 232, 240))"
-       "        }"
-       "        root.addView(activityStatus, LinearLayout.LayoutParams("
-       "            LinearLayout.LayoutParams.MATCH_PARENT,"
-       "            LinearLayout.LayoutParams.WRAP_CONTENT"
-       "        ))"
-	       "        reviewView = SsdReviewView(this).apply {"
-	       "            onStatus = { setStatus(it) }"
-	       "            onAreaSelected = { rect, ids -> handleAreaSelected(rect, ids) }"
-	       "            onGroupTapped = { selectGroup(it) }"
-	       "        }"
-       "        root.addView(reviewView, LinearLayout.LayoutParams("
-       "            LinearLayout.LayoutParams.MATCH_PARENT,"
-       "            0,"
-       "            1f"
-       "        ))"
-       "        val controls = LinearLayout(this).apply {"
-       "            orientation = LinearLayout.VERTICAL"
-	       "            addView(actionRow("
-	       "                button(\"Open SSDs\", ButtonKind.PRIMARY) { openPdf() },"
-	       "                button(\"Server SSDs\", ButtonKind.ACCENT) { openServerPdfs() },"
-	       "                button(\"Detect\", ButtonKind.ACCENT) { detectCurrent(forceDetect = true) },"
-	       "                button(\"Set SSD Area\", ButtonKind.MODE) { startSsdAreaMode() },"
-	       "            ))"
-	       "            addView(actionRow("
-	       "                button(\"Open Any\") { openAnyPdf() },"
-	       "                button(\"Rotate\") { rotatePage() },"
-	       "                button(\"Fit\") { reviewView.fitImage() },"
-	       "            ))"
-	       "            addView(actionRow("
-	       "                button(\"Prev Page\") { changePage(-1) },"
-	       "                button(\"Page #\") { showPageDialog() },"
-	       "                button(\"Next Page\") { changePage(1) },"
-	       "            ))"
-	       "            addView(actionRow("
-	       "                button(\"Pan\", ButtonKind.NEUTRAL) { setSelectMode(false) },"
-	       "                button(\"Select Area\", ButtonKind.PRIMARY) { setSelectMode(true) },"
-	       "                button(\"Add\", ButtonKind.ACCENT) { startAppendMode() },"
-	       "            ))"
-	       "            addView(actionRow("
-	       "                button(\"Prev\", ButtonKind.NAV) { selectRelativeGroup(-1) },"
-	       "                button(\"Next\", ButtonKind.PRIMARY) { selectRelativeGroup(1) },"
-	       "                button(\"Edit\") { editSelectedGroup() },"
-	       "            ))"
-	       "            addView(actionRow("
-	       "                button(\"Approve\", ButtonKind.ACCENT) { approveSelectedGroup() },"
-	       "                button(\"Delete\", ButtonKind.DANGER) { deleteSelectedGroup() },"
-	       "                button(\"Save\") { saveTruth() },"
-	       "            ))"
-	       "            addView(actionRow("
-	       "                button(\"Send Display to AI\", ButtonKind.PRIMARY) { showAiIssueDialog() },"
-	       "            ))"
-       "            addView(actionRow("
-       "                button(\"Pull Truth\") { pullTruthDump() },"
-       "                button(\"Push Truth\") { pushTruthDump() },"
-       "                button(\"Find Server\") { findServerAsync() },"
-       "                button(\"Server URL\") { showServerConfigDialog() },"
-       "                button(\"Sync Folder\") { syncTruthFolder() },"
-       "            ))"
-	       "        }"
-       "        root.addView(ScrollView(this).apply {"
-       "            isFillViewport = false"
-       "            addView(controls)"
-       "        }, LinearLayout.LayoutParams("
-       "            LinearLayout.LayoutParams.MATCH_PARENT,"
-       "            LinearLayout.LayoutParams.WRAP_CONTENT"
-       "        ))"
-       "        return root"
-       "    }"
-       ""
-	       "    private enum class ButtonKind(val backgroundColor: Int, val foregroundColor: Int, val textSp: Float, val heightDp: Int) {"
-	       "        PRIMARY(Color.rgb(37, 99, 235), Color.WHITE, 18f, 56),"
-	       "        ACCENT(Color.rgb(5, 150, 105), Color.WHITE, 18f, 56),"
-	       "        MODE(Color.rgb(245, 158, 11), Color.rgb(15, 23, 42), 17f, 54),"
-	       "        NAV(Color.rgb(51, 65, 85), Color.WHITE, 17f, 52),"
-	       "        NEUTRAL(Color.rgb(226, 232, 240), Color.rgb(15, 23, 42), 15f, 48),"
-	       "        DANGER(Color.rgb(185, 28, 28), Color.WHITE, 16f, 50)"
-	       "    }"
-	       ""
-	       "    private fun dp(value: Int): Int = (value * resources.displayMetrics.density + 0.5f).toInt()"
-	       ""
-	       "    private fun button(label: String, kind: ButtonKind = ButtonKind.NEUTRAL, action: () -> Unit): Button ="
-	       "        Button(this).apply {"
-	       "            text = label"
-	       "            textSize = kind.textSp"
-	       "            minHeight = dp(kind.heightDp)"
-	       "            setTextColor(kind.foregroundColor)"
-	       "            setAllCaps(false)"
-	       "            setPadding(dp(10), dp(6), dp(10), dp(6))"
-	       "            background = GradientDrawable().apply {"
-	       "                cornerRadius = dp(9).toFloat()"
-	       "                setColor(kind.backgroundColor)"
-	       "            }"
-	       "            setOnClickListener { action() }"
-	       "        }"
-       ""
-       "    private fun actionRow(vararg buttons: Button): LinearLayout ="
-       "        LinearLayout(this).apply {"
-       "            orientation = LinearLayout.HORIZONTAL"
-       "            gravity = Gravity.CENTER_VERTICAL"
-	       "            buttons.forEach { item ->"
-	       "                val params = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)"
-	       "                params.setMargins(dp(3), dp(3), dp(3), dp(3))"
-	       "                addView(item, params)"
-	       "            }"
-	       "        }"
-       ""
-       "    private fun boxTypeInput(showDropdownOnFocus: Boolean = true): AutoCompleteTextView ="
-       "        AutoCompleteTextView(this).apply {"
-       "            inputType = InputType.TYPE_CLASS_TEXT"
-       "            threshold = 1"
-       "            setAdapter(ArrayAdapter(this@MainActivity, android.R.layout.simple_dropdown_item_1line, boxTypeDisplays))"
-       "            setOnClickListener { showDropDown() }"
-       "            setOnFocusChangeListener { _, hasFocus -> if (showDropdownOnFocus && hasFocus) post { showDropDown() } }"
-       "        }"
-       ""
-       "    private fun firingArcInput(): AutoCompleteTextView ="
-       "        AutoCompleteTextView(this).apply {"
-       "            hint = \"Firing arc, e.g. FA/FH/LS/RS\""
-       "            inputType = InputType.TYPE_CLASS_TEXT"
-       "            threshold = 0"
-       "            setAdapter(ArrayAdapter(this@MainActivity, android.R.layout.simple_dropdown_item_1line, firingArcDisplays))"
-       "            setOnClickListener { showDropDown() }"
-       "            setOnFocusChangeListener { _, hasFocus -> if (hasFocus) post { showDropDown() } }"
-       "        }"
-       ""
-       "    private fun wireBoxTypeDialogBehavior("
-       "        label: EditText,"
-       "        boxType: AutoCompleteTextView,"
-       "        firingArc: AutoCompleteTextView,"