Move SSD JSONL line normalization to typed Kotlin

ober

d75f09c98abe108c5c34f03843d2481c1e37b609

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 79730dd..3c77b6c 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3877,6 +3877,7 @@
                 shouldIncludeSsdPdfChoiceName shouldIncludeSsdPdfFile
                 shouldStopSsdPdfTreeScan ssdPdfDocumentIsDirectory
                 shouldIncludeSsdPdfDocument
+                trimmedNonBlankLines
                 sourceBaseName canonicalSourceName isUsefulGuessLabel isErrorStatus
                 formatActivityDuration groupDisplayLabel appendModeStatus
                 selectedGroupStatus sessionCountsStatus rotatedPageStatus
@@ -3947,6 +3948,8 @@
           (kotlin-member-get isDirectory))
         (extern (documentFileIsFile (file : DocumentFile)) : Bool
           (kotlin-member-get isFile))
+        (extern (stringLines (text : String)) : (List String)
+          (kotlin-member-call lines))
         (extern (androidColorRgb (red : Int32)
                                  (green : Int32)
                                  (blue : Int32)) : Int32
@@ -4063,6 +4066,16 @@
                                           (limit : Int32)) : Bool
           (and (documentFileIsFile file)
                (shouldIncludeSsdPdfChoiceName name currentChoices limit)))
+        (def (trimmedNonBlankLines (text : String)) : (MutableList String)
+          (let ((lines (stringLines text)))
+            (for/fold ((out (mutable-list-empty String)))
+                      ((i (in-range (int32 0) (list-size lines))))
+              (let ((line (string-trim (list-ref lines i))))
+                (if (string-blank? line)
+                    out
+                    (begin
+                      (mutable-list-add! out line)
+                      out))))))
         (def (sourceBaseName (value : String)) : String
           (string-lowercase
             (string-replace-regex
@@ -8284,7 +8297,7 @@
        "    private fun validateImportedText(name: String, text: String) {"
        "        if (name.endsWith(\".jsonl\")) {"
        "            var lines = 0"
-       "            text.lineSequence().map { it.trim() }.filter { it.isNotBlank() }.forEach { line ->"
+       "            trimmedNonBlankLines(text).forEach { line ->"
        "                lines += 1"
        "                if (lines > MAX_JSONL_LINES) throw IllegalStateException(\"JSONL line-count limit exceeded\")"
        "                jsonObjectFromText(line)"
@@ -8493,9 +8506,9 @@
        "        validateImportedText(file.name, remoteText)"
        "        val lines = linkedSetOf<String>()"
        "        if (fileExists(file)) {"
-       "            readLocalText(file).lineSequence().map { it.trim() }.filter { it.isNotBlank() }.forEach { lines.add(it) }"
+       "            trimmedNonBlankLines(readLocalText(file)).forEach { lines.add(it) }"
        "        }"
-       "        remoteText.lineSequence().map { it.trim() }.filter { it.isNotBlank() }.forEach {"
+       "        trimmedNonBlankLines(remoteText).forEach {"
        "            if (lines.size >= MAX_JSONL_LINES) throw IllegalStateException(\"JSONL line-count limit exceeded\")"
        "            lines.add(it)"
        "        }"