Skip to content
Closed
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
19 changes: 18 additions & 1 deletion apps/swift-ios/Features/Chat/FeatureComposerView.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -198,7 +198,7 @@ struct FeatureComposerView: View {
axis: .vertical
)
.font(T3Typography.composer)
.lineLimit(1...7)
.modifier(FeatureComposerGrowingTextInput())
.focused(focused)
// Return is always editing input. Sending is deliberately button-only.
.submitLabel(.return)
Expand DownExpand Up@@ -448,6 +448,23 @@ struct FeatureComposerView: View {
}
}

struct FeatureComposerGrowingTextInput: ViewModifier {
static let maximumLineCount: Int? = nil

@ViewBuilder
func body(content: Content) -> some View {
if let maximumLineCount = Self.maximumLineCount {
content
.lineLimit(1...maximumLineCount)
.layoutPriority(1)
} else {
content
.lineLimit(1...)
.layoutPriority(1)
}
}
}

enum FeatureComposerCollapsePolicy {
static func shouldCollapse(
isFocused: Bool,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,70 @@
import SwiftUI
import Testing
@testable import T3Code

@Suite("Composer power features")
struct FeatureComposerPowerTests {
@MainActor
@Test
func composerTextInputGrowsBeyondThreeLinesWithoutANumericCap() {
#expect(FeatureComposerGrowingTextInput.maximumLineCount == nil)

let threeLineHeight = composerHeight(lineCount: 3)
let tenLineHeight = composerHeight(lineCount: 10)
let elevenLineHeight = composerHeight(lineCount: 11)
let thirtyLineHeight = composerHeight(lineCount: 30)
let measuredLineHeight = elevenLineHeight - tenLineHeight

#expect(thirtyLineHeight > threeLineHeight + (measuredLineHeight * 20))
}

@MainActor
@Test
func composerTextInputYieldsToTheAvailableViewportForVeryTallDrafts() {
let viewportHeight: CGFloat = 500

#expect(composerHeight(lineCount: 100, maximumHeight: viewportHeight) <= viewportHeight)
}

@MainActor
private func composerHeight(
lineCount: Int,
maximumHeight: CGFloat = .greatestFiniteMagnitude
) -> CGFloat {
let text = Array(repeating: "A full visible prompt line", count: lineCount)
.joined(separator: "\n")
let controller = UIHostingController(
rootView: ComposerHarness(text: text)
.frame(width: 320)
)

return controller.sizeThatFits(
in: CGSize(width: 320, height: maximumHeight)
).height
}

private struct ComposerHarness: View {
let text: String

@FocusState private var focused: Bool

var body: some View {
FeatureComposerView(
text: .constant(text),
selection: .constant(nil),
attachments: .constant([]),
providers: [],
threadSelection: nil,
isSending: false,
isWorking: false,
focused: $focused,
onSend: {},
onStop: {},
forceExpanded: true
)
}
}

@Test
func detectsCommandsModelsSkillsAndPathsAtTheCursor() {
#expect(
Expand Down
12 changes: 8 additions & 4 deletions apps/swift-ios/Tests/FeatureTests/HomeThreadMetadataTests.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -130,7 +130,9 @@ struct HomeThreadMetadataTests {
path: "/work/t3code"
),
],
providers: [FeatureProvider(id: "claude", name: "Claude")]
providersByEnvironment: [
"device": [FeatureProvider(id: "claude", name: "Claude")],
]
)

#expect(thread.homeEnvironmentLabel(in: snapshot) == "steambox")
Expand DownExpand Up@@ -162,9 +164,11 @@ struct HomeThreadMetadataTests {
),
],
threads: [knownThread, customThread],
providers: [
FeatureProvider(id: "work-claude", name: "Claude Code", driver: "custom"),
FeatureProvider(id: "acme-agent", name: "Acme Agent", driver: "custom"),
providersByEnvironment: [
"device": [
FeatureProvider(id: "work-claude", name: "Claude Code", driver: "custom"),
FeatureProvider(id: "acme-agent", name: "Acme Agent", driver: "custom"),
],
]
)

Expand Down
Loading