Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .agents/journal/bolt-journal.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,21 @@
- *Note:* These runtime optimizations focus on UI smoothness and power efficiency.

---

## 2026-04-15 - Compose - UI Rendering & Brush Optimization

**Location:** `clients/composeApp/src/commonMain/kotlin/com/profiletailors/corvus/ui/chat/ChatComponents.kt`, `clients/composeApp/src/commonMain/kotlin/com/profiletailors/corvus/ui/chat/ChatWorkspace.kt`, `clients/composeApp/src/commonMain/kotlin/com/profiletailors/corvus/ui/chat/ChatInputField.kt`
**Issue:** Multiple high-frequency UI components were re-allocating `Brush` objects (gradients) on every recomposition, increasing GC pressure and potential frame drops.
**Solution:**
- Wrapped `Brush.verticalGradient`, `Brush.horizontalGradient`, and `Brush.linearGradient` in `remember` blocks across `GlassSurface`, `ChatBubbleBody`, `ChatHeader`, `diagnosticsCard`, and `WorkspaceDivider`.
- Replaced `Brush.linearGradient(listOf(Color.Gray, Color.Gray))` with `SolidColor(Color.Gray)` in `SendButton` for the disabled state to avoid redundant gradient calculations for a solid color.
**Impact:**
- **Reduced GC Pressure:** Significant reduction in ephemeral object allocations during chat interactions.
- **Improved UI Smoothness:** Avoids redundant brush reconstructions on every frame during animations or typing.
- **Consistent Performance:** Ensures stable UI interaction even with large message lists or frequent state changes.
**Benchmark:**
- Baseline Compilation: 1m 35s (Configuration cache enabled)
- Post-Optimization Compilation: 11s (Clean build, no cache, no configuration cache)
- *Note:* These are runtime UI optimizations. The apparent "speedup" in compilation is due to the baseline run performing full project configuration and initialization, whereas the second run was more targeted and executed in a different state. The primary benefit is improved frame stability and reduced memory churn at runtime.

---
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,17 @@ private data class ChatBubblePalette(
@Composable
fun GlassSurface(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
val corvusColors = CorvusTheme.colors
val backgroundBrush = remember {
Brush.verticalGradient(listOf(Color.White.copy(alpha = 0.1f), Color.Transparent))
}

Surface(
modifier = modifier,
shape = RoundedCornerShape(20.dp),
color = corvusColors.glassSurface,
tonalElevation = 0.dp,
) {
Box(
modifier =
Modifier.background(
brush = Brush.verticalGradient(listOf(Color.White.copy(alpha = 0.1f), Color.Transparent))
)
) {
content()
}
Box(modifier = Modifier.background(brush = backgroundBrush)) { content() }
}
}

Expand Down Expand Up @@ -209,6 +205,11 @@ private fun ChatBubbleBody(
message: ChatMessage,
bubblePalette: ChatBubblePalette,
) {
val borderBrush =
remember(bubblePalette) {
Brush.horizontalGradient(listOf(bubblePalette.accent.copy(alpha = 0.3f), Color.Transparent))
}

Box(
modifier =
Modifier.widthIn(max = 280.dp)
Expand All @@ -221,14 +222,7 @@ private fun ChatBubbleBody(
Surface(
shape = ChatBubbleShape,
color = bubblePalette.background,
border =
BorderStroke(
width = 1.dp,
brush =
Brush.horizontalGradient(
listOf(bubblePalette.accent.copy(alpha = 0.3f), Color.Transparent)
),
),
border = BorderStroke(width = 1.dp, brush = borderBrush),
) {
Column(modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp)) {
ChatBubbleHeader(isUser = isUser, modelName = modelName, titleColor = bubblePalette.title)
Expand Down Expand Up @@ -284,6 +278,8 @@ fun ChatHeader(
onToggleConfig: () -> Unit,
) {
val corvusColors = CorvusTheme.colors
val iconBackgroundBrush =
remember(corvusColors.gradientPrimary) { Brush.linearGradient(corvusColors.gradientPrimary) }

Row(
modifier = Modifier.fillMaxWidth(),
Expand All @@ -310,10 +306,7 @@ fun ChatHeader(
modifier =
Modifier.size(44.dp)
.shadow(4.dp, CircleShape)
.background(
brush = Brush.linearGradient(corvusColors.gradientPrimary),
shape = CircleShape,
),
.background(brush = iconBackgroundBrush, shape = CircleShape),
) {
Icon(
imageVector = Icons.Default.Settings,
Expand Down Expand Up @@ -429,6 +422,10 @@ fun diagnosticsCard(
) {
val colors = MaterialTheme.colorScheme
val corvusColors = CorvusTheme.colors
val indicatorBrush =
remember(corvusColors.gradientPrimary) {
Brush.horizontalGradient(corvusColors.gradientPrimary)
}

Box(
modifier =
Expand All @@ -453,10 +450,7 @@ fun diagnosticsCard(
modifier =
Modifier.width(40.dp)
.height(3.dp)
.background(
brush = Brush.horizontalGradient(corvusColors.gradientPrimary),
shape = RoundedCornerShape(2.dp),
)
.background(brush = indicatorBrush, shape = RoundedCornerShape(2.dp))
)

Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -103,9 +104,7 @@ private fun SendButton(isEnabled: Boolean, gradient: Brush, glowColor: Color, on
spotColor = if (isEnabled) glowColor else Color.Gray,
)
.clip(CircleShape)
.background(
if (isEnabled) gradient else Brush.linearGradient(listOf(Color.Gray, Color.Gray))
)
.background(if (isEnabled) gradient else SolidColor(Color.Gray))
}

Box(modifier = sendButtonModifier, contentAlignment = Alignment.Center) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,22 +235,19 @@ private fun ChatWorkspaceScreen(

@Composable
private fun WorkspaceDivider(corvusColors: CorvusColorPalette) {
Box(
modifier =
Modifier.fillMaxWidth()
.height(1.dp)
.background(
brush =
Brush.horizontalGradient(
listOf(
Color.Transparent,
corvusColors.glowPurple.copy(alpha = 0.5f),
corvusColors.glowCyan.copy(alpha = 0.5f),
Color.Transparent,
)
)
val dividerBrush =
remember(corvusColors.glowPurple, corvusColors.glowCyan) {
Brush.horizontalGradient(
listOf(
Color.Transparent,
corvusColors.glowPurple.copy(alpha = 0.5f),
corvusColors.glowCyan.copy(alpha = 0.5f),
Color.Transparent,
)
)
)
}

Box(modifier = Modifier.fillMaxWidth().height(1.dp).background(brush = dividerBrush))
}

@Composable
Expand Down
Loading