| ... | @@ -23,6 +23,7 @@ import { | ... | @@ -23,6 +23,7 @@ import { |
| 23 | FlatList, | 23 | FlatList, |
| 24 | Platform, | 24 | Platform, |
| 25 | Pressable, | 25 | Pressable, |
| | 26 | RefreshControl, |
| 26 | ScrollView, | 27 | ScrollView, |
| 27 | type NativeSyntheticEvent, | 28 | type NativeSyntheticEvent, |
| 28 | StyleSheet, | 29 | StyleSheet, |
| ... | @@ -399,7 +400,11 @@ export function ReviewSheet(props: ReviewSheetProps) { | ... | @@ -399,7 +400,11 @@ export function ReviewSheet(props: ReviewSheetProps) { |
| 399 | selectedSection, | 400 | selectedSection, |
| 400 | draftMessage, | 401 | draftMessage, |
| 401 | }); | 402 | }); |
| 402 | const NativeReviewDiffView = resolveNativeReviewDiffView()!; | 403 | // Resolution returns null while Expo registers the native view (or forever |
| | 404 | // when the binary lacks it). Rendering a null component type crashes the |
| | 405 | // app, so callers must fall back — ThreadFeed's ReviewCommentCard does the |
| | 406 | // same check. |
| | 407 | const NativeReviewDiffView = resolveNativeReviewDiffView(); |
| 403 | const nativeReviewDiffViewRef = useRef<NativeReviewDiffViewHandle>(null); | 408 | const nativeReviewDiffViewRef = useRef<NativeReviewDiffViewHandle>(null); |
| 404 | const showcasedReviewDrawRef = useRef<string | null>(null); | 409 | const showcasedReviewDrawRef = useRef<string | null>(null); |
| 405 | // Native pull-to-refresh on the diff surface (replaces the old Refresh menu item). | 410 | // Native pull-to-refresh on the diff surface (replaces the old Refresh menu item). |
| ... | @@ -597,11 +602,27 @@ export function ReviewSheet(props: ReviewSheetProps) { | ... | @@ -597,11 +602,27 @@ export function ReviewSheet(props: ReviewSheetProps) { |
| 597 | .filter((part): part is string => Boolean(part)) | 602 | .filter((part): part is string => Boolean(part)) |
| 598 | .join(" · "); | 603 | .join(" · "); |
| 599 | | 604 | |
| 600 | // The changed-files navigator lives in the workspace inspector column — | 605 | // The changed-files navigator drives the native diff surface via |
| 601 | // the single right-hand pane per route — instead of an in-screen panel. | 606 | // scrollToFile, so it is only useful when that surface resolved. In raw |
| | 607 | // fallback mode the ref is necessarily null and the raw patch neither |
| | 608 | // scrolls nor filters — registering the navigator would present working |
| | 609 | // controls that cannot navigate. |
| 602 | const showChangedFilesPane = | 610 | const showChangedFilesPane = |
| 603 | !showConnectionNotice && selectedSection !== null && parsedDiff.kind === "files"; | 611 | !showConnectionNotice && |
| | 612 | selectedSection !== null && |
| | 613 | parsedDiff.kind === "files" && |
| | 614 | NativeReviewDiffView !== null; |
| 604 | useRegisterWorkspaceInspector(showChangedFilesPane ? renderInspector : undefined); | 615 | useRegisterWorkspaceInspector(showChangedFilesPane ? renderInspector : undefined); |
| | 616 | // Raw fallback renders the patch inline with no inspector content, so the |
| | 617 | // pane toggle would open an empty column — hide it in exactly that case. |
| | 618 | const showChangedFilesToggle = |
| | 619 | panes.supportsAuxiliaryPane && |
| | 620 | !( |
| | 621 | !showConnectionNotice && |
| | 622 | selectedSection !== null && |
| | 623 | parsedDiff.kind === "files" && |
| | 624 | NativeReviewDiffView === null |
| | 625 | ); |
| 605 | | 626 | |
| 606 | const listHeader = useMemo(() => { | 627 | const listHeader = useMemo(() => { |
| 607 | const children: ReactElement[] = []; | 628 | const children: ReactElement[] = []; |
| ... | @@ -688,7 +709,7 @@ export function ReviewSheet(props: ReviewSheetProps) { | ... | @@ -688,7 +709,7 @@ export function ReviewSheet(props: ReviewSheetProps) { |
| 688 | | 709 | |
| 689 | {!isAndroid && (showSectionToolbar || panes.supportsAuxiliaryPane || gitMenuAvailable) ? ( | 710 | {!isAndroid && (showSectionToolbar || panes.supportsAuxiliaryPane || gitMenuAvailable) ? ( |
| 690 | <NativeHeaderToolbar placement="right"> | 711 | <NativeHeaderToolbar placement="right"> |
| 691 | {panes.supportsAuxiliaryPane ? ( | 712 | {showChangedFilesToggle ? ( |
| 692 | <NativeHeaderToolbar.Button | 713 | <NativeHeaderToolbar.Button |
| 693 | accessibilityLabel={ | 714 | accessibilityLabel={ |
| 694 | panes.auxiliaryPaneVisible ? "Hide changed files" : "Show changed files" | 715 | panes.auxiliaryPaneVisible ? "Hide changed files" : "Show changed files" |
| ... | @@ -781,7 +802,7 @@ export function ReviewSheet(props: ReviewSheetProps) { | ... | @@ -781,7 +802,7 @@ export function ReviewSheet(props: ReviewSheetProps) { |
| 781 | onRetry={handleRetryEnvironment} | 802 | onRetry={handleRetryEnvironment} |
| 782 | /> | 803 | /> |
| 783 | </View> | 804 | </View> |
| 784 | ) : selectedSection && parsedDiff.kind === "files" ? ( | 805 | ) : selectedSection && parsedDiff.kind === "files" && NativeReviewDiffView ? ( |
| 785 | <View | 806 | <View |
| 786 | className="flex-1" | 807 | className="flex-1" |
| 787 | style={{ | 808 | style={{ |
| ... | @@ -835,6 +856,16 @@ export function ReviewSheet(props: ReviewSheetProps) { | ... | @@ -835,6 +856,16 @@ export function ReviewSheet(props: ReviewSheetProps) { |
| 835 | }} | 856 | }} |
| 836 | showsVerticalScrollIndicator={false} | 857 | showsVerticalScrollIndicator={false} |
| 837 | className="flex-1" | 858 | className="flex-1" |
| | 859 | refreshControl={ |
| | 860 | // The native diff surface owns pull-to-refresh via onPullToRefresh; |
| | 861 | // the raw fallback (and empty states) need an explicit control — |
| | 862 | // iOS has no other refresh affordance here (the explicit |
| | 863 | // "Refresh current diff" menu is Android-only). |
| | 864 | <RefreshControl |
| | 865 | refreshing={isPullRefreshing} |
| | 866 | onRefresh={() => void handlePullToRefresh()} |
| | 867 | /> |
| | 868 | } |
| 838 | > | 869 | > |
| 839 | {listHeader} | 870 | {listHeader} |
| 840 | {!selectedSection ? ( | 871 | {!selectedSection ? ( |
| ... | @@ -867,6 +898,19 @@ export function ReviewSheet(props: ReviewSheetProps) { | ... | @@ -867,6 +898,19 @@ export function ReviewSheet(props: ReviewSheetProps) { |
| 867 | </Text> | 898 | </Text> |
| 868 | </ScrollView> | 899 | </ScrollView> |
| 869 | </View> | 900 | </View> |
| | 901 | ) : parsedDiff.kind === "files" ? ( |
| | 902 | // The native diff surface could not be resolved on this binary; |
| | 903 | // degrade to the raw patch instead of crashing the app. |
| | 904 | <View className="gap-3 border-b border-border bg-card px-4 py-4"> |
| | 905 | <Text className="text-xs leading-normal text-foreground-muted"> |
| | 906 | Native diff view unavailable. Showing the raw patch. |
| | 907 | </Text> |
| | 908 | <ScrollView horizontal showsHorizontalScrollIndicator={false} bounces={false}> |
| | 909 | <Text selectable className="font-mono text-xs leading-relaxed text-foreground"> |
| | 910 | {selectedSection?.diff ?? ""} |
| | 911 | </Text> |
| | 912 | </ScrollView> |
| | 913 | </View> |
| 870 | ) : null} | 914 | ) : null} |
| 871 | </ScrollView> | 915 | </ScrollView> |
| 872 | )} | 916 | )} |