diff --git a/packages/app/src/components/headers/back-header.tsx b/packages/app/src/components/headers/back-header.tsx index 9817e90bc..7ee9e7dcd 100644 --- a/packages/app/src/components/headers/back-header.tsx +++ b/packages/app/src/components/headers/back-header.tsx @@ -1,71 +1,45 @@ -import { View, Pressable, Text } from "react-native"; +import type { ReactNode } from "react"; +import { Pressable, Text } from "react-native"; import { router } from "expo-router"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { ArrowLeft } from "lucide-react-native"; +import { ScreenHeader } from "./screen-header"; interface BackHeaderProps { title?: string; - rightContent?: React.ReactNode; + rightContent?: ReactNode; } export function BackHeader({ title, rightContent }: BackHeaderProps) { const { theme } = useUnistyles(); - const insets = useSafeAreaInsets(); return ( - - - - {/* Left side - Back button */} - - router.back()} - style={styles.backButton} - > - - - {title && ( - - {title} - - )} - - - {/* Right side */} - - {rightContent} - - - - + + router.back()} + style={styles.backButton} + > + + + {title && ( + + {title} + + )} + + } + right={rightContent} + leftStyle={styles.left} + /> ); } const styles = StyleSheet.create((theme) => ({ - header: { - backgroundColor: theme.colors.background, - }, - headerRow: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - paddingHorizontal: theme.spacing[3], - paddingBottom: theme.spacing[2], - borderBottomWidth: theme.borderWidth[1], - borderBottomColor: theme.colors.border, - }, - headerLeft: { - flex: 1, - flexDirection: "row", - alignItems: "center", + left: { gap: theme.spacing[3], }, - headerRight: { - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[2], - }, backButton: { padding: theme.spacing[3], borderRadius: theme.borderRadius.lg, diff --git a/packages/app/src/components/headers/home-header.tsx b/packages/app/src/components/headers/home-header.tsx index ebe9ce124..b1cfe6b87 100644 --- a/packages/app/src/components/headers/home-header.tsx +++ b/packages/app/src/components/headers/home-header.tsx @@ -1,8 +1,8 @@ -import { View, Pressable } from "react-native"; +import { Pressable } from "react-native"; import { router } from "expo-router"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { Settings, MessageSquare, Plus } from "lucide-react-native"; +import { ScreenHeader } from "./screen-header"; interface HomeHeaderProps { onCreateAgent: () => void; @@ -10,64 +10,35 @@ interface HomeHeaderProps { export function HomeHeader({ onCreateAgent }: HomeHeaderProps) { const { theme } = useUnistyles(); - const insets = useSafeAreaInsets(); return ( - - - - {/* Left side - Settings */} - - router.push("/settings")} - style={styles.iconButton} - > - - - - - {/* Right side - Activity and New Agent */} - - router.push("/orchestrator")} - style={styles.iconButton} - > - - - - - - - - - + router.push("/settings")} + style={styles.iconButton} + > + + + } + right={ + <> + router.push("/orchestrator")} + style={styles.iconButton} + > + + + + + + + } + /> ); } const styles = StyleSheet.create((theme) => ({ - header: { - backgroundColor: theme.colors.background, - }, - headerRow: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - paddingHorizontal: theme.spacing[3], - paddingBottom: theme.spacing[2], - borderBottomWidth: theme.borderWidth[1], - borderBottomColor: theme.colors.border, - }, - headerLeft: { - flex: 1, - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[2], - }, - headerRight: { - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[2], - }, iconButton: { padding: theme.spacing[3], borderRadius: theme.borderRadius.lg, diff --git a/packages/app/src/components/headers/screen-header.tsx b/packages/app/src/components/headers/screen-header.tsx new file mode 100644 index 000000000..907eb263e --- /dev/null +++ b/packages/app/src/components/headers/screen-header.tsx @@ -0,0 +1,58 @@ +import type { ReactNode } from "react"; +import { View, type StyleProp, type ViewStyle } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { StyleSheet, useUnistyles } from "react-native-unistyles"; + +interface ScreenHeaderProps { + left?: ReactNode; + right?: ReactNode; + leftStyle?: StyleProp; + rightStyle?: StyleProp; +} + +/** + * Shared frame for the home/back headers so we only maintain padding, border, + * and safe-area logic in one place. + */ +export function ScreenHeader({ left, right, leftStyle, rightStyle }: ScreenHeaderProps) { + const insets = useSafeAreaInsets(); + const { theme } = useUnistyles(); + + return ( + + + + {left} + {right} + + + + ); +} + +const styles = StyleSheet.create((theme) => ({ + header: { + backgroundColor: theme.colors.background, + }, + inner: {}, + row: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + paddingHorizontal: theme.spacing[3], + paddingBottom: theme.spacing[2], + borderBottomWidth: theme.borderWidth[1], + borderBottomColor: theme.colors.border, + }, + left: { + flex: 1, + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + }, + right: { + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + }, +})); diff --git a/plan.md b/plan.md index fcdaff08b..aea9b8bd7 100644 --- a/plan.md +++ b/plan.md @@ -86,5 +86,6 @@ - Split the server build/typecheck configs so the typechecker can include the app stream helpers without breaking builds, added path aliases for `@server/*`, and tightened the shared stream types (status unions + `isAgentToolCallItem`) so the server e2e suites can import them safely. Cleaned up every failing app/server test by narrowing tool-call payloads via the helper, updated the harness + Codex/Claude specs, and re-ran `npm run typecheck` (app + server) to green. - [x] audit codebase for unecessary untyped code, hacks, casts, and `any`, type things properly - Tightened the Codex rollout parser typings so permission requests, rollout/event payloads, plan arguments, and JSON helpers all operate on well-defined TypeScript structures instead of `any`. Added explicit payload/entry guards, removed unsafe casts throughout `packages/server/src/server/agent/providers/codex-agent.ts`, and re-ran `npm run typecheck --workspace=@voice-dev/server` to validate the stricter typing. -- [ ] audit codebase for duplicated code, clean it up +- [x] audit codebase for duplicated code, clean it up + - Consolidated the duplicated home/back header layouts into a shared `ScreenHeader` component so safe-area padding, borders, and spacing live in one place; both headers now only define their unique button/title content. Ran `npm run typecheck --workspace=@voice-dev/app` to confirm the refactor stays type-safe. - [ ] rename the project to Paseo, including the Expo app, package names etc.