Files
ai-app/app/androidApp/src/main/kotlin/com/example/aiapp/PeerMessage.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

142 lines
5.9 KiB
Kotlin

package com.example.aiapp
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
/**
* A message another agent sent this session, closed until somebody asks.
*
* Closed by default, like a tool call and for the same reason: these are long, there can be several
* in a row, and what a reader scanning the transcript needs from one is that it happened and who
* sent it. The first line comes with the heading because a name alone does not say which message
* this was.
*
* Drawn as its own kind rather than as the reader's own bubble. They did not say this, and a
* transcript that puts it in their voice is making a claim about who asked for the work that
* follows.
*
* Opened, the card is drawn in *pieces* -- this heading and one [PeerBlockRow] per markdown block,
* each its own item of the transcript list. See [TranscriptUnit.PeerHead] for what that bought;
* what matters here is that the pieces have to add up to the card that was there before, so the
* fill, the corner radius and the padding all live in [peerSurface].
*/
@Composable
fun PeerHeadRow(
item: TranscriptItem.PeerNote,
open: Boolean,
onToggle: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier.cardPiece(
top = true,
bottom = !open,
fill = CardDefaults.cardColors().containerColor,
onPress = onToggle,
)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Message from ${item.from}", style = MaterialTheme.typography.titleSmall)
if (!open) {
Spacer(Modifier.width(8.dp))
Text(
item.text.lineSequence().firstOrNull { it.isNotBlank() }.orEmpty(),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
// The head, not the tail: a message is identified by how it opens.
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
/**
* One block of an opened peer message, on the same card the heading started.
*
* Clickable like the heading, so the card still shuts wherever it is pressed -- it was one control
* before it was several items, and which piece the finger lands on is not something the reader
* chose.
*/
@Composable
fun PeerBlockRow(unit: TranscriptUnit.PeerBlock, replies: ParsedReplies, onToggle: () -> Unit) {
Column(
Modifier.cardPiece(
top = false,
bottom = unit.last,
fill = CardDefaults.cardColors().containerColor,
onPress = onToggle,
)
) {
// The words shut the card too, and have to do it themselves -- see [LocalMarkdownTap].
// Without this the card closes everywhere except on the text, which is most of it.
CompositionLocalProvider(LocalMarkdownTap provides rememberMarkdownTap(onToggle)) {
// The gap the card's own column used to provide between its heading and its prose, and
// between one block and the next -- inside the piece, so the card's fill runs through
// it.
MarkdownPiece(unit.text, unit.piece, replies, Modifier.padding(top = unit.spacing))
}
}
}
/**
* One piece of a card drawn in slices: the fill, the corners it owns, and the room inside it.
*
* A filled Material card is elevation zero, so there is no shadow that a seam would show through --
* which is the whole reason a card can be cut up at all. Each piece paints the caller's container
* colour and rounds only the corners at the ends of the message, so the pieces abut into one
* continuous card. Shared by the two rows cut this way -- an opened peer message and a long user
* message -- because two copies of the corner logic is how one of them grows a seam.
*
* The padding is the other half: 12dp all round was the card's own, so the top piece keeps the top
* of it, the bottom piece the bottom, and the middle pieces neither.
*/
@Composable
fun Modifier.cardPiece(
top: Boolean,
bottom: Boolean,
fill: Color,
onPress: (() -> Unit)? = null,
): Modifier {
val square = CornerSize(0.dp)
val shape =
MaterialTheme.shapes.medium.copy(
topStart = if (top) MaterialTheme.shapes.medium.topStart else square,
topEnd = if (top) MaterialTheme.shapes.medium.topEnd else square,
bottomStart = if (bottom) MaterialTheme.shapes.medium.bottomStart else square,
bottomEnd = if (bottom) MaterialTheme.shapes.medium.bottomEnd else square,
)
return fillMaxWidth()
.clip(shape)
.background(fill)
.then(if (onPress == null) Modifier else Modifier.clickable(onClick = onPress))
.padding(
start = CARD_PADDING,
end = CARD_PADDING,
top = if (top) CARD_PADDING else 0.dp,
bottom = if (bottom) CARD_PADDING else 0.dp,
)
}
/** The room inside a sliced card, which was `Card { Column(padding(12.dp)) }`. */
private val CARD_PADDING = 12.dp