feat(app): replace mock data with live Handler API

Wire the mobile app to the real Handler API instead of the transcribed
prototype data:

- src/api/client.ts: typed client ported from frontend/lib/api.ts (bearer
  auth, AuthError/ApiError, base URL param, allow401 for admin-only resume)
- src/api/format.ts: relative-time + status label/tone/color helpers
- src/state/ServerConfig.tsx: endpoint+token persisted to AsyncStorage
- src/screens/ConnectScreen.tsx: first-open config, validated via
  /health then /projects
- src/state/AppState.tsx: data-driven store polling projects -> agents ->
  checkmarks -> logs every 10s with per-item failure isolation; derives
  fleet counts, waiting list, recent checkmarks, merged log; answer+resume,
  spawn, kill mutations; 401 routes back to ConnectScreen
- screens render live data; detail meta is Started/Status/Tests/Build;
  Pause removed (no endpoint); Kill confirms; log filters are per-project
- delete src/data/mock.ts

tsc clean; Hermes bundle builds (200).
This commit is contained in:
2026-07-21 15:16:56 -04:00
parent 678a16605d
commit c0342a9796
15 changed files with 1578 additions and 373 deletions
+14
View File
@@ -3,7 +3,9 @@ import {
StyleSheet,
TextInput,
View,
type KeyboardTypeOptions,
type StyleProp,
type TextInputProps,
type ViewStyle,
} from "react-native";
import { fonts, radius } from "../theme/tokens";
@@ -21,6 +23,10 @@ export function TextField({
multiline = false,
height = 48,
style,
secureTextEntry = false,
autoCapitalize,
autoCorrect,
keyboardType,
}: {
value: string;
onChangeText: (t: string) => void;
@@ -29,6 +35,10 @@ export function TextField({
/** For single-line this is the control height; for multiline, the box height. */
height?: number;
style?: StyleProp<ViewStyle>;
secureTextEntry?: boolean;
autoCapitalize?: TextInputProps["autoCapitalize"];
autoCorrect?: boolean;
keyboardType?: KeyboardTypeOptions;
}) {
const { colors } = useTheme();
const [focus, setFocus] = useState(false);
@@ -52,6 +62,10 @@ export function TextField({
placeholder={placeholder}
placeholderTextColor={colors.textMuted}
multiline={multiline}
secureTextEntry={secureTextEntry}
autoCapitalize={autoCapitalize}
autoCorrect={autoCorrect}
keyboardType={keyboardType}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
style={[
+7 -1
View File
@@ -77,11 +77,17 @@ export function StatusDot({ color, size = 8 }: { color: string; size?: number })
export function Mono({
children,
style,
numberOfLines,
}: {
children: React.ReactNode;
style?: StyleProp<TextStyle>;
numberOfLines?: number;
}) {
return <Text style={[monoStyles.mono, style]}>{children}</Text>;
return (
<Text numberOfLines={numberOfLines} style={[monoStyles.mono, style]}>
{children}
</Text>
);
}
const monoStyles = StyleSheet.create({