Voice chips follow the saved report's voice in saved-report view
Opening a recording whose live summary row isn't loaded fell through to sumTone (null = Neutral), so the pill showed Neutral while the report text on screen read "*Voice: funny*". chipTone now reads the report's own *Voice: X* metadata line as the fallback, so the chips can never contradict the artifact being displayed. Running job and live summary rows still take precedence. 3 new tests (reportVoice).
This commit is contained in:
parent
1a6acd92d6
commit
70483b4cf8
3 changed files with 38 additions and 0 deletions
|
|
@ -1957,6 +1957,21 @@ class DesktopState(private val appDir: File = defaultAppDir()) {
|
||||||
listOf(uploadStage, tStage, sStage, fStage), elapsed, eta)
|
listOf(uploadStage, tStage, sStage, fStage), elapsed, eta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Voice recorded inside a saved .transcript.md report — the
|
||||||
|
* `*Voice: X*` metadata line written by Reports.renderReport.
|
||||||
|
* Used when the live summary row isn't loaded (saved-report
|
||||||
|
* view) so the voice chips can't contradict the report on
|
||||||
|
* screen. "neutral" (or absent) maps to null. Pure — tested. */
|
||||||
|
fun reportVoice(reportText: String?): String? {
|
||||||
|
reportText ?: return null
|
||||||
|
val line = reportText.lineSequence().firstOrNull {
|
||||||
|
it.trim().startsWith("*Voice:")
|
||||||
|
} ?: return null
|
||||||
|
val v = line.trim().removePrefix("*Voice:").removeSuffix("*")
|
||||||
|
.trim()
|
||||||
|
return v.takeIf { it.isNotBlank() && !it.equals("neutral", true) }
|
||||||
|
}
|
||||||
|
|
||||||
const val KEY_URL = "server.url"
|
const val KEY_URL = "server.url"
|
||||||
const val KEY_FOLDER = "library.folder"
|
const val KEY_FOLDER = "library.folder"
|
||||||
const val KEY_PASSWORD = "local.password"
|
const val KEY_PASSWORD = "local.password"
|
||||||
|
|
|
||||||
|
|
@ -683,6 +683,11 @@ fun DetailScreen(state: DesktopState) {
|
||||||
val chipTone: String? = when {
|
val chipTone: String? = when {
|
||||||
sJobNow != null && sJobNow.status in setOf("queued", "running") -> sJobNow.tone
|
sJobNow != null && sJobNow.status in setOf("queued", "running") -> sJobNow.tone
|
||||||
sSummary != null -> sSummary.tone
|
sSummary != null -> sSummary.tone
|
||||||
|
// Live summary row unavailable (saved-report view): read the
|
||||||
|
// voice out of the report itself so the chips can't contradict
|
||||||
|
// the "*Voice: funny*" text on screen (user: report said funny,
|
||||||
|
// Neutral pill was highlighted).
|
||||||
|
detail.reportText != null -> DesktopState.reportVoice(detail.reportText)
|
||||||
else -> sumTone
|
else -> sumTone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,4 +189,22 @@ class JobProgressTest {
|
||||||
assertNull(DesktopState.etaFrom(300, 99)) // no promising the last token
|
assertNull(DesktopState.etaFrom(300, 99)) // no promising the last token
|
||||||
assertEquals(25L, DesktopState.etaFrom(100, 80))
|
assertEquals(25L, DesktopState.etaFrom(100, 80))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- reportVoice: chips follow the saved report's own voice ----
|
||||||
|
|
||||||
|
@Test fun `reportVoice reads the metadata line`() {
|
||||||
|
val md = "# X\n## Summary\n*Voice: funny*\n\ntext"
|
||||||
|
assertEquals("funny", DesktopState.reportVoice(md))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `reportVoice neutral and absent map to null`() {
|
||||||
|
assertNull(DesktopState.reportVoice("# X\n*Voice: neutral*\n"))
|
||||||
|
assertNull(DesktopState.reportVoice("# X\nno voice line\n"))
|
||||||
|
assertNull(DesktopState.reportVoice(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `reportVoice keeps multi-word tones intact`() {
|
||||||
|
assertEquals("dry, witty",
|
||||||
|
DesktopState.reportVoice("*Voice: dry, witty*"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue