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.