Move SSD session count formatting to typed Kotlin

ober

cdf0f9c96d3b8642041ce39e9db69c35ecf4850a

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index b07a160..6861398 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3576,7 +3576,7 @@
                 isGenericGuessLabel isSsdPdfName
                 sourceBaseName canonicalSourceName isUsefulGuessLabel isErrorStatus
                 formatActivityDuration groupDisplayLabel appendModeStatus
-                selectedGroupStatus)
+                selectedGroupStatus sessionCountsStatus rotatedPageStatus)
         (def (normalizeFiringArc (value : String)) : String
           (string-uppercase (string-trim value)))
         (def (normalizeLabel (value : String)) : String
@@ -3712,6 +3712,20 @@
                           (string-append
                             ", "
                             (string-append (SsdGroup-status group) ")")))))))))))
+        (def (sessionCountsStatus (session : SsdSession) (suffix : String)) : String
+          (string-append
+            (int32->string (list-size (SsdSession-cells session)))
+            (string-append
+              " cells, "
+              (string-append
+                (int32->string (list-size (SsdSession-groups session)))
+                (string-append " groups" suffix)))))
+        (def (rotatedPageStatus (session : (Nullable SsdSession))) : String
+          (if (nullable-null? session)
+            "Rotated page"
+            (string-append
+              "Rotated page; "
+              (sessionCountsStatus (nullable-get session) ""))))
         (def (formatActivityDuration (ms : Int)) : String
           (let ((safe (if (< ms (int 0)) (int 0) ms)))
             (if (< safe (int 1000))
@@ -4935,8 +4949,7 @@
        "        loadGeneration += 1"
        "        reviewView.rotateClockwise()"
        "        val session = currentSession"
-       "        val suffix = if (session != null) \"; ${session.cells.size} cells, ${session.groups.size} groups\" else \"\""
-       "        setStatus(\"Rotated page$suffix\")"
+       "        setStatus(rotatedPageStatus(session))"
        "    }"
        ""
        "    private fun showPageDialog() {"
@@ -5117,7 +5130,7 @@
 	       "                        reviewView.session = session"
 	       "                        reviewView.selectedGroupId = null"
 	       "                        reviewView.ssdArea = session.ssdArea"
-	       "                        setStatus(\"${session.cells.size} cells, ${session.groups.size} groups, replayed cached truth\")"
+	       "                        setStatus(sessionCountsStatus(session, \", replayed cached truth\"))"
 	       "                        logDetectResult(\"cached-truth\", session, \"local-truth\")"
        "                    }"
        "                    truthStore.pullRemoteTruth(session)"
@@ -5171,7 +5184,7 @@
        "                    val truthNote = if (existingTruth != null) \", replayed truth\" else \"\""
        "                    val ocrNote = if (ocrGuesses > 0) \", $ocrGuesses OCR guesses\" else if (session.ocrWords.isNotEmpty()) \", OCR ${session.ocrWords.size} words\" else \"\""
        "                    val learnedNote = if (learnedGuesses > 0) \", $learnedGuesses learned guesses\" else \"\""
-       "                    setStatus(\"${session.cells.size} cells, ${session.groups.size} groups$detectorNote$truthNote$ocrNote$learnedNote\")"
+       "                    setStatus(sessionCountsStatus(session, detectorNote + truthNote + ocrNote + learnedNote))"
        "                    logDetectResult(\"page-load\", session, detectorSource, learnedGuesses, ocrGuesses)"
        "                    if (existingTruth == null && !forceDetect) mergeRemoteTruthAsync(session, expectedGeneration, uri, expectedPageIndex)"
        "                }"
@@ -5217,7 +5230,7 @@
        "                reviewView.selectedGroupId = null"
        "                reviewView.ssdArea = merged.ssdArea"
        "                reviewView.invalidate()"
-       "                setStatus(\"${merged.cells.size} cells, ${merged.groups.size} groups, pulled remote truth\")"
+       "                setStatus(sessionCountsStatus(merged, \", pulled remote truth\"))"
        "                logDetectResult(\"remote-truth\", merged, \"remote-truth\")"
        "            }"
        "        }"