Skip to content

Commit 33d0c96

Browse files
chore(lint): every React effect states its reason at the call site (#26161)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4cb5f10 commit 33d0c96

270 files changed

Lines changed: 611 additions & 628 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ Architecture and its constraints: `docs/internals/overview.md`. Glossary: `docs/
199199
options, and pass combinators as additional function-builder arguments.
200200
- Inferred types over annotations. `any` is the enemy.
201201
- Default to no comment. Write one only for a constraint, an external quirk, or an invariant the code can't show, in one line where you can and four at most. Don't restate names or signatures, narrate steps, record history, or explain the fix you just made; the PR carries that. `t3code/no-low-value-comments` enforces the length cap and the mechanical cases.
202-
- React effects are for synchronizing with something outside React. Derive in render and act in handlers instead of syncing state, but don't hide real lifecycle work in refs or stores to dodge one. The `t3code/react-effect-budget` lint rule holds each file to its count in `scripts/effect-budget.json`; a new effect states its reason with `// oxlint-disable-next-line t3code/react-effect-budget -- <why>`.
202+
- React effects are for synchronizing with something outside React. Derive in render and act in handlers instead of syncing state, but don't hide real lifecycle work in refs or stores to dodge one. The `t3code/no-unreasoned-state-sync` lint rule reports every effect; one that stays states its reason with `// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- <why>`.
203203
- Our users drive agents all day and notice a dropped frame, a lying spinner, and a stale label. No continuously repainting animations; they peg the GPU on high-refresh displays.
204204
- If a rule here fights the task in front of you, say so loudly and get a human sign-off before breaking it.
205205

‎apps/mobile/src/App.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ disposeOnFoundationReplace(typeof module === "undefined" ? undefined : module.ho
195195
function SplashScreenCoordinator() {
196196
const { isReady } = useAppearancePreferences();
197197

198+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- hides the native splash screen once appearance preferences have loaded
198199
useEffect(() => {
199200
if (isReady) void SplashScreen.hide();
200201
}, [isReady]);

‎apps/mobile/src/components/ComposerEditor.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export function ComposerEditor({
6161
? preferencesResult.value.composerEnterBehavior
6262
: undefined;
6363
const contextHistory = createComposerDraftContextHistory();
64+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- releases the attachment file leases held for native text undo when the history is replaced or unmounts
6465
useEffect(() => () => contextHistory.dispose(), [contextHistory]);
6566
const changeText = (text: string) => {
6667
const restored = contextHistory.restore(

‎apps/mobile/src/components/FilePreview.ios.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function NativeFilePreview(props: {
3434
else Dialog.alert("Could not open preview", "The file could not be loaded. Please try again.");
3535
});
3636

37+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- presents the native file preview and dismisses it on cleanup
3738
useEffect(() => {
3839
let canceled = false;
3940
const presentation =

‎apps/mobile/src/components/FilePreview.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ function DocumentPreview(props: {
5555
"A compatible viewer must be installed. Check your connection and try again.",
5656
);
5757
});
58+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- opens the attachment in an external viewer and aborts the open on cleanup
5859
useEffect(() => {
5960
const controller = new AbortController();
6061
const input = {

‎apps/mobile/src/components/FilePreviewModal.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ function ResolvedFilePreview(props: {
8686
props.onRequestClose();
8787
});
8888
// Resolves once per presentation and holds local file leases until it closes.
89+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- resolves preview files and holds their local file leases until the preview closes
8990
useEffect(() => {
9091
if (items.every((item) => "uri" in item)) return;
9192
const controller = new AbortController();

‎apps/mobile/src/components/MediaVideoPlayer.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function LoadedMediaVideo(props: {
3333
useMediaVideoPlayback(player, props, playback);
3434
const previousPlaybackRequested = useRef(false);
3535
const onPlaybackStart = useEffectEvent(() => props.onPlaybackStart?.());
36+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- subscribes to native video player events to keep the shared audio session in step
3637
useEffect(() => {
3738
const subscription = player.addListener("playingChange", ({ isPlaying }) => {
3839
if (isPlaying && !active.current && !fullscreen.current) playback.pause();
@@ -74,6 +75,7 @@ function LoadedMediaVideo(props: {
7475
if (!signal.aborted && props.playRequested) await playIfUninterrupted();
7576
});
7677

78+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- loads the video source into the native player and aborts the load on cleanup
7779
useEffect(() => {
7880
const controller = new AbortController();
7981
// oxlint-disable-next-line react/set-state-in-effect -- Starting a native media load publishes pending before its async result.

‎apps/mobile/src/components/ProjectFavicon.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ function ProjectFaviconImage(props: {
165165
);
166166
// Registers with the module-level loader before the Image's native onLoad/onError can fire,
167167
// so stale loads for a superseded URL under the same cacheKey are ignored.
168+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- marks this favicon URL as the current request in the module registry before the native Image reports load or error
168169
useLayoutEffect(() => {
169170
if (faviconRequest === null) return;
170171
return beginProjectFaviconRequest(faviconRequest);

‎apps/mobile/src/components/VideoPreviewModal.ios.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ function NativeVideoPreview(props: {
6565
}
6666
});
6767

68+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- presents the native fullscreen video and owns its audio session and file lease until cleanup
6869
useEffect(() => {
6970
const controller = new AbortController();
7071
let token: ReturnType<typeof acquireAudioSession> | null = null;

‎apps/mobile/src/components/ui/Notice.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function Notice(props: {
1616
readonly dismissLabel?: string;
1717
}) {
1818
const danger = props.tone === "danger";
19+
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- announces the message to VoiceOver through AccessibilityInfo
1920
useEffect(() => {
2021
// Live regions reach TalkBack; VoiceOver needs an explicit announcement.
2122
if (Platform.OS === "ios") AccessibilityInfo.announceForAccessibility(props.message);

0 commit comments

Comments
 (0)