Files
ai-app/app/androidApp/src/main/kotlin/com/example/aiapp/CrashLog.kt
T
irisandClaude Opus 5 edc39c7371 Thin the app's comments
The same pass the server had, on the Kotlin side: comments restating what
the code says are gone, and the ones recording a measurement, a constraint
or an incident are kept but cut to a few lines each. 6540 comment lines to
5674, and 920 lines off the app.

Two doc comments had drifted onto the item above the one they describe --
`contextAfter`'s onto `sessionWorking` in Events.kt, and `UsageMonitor`'s
equivalent on the server was fixed in the previous commit. Each is back on
its own item, which is the only non-comment line this diff moves.

The comments are reflowed to the column limit at their own indentation:
several were written wide, and ktfmt re-wrapped them into lines holding a
single orphan word. `/tmp` script, not kept -- ktfmt is idempotent over the
result, which is the check.

Left alone deliberately: this codebase's remaining comment density is high
because the comments carry things the code cannot say -- what a null means,
what a number was measured against, which bug a guard exists for. Of the
238 one-line doc comments in the app, five were pure restatement of the
name and were removed; the rest each say something the signature does not.

ktfmtFormat, compileDebugKotlin, lintDebug and testDebugUnitTest pass;
cargo test (127), clippy --all-targets and fmt still clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:20:16 -04:00

64 lines
2.6 KiB
Kotlin

package com.example.aiapp
import android.content.Context
import java.io.File
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* The last crash, kept so the debug button can hand it over.
*
* The alternative is asking somebody to reproduce a crash with the phone plugged into a computer
* and `logcat` running, which is the one thing nobody has set up at the moment it happens. This
* costs one file write on a process that is already dying, and it turns "it crashes when I open
* that chat" into the frame it crashed in.
*
* Kept until it is read rather than cleared on the next launch: the app restarts before anybody can
* ask about it, so a log that lives for one session is a log that is never read.
*/
private const val CRASH_FILE = "last-crash.txt"
/**
* How much of a stack is kept.
*
* This is pasted into a conversation, so it has a budget like any other output written for a
* reader. The top of a stack is what identifies a crash and the bottom is framework plumbing.
*/
private const val CRASH_LIMIT = 4000
/**
* Records uncaught exceptions, then lets the platform do what it was going to do.
*
* Chained rather than replacing: the default handler is what shows the "app has stopped" dialog and
* ends the process, and an app that swallows that instead sits there in an unknown state.
*/
fun installCrashLog(context: Context) {
val app = context.applicationContext
val previous = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, error ->
runCatching { File(app.filesDir, CRASH_FILE).writeText(describe(thread, error)) }
previous?.uncaughtException(thread, error)
}
}
private fun describe(thread: Thread, error: Throwable): String {
val when_ = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(Date())
val stack = StringWriter().also { error.printStackTrace(PrintWriter(it)) }.toString()
val kept =
if (stack.length <= CRASH_LIMIT) stack
else stack.take(CRASH_LIMIT) + "\n ... ${stack.length - CRASH_LIMIT} more characters"
return "$when_ on thread ${thread.name}\n$kept"
}
/** The last crash, or null if there has not been one since it was last read. */
fun lastCrash(context: Context): String? =
File(context.applicationContext.filesDir, CRASH_FILE).takeIf { it.exists() }?.readText()
/** Forgets the last crash, once somebody has taken a copy of it. */
fun clearCrash(context: Context) {
File(context.applicationContext.filesDir, CRASH_FILE).delete()
}