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>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-04 16:20:16 -04:00
1 parent 79682f03a7
commit edc39c7371
68 files changed
+2077 -2997

No files matched your search

@@ -41,13 +41,12 @@ data class QuestionAnswer(val questionId: String, val answers: List<String>)
* What the reader has settled on for one question, before any of it is sent.
*
* Held here rather than inferred from the transcript, which is what made picking an option feel
* broken: the mark used to appear only when the answer had crossed the tunnel, been recorded and
* come back as an event, so on a phone the card sat unchanged for most of a second after a tap and
* the natural response was to tap again.
* broken: the mark used to appear only when the answer had crossed the tunnel and come back as an
* event, so the card sat unchanged for most of a second after a tap.
*
* Picked options and typed words are one field each because they are alternatives rather than
* parts: answering in the reader's own words is the case no option covers, so typing puts the picks
* away and picking puts the words away, and there is never a draft that means two things.
* parts: typing puts the picks away and picking puts the words away, so there is never a draft that
* means two things.
*/
data class Draft(val picked: Set<String> = emptySet(), val other: String = "") {
val settled: Boolean
@@ -65,29 +64,26 @@ data class Draft(val picked: Set<String> = emptySet(), val other: String = "") {
/**
* Every question one tool call is waiting on, one at a time.
*
* All of it comes from the question events themselves -- what each option means, what picking it
* would produce, whether several may be picked at once. None of it is read out of the call's own
* All of it comes from the question events themselves. None of it is read out of the call's own
* input, which is one provider's JSON: parsing that here would put that provider's schema in the
* app, where no other provider can reach it and where it drifts the first time the schema moves.
*
* One question on screen with arrows to the others, rather than all of them stacked. A card asking
* three questions with four options and a description each is several screens tall, so the reader
* scrolls past the question they are answering to reach the button that sends it, and never sees
* the whole of any one of them. Paged, each question is a screen and the count says how many are
* left -- which is also what makes "not all of them are answered" something the reader can act on
* rather than something to go hunting for.
* scrolls past the question they are answering to reach the button that sends it. Paged, each
* question is a screen and the count says how many are left.
*
* Nothing is sent until Submit. Answering is one act even when it is several questions: the tool
* asked them together and is waiting on all of them, and sending each as it was tapped meant the
* reader could not change their mind about the first after reading the third.
* asked them together, and sending each as it was tapped meant the reader could not change their
* mind about the first after reading the third.
*/
@Composable
fun AskUserQuestionBody(
asks: List<TranscriptItem.QuestionCard>,
onAnswer: (List<QuestionAnswer>, onSettled: () -> Unit) -> Unit,
) {
// Seeded from what was already answered, so a card the reader comes back to shows their
// answers rather than an empty draft over them.
// Seeded from what was already answered, so a card the reader comes back to shows their answers
// rather than an empty draft over them.
var drafts by
remember(asks.map { it.id }) {
mutableStateOf(
@@ -125,7 +121,7 @@ fun AskUserQuestionBody(
modifier = Modifier.weight(1f),
)
// Disabled at the ends rather than absent, so the pair keeps its place and the
// reader can see that there is nothing further that way.
// reader can see there is nothing further that way.
MarkButton("Previous question", { at-- }, enabled = at > 0) {
Chevron(Pointing.Left, colour = LocalContentColor.current)
}
@@ -143,7 +139,7 @@ fun AskUserQuestionBody(
if (outstanding.isNotEmpty()) {
Spacer(Modifier.height(12.dp))
// Greyed until every question has an answer, because the tool is waiting on all of
// them: a submit that sent two of three would leave the third one asked and the card
// them: a submit that sent two of three would leave the third asked and the card
// looking dealt with.
val ready = outstanding.all { drafts[it.id]?.settled == true }
Button(
@@ -155,8 +151,8 @@ fun AskUserQuestionBody(
}
) {
// Back to a button whatever happened. A refusal is reported by the screen
// around this, and the draft is still here to send again -- a spinner
// that never stops would be the only sign of a failure this card cannot
// around this, and the draft is still here to send again -- a spinner that
// never stops would be the only sign of a failure this card cannot
// describe.
sending = false
}
@@ -165,8 +161,8 @@ fun AskUserQuestionBody(
modifier = Modifier.fillMaxWidth(),
) {
if (sending) {
// In the button rather than beside it, so the row does not change height at
// the moment it is pressed.
// In the button rather than beside it, so the row does not change height at the
// moment it is pressed.
CircularProgressIndicator(
Modifier.height(18.dp).width(18.dp),
strokeWidth = 2.dp,
@@ -186,8 +182,7 @@ fun AskUserQuestionBody(
* One question: what is being asked, what can be answered, and what was.
*
* The same body wherever a question appears -- on the call that asked it, or as a card of its own
* when nothing did. A question is the same thing either way, and two renderings of it would be two
* places for an answer to go missing.
* when nothing did. Two renderings of it would be two places for an answer to go missing.
*
* [draft] is what the reader has picked so far and [onDraft] is how they change it; nothing here
* sends anything. An answered question ignores both and draws what was answered.
@@ -214,10 +209,10 @@ fun AskedQuestion(
// replacing them with a line repeating it. The options are what the question *was*, and
// dropping them leaves an answer with nothing to have been an answer to -- "Sonnet" says
// very little without the three it was chosen over. Marked in the same purple that says
// "picked" while the question is still open, so it is one appearance learned once.
// "picked" while the question is open, so it is one appearance learned once.
val answered = ask.answers.isNotEmpty()
// What is marked: what was answered once there is an answer, and what the finger has
// chosen until then.
// What is marked: what was answered once there is an answer, and what the finger has chosen
// until then.
val marked = if (answered) ask.answers.toSet() else draft.picked
// Null once the question is answered: the options stay and stop being pressable.
val onPick: ((String) -> Unit)? =
@@ -233,9 +228,9 @@ fun AskedQuestion(
}
}
}
// What was answered in the reader's own words, which no option can mark -- see
// [OtherAnswer]. Only ever the answers that match nothing offered, so a question answered
// by picking says it by the mark alone.
// What was answered in the reader's own words, which no option can mark. Only ever the
// answers that match nothing offered, so a question answered by picking says it by the
// mark.
val inWords = ask.answers.filterNot { answer -> ask.options.any { it.label == answer } }
if (inWords.isNotEmpty()) {
Text(
@@ -252,10 +247,8 @@ fun AskedQuestion(
}
/**
* [label] added to, or taken out of, what [draft] has picked.
*
* A single-answer question replaces rather than accumulates, and either way picking puts any typed
* words away -- see [Draft].
* [label] added to, or taken out of, what [draft] has picked. A single-answer question replaces
* rather than accumulates, and either way picking puts any typed words away -- see [Draft].
*/
private fun pick(draft: Draft, label: String, multiSelect: Boolean): Draft =
when {
@@ -269,8 +262,7 @@ private fun pick(draft: Draft, label: String, multiSelect: Boolean): Draft =
*
* Outlined rather than tinted. Drawn first as a card one step up the surface ladder, it was
* indistinguishable from the card behind it -- three paragraphs of text where three things to press
* should have been, which is the failure a tint step routinely produces on a dark theme. A border
* is one cue and it is unambiguous.
* should have been. A border is one cue and it is unambiguous.
*/
@Composable
private fun OptionCard(option: QuestionOption, selected: Boolean, onPick: () -> Unit) {
@@ -324,8 +316,8 @@ private fun Preview(preview: String) {
preview,
style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace,
// Not wrapped: these are mockups and diffs, where a wrapped line reads as two lines
// of the thing being previewed.
// Not wrapped: these are mockups and diffs, where a wrapped line reads as two lines of
// the thing being previewed.
softWrap = false,
modifier = Modifier.padding(8.dp).horizontalScroll(rememberScrollState()),
)
@@ -336,8 +328,7 @@ private fun Preview(preview: String) {
* The choice the asker always leaves open, and the app has to as well.
*
* Every AskUserQuestion carries an implicit "Other" -- the reader may answer in their own words
* rather than pick. Leaving it out narrows a question that was never that narrow, and the reader
* cannot tell that it was ever open.
* rather than pick. Leaving it out narrows a question that was never that narrow.
*/
@Composable
private fun OtherAnswer(text: String, onText: (String) -> Unit) {
@@ -358,8 +349,7 @@ private fun OtherAnswer(text: String, onText: (String) -> Unit) {
*
* A Row hands out intrinsic widths in order and clips whatever runs past the edge, so a question
* with four options showed the first one or two and dropped the rest off the side of the screen.
* That does not read as a bug: it reads as those having been the only choices, which is the worst
* way for a list of choices to be wrong.
* That reads as those having been the only choices.
*/
@Composable
fun AnswerOptions(
@@ -379,8 +369,8 @@ fun AnswerOptions(
OutlinedButton(
onClick = { onPick?.invoke(option.label) },
// Disabled rather than removed, so an answered question still shows what it
// offered. Material dims a disabled button's own border and label, which would
// take the mark with it -- both are stated here instead.
// offered. Material dims a disabled button's own border and label, which would take
// the mark with it -- both are stated here instead.
enabled = onPick != null,
border =
BorderStroke(