fix(ux): 10 UI/UX bugs from pre-release audit (toward no-bug launch gate)

Found via parallel screen audit; each confirmed in code:
- AuthGate: auto-prompt biometrics on lock (useEffect was imported but unused)
- CodeBlock: horizontal scroll for long code lines (were wrapped/mangled)
- DiffView: horizontal scroll instead of numberOfLines=1 truncation
- chat: biometric-cancel on send shows feedback instead of silently dropping msg
- chat: send failure restores input + attachments and alerts
- chat: removed dead /compact + /clear builtin commands (advertised, no-op)
- sessions: delete + rename failures alert instead of silent; rename guarded
  against double-submit
- sessions: onRefresh spinner no longer hangs forever if a refresh rejects
- add/edit connection: validate URL has http(s):// scheme before save/test

typecheck clean, 65/65 unit tests pass. Runtime UI behavior still needs on-device
verification per the pre-posting test gate (HANDOFF §0b).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
engineer
2026-06-08 05:34:12 -07:00
parent 609bd45889
commit 42fa36f3cd
7 changed files with 103 additions and 57 deletions

View File

@@ -13,6 +13,16 @@ export function AuthGate({ children }: Props) {
const { isAuthenticated, settings, hasBiometrics, biometricType, authenticate, error } = useAuth()
// Auto-prompt the OS biometric dialog once when the lock screen appears, so users
// aren't forced to tap "Unlock" on every cold start. If they cancel/fail, the
// manual "Unlock" button below is the fallback.
useEffect(() => {
if (settings.requireBiometric && hasBiometrics && !isAuthenticated) {
authenticate()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// If biometric not required, or no biometrics available, show children
if (!settings.requireBiometric || !hasBiometrics) {
return <>{children}</>

View File

@@ -1,4 +1,4 @@
import { View, Text, StyleSheet, Platform } from "react-native"
import { View, Text, StyleSheet, Platform, ScrollView } from "react-native"
const mono = Platform.OS === "ios" ? "Menlo" : "monospace"
@@ -76,32 +76,35 @@ export function DiffView({ before, after, isDark }: Props) {
return (
<View style={[s.container, isDark && s.containerDark]}>
{lines.map((line, idx) => (
<View
key={idx}
style={[
s.line,
line.type === "add" && (isDark ? s.addDark : s.add),
line.type === "remove" && (isDark ? s.removeDark : s.remove),
]}
>
<Text style={[s.prefix, isDark && s.prefixDark]}>
{line.type === "add" ? "+" : line.type === "remove" ? "-" : " "}
</Text>
<Text
style={[
s.text,
isDark && s.textDark,
line.type === "add" && s.addText,
line.type === "remove" && s.removeText,
]}
selectable
numberOfLines={1}
>
{line.text}
</Text>
<ScrollView horizontal showsHorizontalScrollIndicator>
<View>
{lines.map((line, idx) => (
<View
key={idx}
style={[
s.line,
line.type === "add" && (isDark ? s.addDark : s.add),
line.type === "remove" && (isDark ? s.removeDark : s.remove),
]}
>
<Text style={[s.prefix, isDark && s.prefixDark]}>
{line.type === "add" ? "+" : line.type === "remove" ? "-" : " "}
</Text>
<Text
style={[
s.text,
isDark && s.textDark,
line.type === "add" && s.addText,
line.type === "remove" && s.removeText,
]}
selectable
>
{line.text}
</Text>
</View>
))}
</View>
))}
</ScrollView>
</View>
)
}
@@ -135,7 +138,6 @@ const s = StyleSheet.create({
prefixDark: { color: "#666666" },
text: {
flex: 1,
fontSize: 12,
fontFamily: mono,
color: "#0a0a0a",

View File

@@ -1,5 +1,5 @@
import { useState } from "react"
import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Platform } from "react-native"
import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Platform, ScrollView } from "react-native"
import * as Clipboard from "expo-clipboard"
interface Props {
@@ -27,9 +27,11 @@ export function CodeBlock({ code, language }: Props) {
<Text style={[styles.copyBtn, isDark && styles.copyBtnDark]}>{copied ? "Copied!" : "Copy"}</Text>
</TouchableOpacity>
</View>
<Text style={[styles.code, isDark && styles.codeDark]} selectable>
{code}
</Text>
<ScrollView horizontal showsHorizontalScrollIndicator contentContainerStyle={styles.codeScroll}>
<Text style={[styles.code, isDark && styles.codeDark]} selectable>
{code}
</Text>
</ScrollView>
</View>
)
}
@@ -72,12 +74,14 @@ const styles = StyleSheet.create({
copyBtnDark: {
color: "#a78bfa",
},
codeScroll: {
padding: 12,
},
code: {
fontFamily: Platform.OS === "ios" ? "Menlo" : "monospace",
fontSize: 13,
lineHeight: 20,
color: "#1a1a1a",
padding: 12,
},
codeDark: {
color: "#e5e5e5",