Skip to content
This repository was archived by the owner on Aug 27, 2026. It is now read-only.

Commit e3f428c

Browse files
hyochancodexclaude
authored
fix(ci): restore Android lint compatibility (#2)
## Summary - update AGP from 8.7.3 to 8.13.2 - update the Gradle wrapper distribution from 8.10.2 to 8.13 - keep both tools on major version 8 while restoring Kotlin 2.3-compatible Android lint - realign the iOS setup UI test with current labels and notification permission behavior - add an accessibility label for the icon-only iOS send action - avoid Xcode 15-only Swift compile failures in ObservableObject dictionary updates and member label interpolation ## Why The daily root build failed on main in :androidApp:lintAnalyzeDebug because AGP 8.7.3 lint expected older Kotlin metadata and could not analyze Kotlin 2.3.0 outputs. Android's AGP 8.13 release notes document Kotlin 2.3 support in AGP 8.13.2 and Gradle 8.13 as the minimum Gradle version: https://developer.android.com/build/releases/agp-8-13-0-release-notes The iOS E2E test was stale against the current SwiftUI labels and notification prompt flow. GitHub's iOS CI runner also uses Xcode 15.2, which rejected in-place @published dictionary mutations and direct interpolation of a user-id suffix collection. ## Validation - ./gradlew build --stacktrace - ./gradlew build --offline --quiet - ./gradlew :shared:serverTest --offline --quiet - ./gradlew :server:test - ./gradlew :androidApp:assembleDebug - ./gradlew :androidApp:testDebugUnitTest - ./gradlew :androidApp:assembleDebug :androidApp:connectedDebugAndroidTest - xcodegen generate - xcodebuild -project DeeplineIOS.xcodeproj -scheme DeeplineIOS -destination id=9D0E2D78-A57A-4AC8-8486-71D3B40E78CC -configuration Debug test CODE_SIGNING_ALLOWED=NO Optional quality tasks remain unwired in this repo: - ./gradlew detekt -> task not found - ./gradlew ktlintCheck -> task not found <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved accessibility labels for chat input controls that adapt based on input state. * **Bug Fixes** * Fixed user identifier display formatting in member lists. * **Chores** * Updated build tools and dependencies to latest stable versions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: OpenAI <codex@openai.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a18facf commit e3f428c

6 files changed

Lines changed: 41 additions & 14 deletions

File tree

clients/ios/DeeplineIOS/Sources/DeeplineAppModel.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ final class DeeplineAppModel: ObservableObject {
174174
try await self.ensureReachableServerBaseURL()
175175
let envelopes = try await self.client.listMessages(baseURL: self.serverBaseURL, conversationId: conversationId)
176176
let parsed = self.parseMessages(currentUserId: userId, envelopes: envelopes)
177-
self.messages[conversationId] = parsed
177+
self.replaceMessages(parsed, for: conversationId)
178178
self.updateConversationPreview(conversationId: conversationId, preview: parsed.last?.body)
179179
}
180180
}
@@ -242,7 +242,7 @@ final class DeeplineAppModel: ObservableObject {
242242
}
243243

244244
let newMessage = parseMessage(currentUserId: currentUserId, envelope: envelope)
245-
messages[conversationId] = existingMessages + [newMessage]
245+
replaceMessages(existingMessages + [newMessage], for: conversationId)
246246
updateConversationPreview(conversationId: conversationId, preview: newMessage.body)
247247
}
248248

@@ -353,7 +353,7 @@ final class DeeplineAppModel: ObservableObject {
353353
func loadGroupMembers(conversationId: String) async {
354354
await performSilentTask { [self] in
355355
let page = try await self.client.listConversationMembers(baseURL: self.serverBaseURL, conversationId: conversationId)
356-
self.groupMembers[conversationId] = page.members
356+
self.replaceGroupMembers(page.members, for: conversationId)
357357
}
358358
}
359359

@@ -473,6 +473,16 @@ final class DeeplineAppModel: ObservableObject {
473473
messages[conversationId] ?? []
474474
}
475475

476+
private func replaceMessages(_ newMessages: [DeeplineMessage], for conversationId: String) {
477+
guard (messages[conversationId] ?? []) != newMessages else { return }
478+
messages[conversationId] = newMessages
479+
}
480+
481+
private func replaceGroupMembers(_ newMembers: [GroupMember], for conversationId: String) {
482+
guard (groupMembers[conversationId] ?? []) != newMembers else { return }
483+
groupMembers[conversationId] = newMembers
484+
}
485+
476486
func primaryConversationId() -> String? {
477487
chats.first?.id
478488
}

clients/ios/DeeplineIOS/Sources/DeeplineServerClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ enum GroupRole: String, Codable {
180180
case MEMBER
181181
}
182182

183-
struct GroupMember: Codable, Identifiable {
183+
struct GroupMember: Codable, Equatable, Identifiable {
184184
let userId: String
185185
let role: GroupRole
186186
let addedByUserId: String?

clients/ios/DeeplineIOS/Sources/RootView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,7 @@ private struct ChatInputBar: View {
10931093
.foregroundStyle(draft.isEmpty ? DeeplineTheme.onSurfaceVariant(colorScheme) : .white)
10941094
}
10951095
}
1096+
.accessibilityLabel(draft.isEmpty ? "Voice message" : "Send")
10961097
}
10971098
.padding(.horizontal, 10)
10981099
.padding(.vertical, 8)
@@ -1439,7 +1440,7 @@ private struct MemberRow: View {
14391440
)
14401441

14411442
VStack(alignment: .leading, spacing: 4) {
1442-
Text("User \(member.userId.suffix(4))")
1443+
Text("User \(String(member.userId.suffix(4)))")
14431444
.font(.subheadline.weight(.medium))
14441445
.foregroundStyle(DeeplineTheme.onSurface(colorScheme))
14451446

clients/ios/DeeplineIOSUITests/DeeplineIOSUITests.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,33 @@ final class DeeplineIOSUITests: XCTestCase {
99
let app = XCUIApplication()
1010
app.launchArguments.append("-resetDeeplineState")
1111
app.launchEnvironment["DEEPLINE_SERVER_URL"] = "http://localhost:9091"
12+
addUIInterruptionMonitor(withDescription: "Notification permission") { alert in
13+
let alertText = ([alert.label] + alert.staticTexts.allElementsBoundByIndex.map(\.label)).joined(separator: " ")
14+
guard alertText.localizedCaseInsensitiveContains("notification") else { return false }
15+
if alert.buttons["Allow"].exists {
16+
alert.buttons["Allow"].tap()
17+
} else if alert.buttons.count > 1 {
18+
alert.buttons.element(boundBy: 1).tap()
19+
} else if alert.buttons.count > 0 {
20+
alert.buttons.element(boundBy: 0).tap()
21+
} else {
22+
return false
23+
}
24+
return true
25+
}
1226
app.launch()
27+
app.tap()
1328

14-
let setupButton = app.buttons["Set Up Local Identity"]
29+
let setupButton = app.buttons["Get Started"]
1530
XCTAssertTrue(setupButton.waitForExistence(timeout: 10))
1631
setupButton.tap()
1732

18-
let displayNameField = app.textFields["Display name"]
19-
XCTAssertTrue(displayNameField.waitForExistence(timeout: 10))
33+
let displayNameField = app.textFields["Enter your name"]
34+
XCTAssertTrue(displayNameField.waitForExistence(timeout: 15))
2035
displayNameField.tap()
2136
displayNameField.typeText("Codex Tester")
2237

23-
let deviceField = app.textFields["Device label"]
38+
let deviceField = app.textFields["e.g. iPhone 15 Pro"]
2439
XCTAssertTrue(deviceField.waitForExistence(timeout: 10))
2540
deviceField.tap()
2641
if let currentValue = deviceField.value as? String, !currentValue.isEmpty {
@@ -31,17 +46,18 @@ final class DeeplineIOSUITests: XCTestCase {
3146

3247
app.buttons["Create Identity"].tap()
3348

34-
let composer = app.textFields["Write a private note"]
49+
let composer = app.textFields["Message"]
3550
if !composer.waitForExistence(timeout: 20) {
3651
let localNotes = app.staticTexts["Local Notes"]
3752
XCTAssertTrue(localNotes.waitForExistence(timeout: 20))
3853
localNotes.tap()
3954
}
4055
XCTAssertTrue(composer.waitForExistence(timeout: 10))
41-
XCTAssertTrue(app.buttons["Send"].exists)
4256
composer.tap()
4357
composer.typeText("UITest secure note")
44-
app.buttons["Send"].tap()
58+
let sendButton = app.buttons["Send"]
59+
XCTAssertTrue(sendButton.waitForExistence(timeout: 10))
60+
sendButton.tap()
4561
XCTAssertTrue(app.staticTexts["UITest secure note"].waitForExistence(timeout: 10))
4662
}
4763
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[versions]
2-
agp = "8.7.3"
2+
agp = "8.13.2"
33
androidx-activity-compose = "1.10.1"
44
firebase-messaging = "24.1.0"
55
androidx-core-ktx = "1.15.0"

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

0 commit comments

Comments
 (0)