Fix major Nearlight deviations from design review
- Expose End conversation + readable draft during connecting/waiting - Stop masking assistant speaking/thinking state in Type/muted modes - Restrict failure diagnosis to real causes (no invented network text) - Replace old blue tint with Rosewood everywhere - Fix transcript short-content bottom-anchor (top-align short histories) - Settings: visible Server URL label, focus on rejected save, edit clears saved confirmation, trim-aware unchanged detection - Task detail: live store lookup with missing-task state - Task rows: status text label restored
This commit is contained in:
1 parent
fa21c81787
commit
f043d6b078
5 files changed
+72
-30
No files matched your search
@@ -120,12 +120,15 @@ private func lightwellPhase(session: VoiceSession, conversation: ConversationSta
|
||||
case .tearingDown: return .tearingDown
|
||||
case .failed: return .failed
|
||||
case .ready:
|
||||
if textMode { return .readyNoInput }
|
||||
if session.isMuted { return .readyNoInput }
|
||||
// Only substitute idle input geometry when otherwise LISTENING.
|
||||
// Assistant output (speaking/thinking) must stay represented in
|
||||
// Type and muted modes per spec.
|
||||
switch conversation.phase {
|
||||
case .speaking: return .speaking(level: conversation.agentLevel)
|
||||
case .thinking: return .thinking
|
||||
default: return .listening(level: conversation.micLevel)
|
||||
default:
|
||||
if textMode || session.isMuted { return .readyNoInput }
|
||||
return .listening(level: conversation.micLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,7 +283,10 @@ private struct ConversationContent: View {
|
||||
}
|
||||
|
||||
private var micDenied: Bool {
|
||||
(session.micError?.contains("Microphone") ?? false) && session.state.isFailed == false && session.state == .ready
|
||||
// Only the explicit permission-denied wording from enableMicrophone
|
||||
// counts; other mic errors keep their real message without a
|
||||
// Settings deep link.
|
||||
session.state == .ready && (session.micError?.lowercased().contains("denied") ?? false)
|
||||
}
|
||||
|
||||
private var failureTitle: String {
|
||||
@@ -293,13 +299,12 @@ private struct ConversationContent: View {
|
||||
return micError
|
||||
}
|
||||
let raw = failureMessage ?? ""
|
||||
if raw.lowercased().contains("agent") {
|
||||
// Only the agent-timeout path is a known classified cause; every
|
||||
// other failure keeps its actual readable error per spec.
|
||||
if raw.contains("Agent never joined") {
|
||||
return "Check that Hope is running on your server."
|
||||
}
|
||||
if raw.lowercased().contains("timed out") || raw.lowercased().contains("connect") {
|
||||
return "Check your Wi-Fi or VPN and make sure your server is running."
|
||||
}
|
||||
return raw.isEmpty ? "Check your Wi-Fi or VPN and make sure your server is running." : raw
|
||||
return raw.isEmpty ? "The connection failed. Try again." : raw
|
||||
}
|
||||
|
||||
// MARK: Dock
|
||||
@@ -316,6 +321,12 @@ private struct ConversationContent: View {
|
||||
modeSelector
|
||||
if session.isReady {
|
||||
if settings.textMode { typeControls } else { voiceControls }
|
||||
} else if isBusy && !session.state.isFailed {
|
||||
// Connecting/waiting: keep the draft readable and expose the
|
||||
// existing cancellation path per spec.
|
||||
if settings.textMode { typeControls }
|
||||
else { HStack(spacing: Nearlight.s12) { muteButton; micMeter } }
|
||||
endButton
|
||||
} else {
|
||||
startControls
|
||||
}
|
||||
@@ -534,13 +545,14 @@ private struct ConversationContent: View {
|
||||
case .tearingDown: return "Ending conversation…"
|
||||
case .failed: return "Connection interrupted"
|
||||
case .ready:
|
||||
if settings.textMode { return "Ready for your message" }
|
||||
if session.isMuted { return "Microphone off" }
|
||||
// Assistant output outranks input-mode substitution.
|
||||
switch conversation.phase {
|
||||
case .listening: return "Listening"
|
||||
case .thinking: return "Thinking…"
|
||||
case .speaking: return "Hope is speaking"
|
||||
default: return "Listening"
|
||||
case .thinking: return "Thinking…"
|
||||
default:
|
||||
if settings.textMode { return "Ready for your message" }
|
||||
if session.isMuted { return "Microphone off" }
|
||||
return "Listening"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,10 @@ struct TranscriptListView: View {
|
||||
.padding(.horizontal, Nearlight.s20)
|
||||
.padding(.top, Nearlight.s16)
|
||||
.padding(.bottom, Nearlight.s20)
|
||||
.frame(minHeight: 200, alignment: .topLeading)
|
||||
}
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
.defaultScrollAnchor(.bottom)
|
||||
.defaultScrollAnchor(.top)
|
||||
.onChange(of: events.last?.text) { _, _ in
|
||||
if followingLatest { proxy.scrollTo("latest", anchor: .bottom) }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user