Refine mobile Assistant interaction layout
This commit is contained in:
1 parent
f14ec61cec
commit
dc7e5ca851
4 files changed
+67
-62
No files matched your search
@@ -381,25 +381,11 @@ private struct MobileAssistantSteeringQueue: View {
|
||||
|
||||
@State private var rowsHeight: CGFloat = 0
|
||||
|
||||
private var deliveryStatus: LocalizedStringKey {
|
||||
switch conversation.runState {
|
||||
case .waitingForApproval, .waitingForUser: "Waiting for response"
|
||||
default: "Next step"
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(spacing: 8) {
|
||||
Label("Steering", systemImage: "arrow.turn.down.right")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer(minLength: 8)
|
||||
Text(deliveryStatus)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Label("Steering", systemImage: "arrow.turn.down.right")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
@@ -442,8 +428,8 @@ private struct MobileAssistantSteeringQueue: View {
|
||||
}
|
||||
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 8, weight: .bold))
|
||||
.frame(width: 20, height: 20)
|
||||
.font(.caption.weight(.bold))
|
||||
.frame(width: 28, height: 28)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
@@ -558,6 +544,14 @@ struct MobileAssistantConversation: View {
|
||||
}
|
||||
|
||||
@ViewBuilder private var bottomSurface: some View {
|
||||
bottomSurfaceContent
|
||||
.padding(12)
|
||||
.frame(maxWidth: 840)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
}
|
||||
|
||||
@ViewBuilder private var bottomSurfaceContent: some View {
|
||||
if conversation.isArchived {
|
||||
archivedNotice
|
||||
.padding(12)
|
||||
@@ -566,15 +560,10 @@ struct MobileAssistantConversation: View {
|
||||
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
||||
.stroke(.primary.opacity(0.08), lineWidth: 1)
|
||||
}
|
||||
.padding(12)
|
||||
} else if conversation.pendingApproval != nil || conversation.pendingUserInteraction != nil {
|
||||
MobileAssistantInteraction(conversation: conversation, hosts: app.library)
|
||||
.padding(12)
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
} else {
|
||||
regularInteractionSurface
|
||||
.padding(12)
|
||||
.background(Color(uiColor: .systemBackground))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ struct MobileWorkspaceListView: View {
|
||||
Image(systemName: row.entry.kind.symbol)
|
||||
.foregroundStyle(.tint)
|
||||
.frame(width: 28, height: 28)
|
||||
.accessibilityLabel(Text(row.entry.kind.label))
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(row.host.name).foregroundStyle(.primary).lineLimit(1)
|
||||
Text(app.library.loginAddress(for: row.host))
|
||||
@@ -149,13 +150,10 @@ struct MobileWorkspaceListView: View {
|
||||
.truncationMode(.middle)
|
||||
}
|
||||
Spacer(minLength: 12)
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text(row.entry.kind.label).font(.caption.weight(.medium))
|
||||
Text(row.entry.connectedAt, format: .relative(presentation: .numeric).locale(locale))
|
||||
.font(.caption2)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
Text(row.entry.connectedAt, format: .relative(presentation: .numeric).locale(locale))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.contentShape(.rect)
|
||||
|
||||
@@ -27,8 +27,11 @@ struct MobileNavigationParityTests {
|
||||
assistant.components(separatedBy: "@ViewBuilder private var conversationTitleMenu").last?
|
||||
.components(separatedBy: "private func titleMenuButton").first)
|
||||
let bottomSurface = try #require(
|
||||
transcript.components(separatedBy: "@ViewBuilder private var bottomSurface").last?
|
||||
.components(separatedBy: "private var archivedNotice").first)
|
||||
transcript.components(separatedBy: "@ViewBuilder private var bottomSurface: some View {").last?
|
||||
.components(separatedBy: "@ViewBuilder private var bottomSurfaceContent").first)
|
||||
let transcriptRows = try #require(
|
||||
transcript.components(separatedBy: "MobileNativeList(").last?
|
||||
.components(separatedBy: ".safeAreaInset(edge: .bottom").first)
|
||||
let assistantRows = try #require(
|
||||
assistant.components(separatedBy: "private func conversationRow").last?
|
||||
.components(separatedBy: "private func select").first)
|
||||
@@ -93,6 +96,7 @@ struct MobileNavigationParityTests {
|
||||
#expect(transcript.contains("AssistantTranscriptPresentationState()"))
|
||||
#expect(transcript.contains("DisclosureGroup(isExpanded: disclosureBinding"))
|
||||
#expect(!transcript.contains("LazyVStack"))
|
||||
#expect(transcriptRows.contains(".frame(maxWidth: 840)"))
|
||||
#expect(workRows.contains("if batch.cards.count == 1, let card = batch.cards.first"))
|
||||
#expect(workRows.contains("defaultExpanded: true"))
|
||||
#expect(workRows.contains("presentation.isExpanded(.toolBatch(batch.id))"))
|
||||
@@ -110,12 +114,15 @@ struct MobileNavigationParityTests {
|
||||
let interaction = try String(
|
||||
contentsOf: root.appending(path: "TFI/Features/AI/MobileAssistantInteraction.swift"), encoding: .utf8)
|
||||
let bottomSurface = try #require(
|
||||
conversation.components(separatedBy: "@ViewBuilder private var bottomSurface").last?
|
||||
.components(separatedBy: "private var archivedNotice").first)
|
||||
conversation.components(separatedBy: "@ViewBuilder private var bottomSurface: some View {").last?
|
||||
.components(separatedBy: "@ViewBuilder private var bottomSurfaceContent").first)
|
||||
let bottomSurfaceContent = try #require(
|
||||
conversation.components(separatedBy: "@ViewBuilder private var bottomSurfaceContent").last?
|
||||
.components(separatedBy: "private var regularInteractionSurface").first)
|
||||
let blockingSurface = try #require(
|
||||
bottomSurface.components(separatedBy: "conversation.pendingUserInteraction != nil {").last?
|
||||
bottomSurfaceContent.components(separatedBy: "conversation.pendingUserInteraction != nil {").last?
|
||||
.components(separatedBy: "} else {").first)
|
||||
let composerSurface = try #require(bottomSurface.components(separatedBy: "} else {").last)
|
||||
let composerSurface = try #require(bottomSurfaceContent.components(separatedBy: "} else {").last)
|
||||
let regularSurface = try #require(
|
||||
conversation.components(separatedBy: "private var regularInteractionSurface").last?
|
||||
.components(separatedBy: "private var archivedNotice").first
|
||||
@@ -126,13 +133,22 @@ struct MobileNavigationParityTests {
|
||||
)
|
||||
|
||||
#expect(
|
||||
bottomSurface.contains(
|
||||
bottomSurfaceContent.contains(
|
||||
"conversation.pendingApproval != nil || conversation.pendingUserInteraction != nil"))
|
||||
#expect(bottomSurface.contains("bottomSurfaceContent"))
|
||||
let padding = try #require(bottomSurface.range(of: ".padding(12)"))
|
||||
let widthLimit = try #require(bottomSurface.range(of: ".frame(maxWidth: 840)"))
|
||||
let fullWidth = try #require(bottomSurface.range(of: ".frame(maxWidth: .infinity)"))
|
||||
let background = try #require(
|
||||
bottomSurface.range(of: ".background(Color(uiColor: .systemBackground))"))
|
||||
#expect(padding.lowerBound < widthLimit.lowerBound)
|
||||
#expect(widthLimit.lowerBound < fullWidth.lowerBound)
|
||||
#expect(fullWidth.lowerBound < background.lowerBound)
|
||||
#expect(bottomSurfaceContent.contains("if conversation.isArchived"))
|
||||
#expect(blockingSurface.contains("MobileAssistantInteraction("))
|
||||
#expect(!blockingSurface.contains("MobileAssistantComposer("))
|
||||
#expect(!blockingSurface.contains("MobileAssistantTaskCapsule("))
|
||||
#expect(!blockingSurface.contains("MobileAssistantSteeringQueue("))
|
||||
#expect(blockingSurface.contains(".background(Color(uiColor: .systemBackground))"))
|
||||
#expect(composerSurface.contains("regularInteractionSurface"))
|
||||
#expect(regularSurface.contains("ZStack(alignment: .top)"))
|
||||
#expect(regularFlow.contains("MobileAssistantSteeringQueue("))
|
||||
@@ -141,7 +157,7 @@ struct MobileNavigationParityTests {
|
||||
#expect(regularSurface.contains("MobileAssistantTaskCapsule("))
|
||||
#expect(regularSurface.contains(".offset(y: -44)"))
|
||||
#expect(!composerSurface.contains("MobileAssistantInteraction("))
|
||||
#expect(composerSurface.contains(".background(Color(uiColor: .systemBackground))"))
|
||||
#expect(!bottomSurfaceContent.contains(".background(Color(uiColor: .systemBackground))"))
|
||||
#expect(interaction.contains("conversation.answerUserInputOption(option.id, in: questionID)"))
|
||||
#expect(!interaction.contains("conversation.toggleUserInputOption(option.id, in: questionID)"))
|
||||
#expect(interaction.components(separatedBy: "MobileAssistantAdaptiveHeight {").count == 3)
|
||||
@@ -186,6 +202,10 @@ struct MobileNavigationParityTests {
|
||||
contentsOf: root.appending(path: "TFI/Features/AI/MobileAssistantTranscriptCards.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
let desktopConversation = try String(
|
||||
contentsOf: root.appending(path: "TFM/Features/AI/ConversationTranscriptView.swift"),
|
||||
encoding: .utf8
|
||||
)
|
||||
|
||||
#expect(!composer.contains("pendingSteering"))
|
||||
#expect(conversation.contains("struct MobileAssistantSteeringQueue"))
|
||||
@@ -197,7 +217,17 @@ struct MobileNavigationParityTests {
|
||||
.components(separatedBy: "struct MobileAssistantConversation").first
|
||||
)
|
||||
#expect(steeringQueue.contains("Image(systemName: \"xmark\")"))
|
||||
#expect(steeringQueue.contains(".font(.caption.weight(.bold))"))
|
||||
#expect(steeringQueue.contains(".frame(width: 28, height: 28)"))
|
||||
#expect(!steeringQueue.contains(".onHover"))
|
||||
let desktopSteeringQueue = try #require(
|
||||
desktopConversation.components(separatedBy: "struct SteeringQueueView").last)
|
||||
for source in [steeringQueue, desktopSteeringQueue] {
|
||||
#expect(!source.contains("\"Next step\""))
|
||||
#expect(!source.contains("\"Waiting for response\""))
|
||||
#expect(!source.contains("deliveryStatus"))
|
||||
#expect(!source.contains("isWaitingForResponse"))
|
||||
}
|
||||
#expect(conversation.contains("struct MobileAssistantTaskCapsule"))
|
||||
#expect(conversation.contains("ProgressRing("))
|
||||
#expect(conversation.contains(".offset(y: -overlayHeight)"))
|
||||
@@ -748,6 +778,13 @@ struct MobileNavigationParityTests {
|
||||
#expect(workspaceList.contains("description: Text(\"Use Connect to start a workspace.\")"))
|
||||
#expect(workspaceList.contains("Section(\"History\")"))
|
||||
#expect(workspaceList.contains("static let historyLimit = 5"))
|
||||
let historyRows = try #require(
|
||||
workspaceList.components(separatedBy: "private var historyRowsContent").last?
|
||||
.components(separatedBy: "private func statusTint").first)
|
||||
#expect(historyRows.contains("Image(systemName: row.entry.kind.symbol)"))
|
||||
#expect(historyRows.contains(".accessibilityLabel(Text(row.entry.kind.label))"))
|
||||
#expect(historyRows.contains("Text(row.entry.connectedAt, format: .relative"))
|
||||
#expect(!historyRows.contains("Text(row.entry.kind.label).font"))
|
||||
#expect(!workspaceList.contains("Open a host from Vault to start a workspace."))
|
||||
#expect(!workspaceList.contains(".buttonStyle(.borderedProminent)"))
|
||||
#expect(workspaceList.contains(".buttonStyle(.plain)"))
|
||||
|
||||
@@ -881,30 +881,11 @@ struct SteeringQueueView: View {
|
||||
@State private var rowsHeight: CGFloat = 0
|
||||
@State private var hoveredMessageID: PendingSteeringMessage.ID?
|
||||
|
||||
/// The wait has two different causes, and only one of them resolves on its
|
||||
/// own. While a card above is holding the run, saying "next step" would be
|
||||
/// a promise nothing is working towards.
|
||||
private var deliveryStatus: LocalizedStringKey {
|
||||
switch conversation.runState {
|
||||
case .waitingForApproval, .waitingForUser: "Waiting for response"
|
||||
default: "Next step"
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Metrics.Space.m) {
|
||||
HStack(spacing: Metrics.Space.s) {
|
||||
Label("Steering", systemImage: "arrow.turn.down.right")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Spacer(minLength: Metrics.Space.s)
|
||||
|
||||
Text(deliveryStatus)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Label("Steering", systemImage: "arrow.turn.down.right")
|
||||
.font(.caption.weight(.medium))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
|
||||
Reference in new issue
Block a user