diff --git a/packages/app/src/app/pair-scan.tsx b/packages/app/src/app/pair-scan.tsx
index 502815d11..694247d32 100644
--- a/packages/app/src/app/pair-scan.tsx
+++ b/packages/app/src/app/pair-scan.tsx
@@ -4,7 +4,7 @@ import { useLocalSearchParams, useRouter } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { CameraView, useCameraPermissions } from "expo-camera";
-import type { BarcodeScanningResult } from "expo-camera";
+import type { BarcodeScanningResult, BarcodeSettings } from "expo-camera";
import { useHostMutations } from "@/runtime/host-runtime";
import { decodeOfferFragmentPayload, normalizeHostPort } from "@/utils/daemon-endpoints";
import { connectToDaemon } from "@/utils/test-daemon-connection";
@@ -272,7 +272,7 @@ export default function PairScanScreen() {
);
}
-const BARCODE_SCANNER_SETTINGS = { barcodeTypes: ["qr"] as const };
+const BARCODE_SCANNER_SETTINGS: BarcodeSettings = { barcodeTypes: ["qr"] };
const CORNER_TL_STYLE = [styles.corner, styles.cornerTL];
const CORNER_TR_STYLE = [styles.corner, styles.cornerTR];
const CORNER_BL_STYLE = [styles.corner, styles.cornerBL];
diff --git a/packages/app/src/components/explorer-sidebar.tsx b/packages/app/src/components/explorer-sidebar.tsx
index 567a66672..7ca210c96 100644
--- a/packages/app/src/components/explorer-sidebar.tsx
+++ b/packages/app/src/components/explorer-sidebar.tsx
@@ -279,6 +279,10 @@ export function ExplorerSidebar({
mobileKeyboardInsetStyle,
],
);
+ const desktopSidebarStyle = useMemo(
+ () => [explorerStaticStyles.desktopSidebar, resizeAnimatedStyle, { paddingTop: insets.top }],
+ [resizeAnimatedStyle, insets.top],
+ );
// Mobile: full-screen overlay with gesture.
// On web, keep it interactive only while open so closed sidebars don't eat taps.
diff --git a/packages/app/src/components/headers/menu-header.tsx b/packages/app/src/components/headers/menu-header.tsx
index 5a7cfcb24..25b50e019 100644
--- a/packages/app/src/components/headers/menu-header.tsx
+++ b/packages/app/src/components/headers/menu-header.tsx
@@ -27,10 +27,7 @@ const MOBILE_MENU_LINE_SHORT_WIDTH = 8;
const MOBILE_MENU_LINE_HEIGHT = 2;
function MobileMenuIcon({ color }: { color: string }) {
- const lineStyle = useMemo(
- () => [styles.mobileMenuLine, { backgroundColor: color }],
- [color],
- );
+ const lineStyle = useMemo(() => [styles.mobileMenuLine, { backgroundColor: color }], [color]);
const shortLineStyle = useMemo(
() => [styles.mobileMenuLine, styles.mobileMenuLineShort, { backgroundColor: color }],
[color],
diff --git a/packages/app/src/components/sidebar-workspace-list.tsx b/packages/app/src/components/sidebar-workspace-list.tsx
index b4a8fe91f..eb87aea26 100644
--- a/packages/app/src/components/sidebar-workspace-list.tsx
+++ b/packages/app/src/components/sidebar-workspace-list.tsx
@@ -230,6 +230,11 @@ export function PrBadge({ hint }: { hint: PrHint }) {
const handleHoverIn = useCallback(() => setIsHovered(true), []);
const handleHoverOut = useCallback(() => setIsHovered(false), []);
+ const prBadgeTextStyle = useMemo(
+ () => [prBadgeStyles.text, { color: activeColor }],
+ [activeColor],
+ );
+
return (
)}
-
+
#{hint.number}
@@ -290,18 +295,20 @@ const prBadgeStyles = StyleSheet.create((theme) => ({
function ChecksBadge({ checks }: { checks: PrHint["checks"] }): ReactElement | null {
const { theme } = useUnistyles();
+ const color = theme.colors.palette.red[500];
+ const textStyle = useMemo(() => [checksBadgeStyles.text, { color }], [color]);
+
if (!checks || checks.length === 0) return null;
const failed = checks.filter((c) => c.status === "failure").length;
if (failed === 0) return null;
- const color = theme.colors.palette.red[500];
const label = `${failed} failed`;
return (
- {label}
+ {label}
);
}
@@ -375,24 +382,25 @@ function WorkspaceStatusIndicator({
? EMPHASIZED_STATUS_DOT_OFFSET
: DEFAULT_STATUS_DOT_OFFSET;
+ const statusDotOverlayStyle = useMemo(
+ () => [
+ styles.statusDotOverlay,
+ {
+ backgroundColor: dotColor ?? undefined,
+ borderColor: theme.colors.surface0,
+ width: statusDotSize,
+ height: statusDotSize,
+ right: statusDotOffset,
+ bottom: statusDotOffset,
+ },
+ ],
+ [dotColor, theme.colors.surface0, statusDotSize, statusDotOffset],
+ );
+
return (
- {dotColor ? (
-
- ) : null}
+ {dotColor ? : null}
);
}
@@ -430,8 +438,9 @@ function ProjectLeadingVisual({
);
}
+ const iconSource = useMemo(() => ({ uri: iconDataUri ?? "" }), [iconDataUri]);
const projectIcon = iconDataUri ? (
-
+
) : (
{placeholderInitial}
@@ -486,24 +495,25 @@ function ProjectLeadingVisual({
? EMPHASIZED_STATUS_DOT_OFFSET
: DEFAULT_STATUS_DOT_OFFSET;
+ const projectStatusDotOverlayStyle = useMemo(
+ () => [
+ styles.statusDotOverlay,
+ {
+ backgroundColor: dotColor ?? "transparent",
+ borderColor: theme.colors.surface0,
+ width: statusDotSize,
+ height: statusDotSize,
+ right: statusDotOffset,
+ bottom: statusDotOffset,
+ },
+ ],
+ [dotColor, theme.colors.surface0, statusDotSize, statusDotOffset],
+ );
+
return (
{projectIcon}
- {dotColor ? (
-
- ) : null}
+ {dotColor ? : null}
);
}
@@ -1059,6 +1069,16 @@ function WorkspaceRowInner({
(s) => s.lifecycle === "running" && (s.type ?? "service") === "service",
);
+ const accessibilityState = useMemo(() => ({ selected }), [selected]);
+ const workspaceBranchTextStyle = useMemo(
+ () => [
+ styles.workspaceBranchText,
+ isHovered && styles.workspaceBranchTextHovered,
+ isCreating && styles.workspaceBranchTextCreating,
+ ],
+ [isHovered, isCreating],
+ );
+
return (
-
+
{workspace.name}
diff --git a/packages/relay/src/e2e.test.ts b/packages/relay/src/e2e.test.ts
index 12bb6a8b0..044d80ea0 100644
--- a/packages/relay/src/e2e.test.ts
+++ b/packages/relay/src/e2e.test.ts
@@ -155,10 +155,7 @@ async function waitForRelayWebSocketReady(
return poll();
}
-async function waitForProcessExit(
- relayProcess: ChildProcess,
- deadline: number,
-): Promise {
+async function waitForProcessExit(relayProcess: ChildProcess, deadline: number): Promise {
if (relayProcess.exitCode !== null) return;
if (Date.now() >= deadline) return;
await sleep(50);