Skip to content

Commit 779c237

Browse files
[codex] Structure native view resolution failures (#3353)
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 350e229 commit 779c237

5 files changed

Lines changed: 71 additions & 4 deletions

File tree

apps/mobile/src/features/diffs/nativeReviewDiffSurface.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,23 @@ describe("resolveNativeReviewDiffView", () => {
5858

5959
it("returns null when the view manager cannot be required", async () => {
6060
setExpoViewConfigAvailable();
61+
const cause = new Error("boom");
6162
expoMocks.requireNativeView.mockImplementation(() => {
62-
throw new Error("boom");
63+
throw cause;
6364
});
65+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
6466
const { resolveNativeReviewDiffView } = await import("./nativeReviewDiffSurface");
67+
68+
expect(resolveNativeReviewDiffView()).toBeNull();
6569
expect(resolveNativeReviewDiffView()).toBeNull();
70+
expect(expoMocks.requireNativeView).toHaveBeenCalledTimes(1);
71+
expect(consoleError).toHaveBeenCalledWith(
72+
expect.objectContaining({
73+
_tag: "NativeViewResolutionError",
74+
nativeModuleName: "T3ReviewDiffSurface",
75+
cause,
76+
}),
77+
);
78+
expect(consoleError).toHaveBeenCalledTimes(1);
6679
});
6780
});

apps/mobile/src/features/diffs/nativeReviewDiffSurface.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { ComponentType } from "react";
22
import type { NativeSyntheticEvent, ViewProps } from "react-native";
33
import { requireNativeView } from "expo";
44

5+
import { NativeViewResolutionError } from "../../native/nativeViewResolutionError";
6+
57
const NATIVE_REVIEW_DIFF_MODULE_NAME = "T3ReviewDiffSurface";
68

79
interface ExpoGlobalWithViewConfig {
@@ -128,6 +130,7 @@ export interface NativeReviewDiffViewProps extends ViewProps {
128130
}
129131

130132
let cachedNativeReviewDiffView: ComponentType<NativeReviewDiffViewProps> | undefined;
133+
let nativeReviewDiffViewResolutionFailed = false;
131134

132135
function getExpoViewConfig(moduleName: string) {
133136
return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.(
@@ -140,6 +143,10 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie
140143
return cachedNativeReviewDiffView;
141144
}
142145

146+
if (nativeReviewDiffViewResolutionFailed) {
147+
return null;
148+
}
149+
143150
if (getExpoViewConfig(NATIVE_REVIEW_DIFF_MODULE_NAME) == null) {
144151
return null;
145152
}
@@ -148,7 +155,14 @@ export function resolveNativeReviewDiffView(): ComponentType<NativeReviewDiffVie
148155
cachedNativeReviewDiffView = requireNativeView<NativeReviewDiffViewProps>(
149156
NATIVE_REVIEW_DIFF_MODULE_NAME,
150157
);
151-
} catch {
158+
} catch (cause) {
159+
nativeReviewDiffViewResolutionFailed = true;
160+
console.error(
161+
new NativeViewResolutionError({
162+
nativeModuleName: NATIVE_REVIEW_DIFF_MODULE_NAME,
163+
cause,
164+
}),
165+
);
152166
return null;
153167
}
154168

apps/mobile/src/features/terminal/nativeTerminalModule.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,23 @@ describe("resolveNativeTerminalSurfaceView", () => {
4343

4444
it("returns null when the view manager cannot be required", async () => {
4545
setExpoViewConfigAvailable();
46+
const cause = new Error("boom");
4647
expoMocks.requireNativeView.mockImplementation(() => {
47-
throw new Error("boom");
48+
throw cause;
4849
});
50+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
4951
const { resolveNativeTerminalSurfaceView } = await import("./nativeTerminalModule");
52+
53+
expect(resolveNativeTerminalSurfaceView()).toBeNull();
5054
expect(resolveNativeTerminalSurfaceView()).toBeNull();
55+
expect(expoMocks.requireNativeView).toHaveBeenCalledTimes(1);
56+
expect(consoleError).toHaveBeenCalledWith(
57+
expect.objectContaining({
58+
_tag: "NativeViewResolutionError",
59+
nativeModuleName: "T3TerminalSurface",
60+
cause,
61+
}),
62+
);
63+
expect(consoleError).toHaveBeenCalledTimes(1);
5164
});
5265
});

apps/mobile/src/features/terminal/nativeTerminalModule.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { ComponentType } from "react";
22
import type { NativeSyntheticEvent, ViewProps } from "react-native";
33
import { requireNativeView } from "expo";
44

5+
import { NativeViewResolutionError } from "../../native/nativeViewResolutionError";
6+
57
const NATIVE_TERMINAL_MODULE_NAME = "T3TerminalSurface";
68

79
interface ExpoGlobalWithViewConfig {
@@ -33,6 +35,7 @@ export interface NativeTerminalSurfaceProps extends ViewProps {
3335
}
3436

3537
let cachedNativeTerminalSurfaceView: ComponentType<NativeTerminalSurfaceProps> | undefined;
38+
let nativeTerminalSurfaceViewResolutionFailed = false;
3639

3740
function getExpoViewConfig(moduleName: string) {
3841
return (globalThis as typeof globalThis & ExpoGlobalWithViewConfig).expo?.getViewConfig?.(
@@ -45,6 +48,10 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal
4548
return cachedNativeTerminalSurfaceView;
4649
}
4750

51+
if (nativeTerminalSurfaceViewResolutionFailed) {
52+
return null;
53+
}
54+
4855
if (getExpoViewConfig(NATIVE_TERMINAL_MODULE_NAME) == null) {
4956
return null;
5057
}
@@ -53,7 +60,14 @@ export function resolveNativeTerminalSurfaceView(): ComponentType<NativeTerminal
5360
cachedNativeTerminalSurfaceView = requireNativeView<NativeTerminalSurfaceProps>(
5461
NATIVE_TERMINAL_MODULE_NAME,
5562
);
56-
} catch {
63+
} catch (cause) {
64+
nativeTerminalSurfaceViewResolutionFailed = true;
65+
console.error(
66+
new NativeViewResolutionError({
67+
nativeModuleName: NATIVE_TERMINAL_MODULE_NAME,
68+
cause,
69+
}),
70+
);
5771
return null;
5872
}
5973

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Schema from "effect/Schema";
2+
3+
export class NativeViewResolutionError extends Schema.TaggedErrorClass<NativeViewResolutionError>()(
4+
"NativeViewResolutionError",
5+
{
6+
nativeModuleName: Schema.String,
7+
cause: Schema.Defect(),
8+
},
9+
) {
10+
override get message(): string {
11+
return `Failed to resolve native view ${this.nativeModuleName}.`;
12+
}
13+
}

0 commit comments

Comments
 (0)