Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e999f03
Split app TypeScript into web and native programs
roryabraham Aug 26, 2026
3242849
Fix knip, spellcheck, and React Compiler CI
roryabraham Aug 26, 2026
d7cdfb4
Widen native PopoverProvider action stubs
roryabraham Aug 26, 2026
7c7ba8f
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 1, 2026
29a7c31
Fix lint project mappings and casts
roryabraham Sep 1, 2026
62eca1f
Include linted mocks in TypeScript project
roryabraham Sep 1, 2026
c8b3dd7
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 1, 2026
748136a
Exclude mocks from app global restrictions
roryabraham Sep 1, 2026
d5b7855
Fix lint mocks and hybrid app tests
roryabraham Sep 2, 2026
624980b
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 2, 2026
0b562dc
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 2, 2026
4d91cd3
Merge origin/main into tsconfig cleanup
roryabraham Sep 2, 2026
11a310c
Fix hybrid app actions test mock
roryabraham Sep 2, 2026
f4bf402
Update generated GitHub Actions bundles
roryabraham Sep 2, 2026
8bb854c
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 3, 2026
feff88d
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 3, 2026
0c5f106
chore: keep required lint project mappings
roryabraham Sep 3, 2026
22bf2b7
chore: lint Nitro sources with module config
roryabraham Sep 3, 2026
332cd0b
fix: narrow font size style type
roryabraham Sep 3, 2026
9b2842b
test: restore Hybrid App mock setup
roryabraham Sep 3, 2026
d5c1434
refactor: share platform no-op contracts
roryabraham Sep 3, 2026
dc09e55
fix: align lint and Jest project roots
roryabraham Sep 3, 2026
9bfd921
fix: keep monthly submit date on current day
roryabraham Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions config/eslint/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,12 @@ const config = defineConfig([

{
files: ['modules/ExpensifyNitroUtils/src/**/*'],
languageOptions: {
parserOptions: {
project: path.resolve(projectRoot, 'modules/ExpensifyNitroUtils/tsconfig.json'),
projectService: false,
},
},
rules: {
'@typescript-eslint/consistent-type-definitions': 'off',
},
Expand All @@ -734,6 +740,16 @@ const config = defineConfig([
},
},

{
files: ['types/env.d.ts'],
languageOptions: {
parserOptions: {
project: path.resolve(projectRoot, 'tsconfig.app.web.json'),
projectService: false,
},
},
},

{
files: ['scripts/**/*.ts', 'tests/tooling/**/*.ts', 'server/{libs,plugins,stubs}/**/*.{ts,tsx}', 'evals/**/*.ts'],
languageOptions: {
Expand Down
3 changes: 1 addition & 2 deletions evals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ Two things about the wiring are load-bearing and easy to break:
explicit paths too, so running the evals on purpose means overriding it with a pattern that matches
nothing.

`evals/` has its own `tsconfig.json` rather than joining the root project, because `@types/bun`'s
globals conflict with the app's. It isn't in the set of projects `npm run typecheck` gates on, so typecheck it with `npm run typecheck -- evals`.
`evals/` is type-checked as part of the Bun project (`tsconfig.bun.json`) and is included in `npm run typecheck`.

## ProposalPolice

Expand Down
4 changes: 2 additions & 2 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import setupMockImages from './setupMockImages';

// Needed for tests to have the necessary environment variables set
if (!('GITHUB_REPOSITORY' in process.env)) {
(process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY_OWNER = 'Expensify';
(process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY = 'Expensify/App';
process.env.GITHUB_REPOSITORY_OWNER = 'Expensify';
process.env.GITHUB_REPOSITORY = 'Expensify/App';
}

setupMockImages();
Expand Down
9 changes: 8 additions & 1 deletion scripts/typecheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ const projectRoot = `${import.meta.dir}/..`;
const tsc = `${projectRoot}/node_modules/typescript/bin/tsc`;

/** TypeScript projects, relative to the repo root, that `npm run typecheck` and CI check. */
const DEFAULT_PROJECTS = ['tsconfig.json', 'tsconfig.jest.json', 'tsconfig.bun.json', 'tsconfig.node.json', 'server/victory-chart-renderer/tsconfig.json'];
const DEFAULT_PROJECTS = [
'tsconfig.app.web.json',
'tsconfig.app.native.json',
'tsconfig.jest.json',
'tsconfig.bun.json',
'tsconfig.node.json',
'server/victory-chart-renderer/tsconfig.json',
];

const cli = new CLI({
positionalArgs: [
Expand Down
3 changes: 2 additions & 1 deletion server/victory-chart-renderer/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"compilerOptions": {
"types": ["react-native", "react-native-web", "node"]
},
"include": ["src", "tests", "scripts", "../../src/types"]
"include": ["src", "tests", "scripts", "../../src/types/**/*.d.ts"],
"exclude": ["**/node_modules/*", "**/dist/*", "../../src/types/native/**"]
}
2 changes: 1 addition & 1 deletion src/CONFIG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default {
},
CAPTURE_METRICS: get(Config, 'CAPTURE_METRICS', 'false') === 'true',
ONYX_METRICS: get(Config, 'ONYX_METRICS', 'false') === 'true',
DEV_PORT: process.env.PORT ?? 8082,
DEV_PORT: Number(process.env.PORT) || 8082,
SEND_CRASH_REPORTS: get(Config, 'SEND_CRASH_REPORTS', 'false') === 'true',
IS_USING_WEB_PROXY: getPlatform() === 'web' && useWebProxy,
APPLE_SIGN_IN: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/AttachmentPicker/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ function AttachmentPicker({
* @param {function} imagePickerFunc - RNImagePicker.launchCamera or RNImagePicker.launchImageLibrary
*/
const showImagePicker = useCallback(
(imagePickerFunc: (options: CameraOptions, callback: Callback) => Promise<ImagePickerResponse>): Promise<Asset[] | void> =>
(imagePickerFunc: (options: CameraOptions, callback: Callback) => void | Promise<ImagePickerResponse>): Promise<Asset[] | void> =>
new Promise((resolve, reject) => {
imagePickerFunc(getImagePickerOptions(type, fileLimit), (response: ImagePickerResponse) => {
if (response.didCancel) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,3 @@ function AutoCompleteSuggestionsPortal<TSuggestion>({
}

export default AutoCompleteSuggestionsPortal;
export type {AutoCompleteSuggestionsPortalProps};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import React, {useCallback, useEffect, useRef} from 'react';
import {FlatList} from 'react-native-gesture-handler';
import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';

import type {AutoCompleteSuggestionsPortalProps} from './AutoCompleteSuggestionsPortal';
import type {AutoCompleteSuggestionsPortalProps} from './AutoCompleteSuggestionsPortal/types';
import type {RenderSuggestionMenuItemProps} from './types';

type ExternalProps<TSuggestion> = Omit<AutoCompleteSuggestionsPortalProps<TSuggestion>, 'left' | 'bottom'>;
Expand All @@ -37,7 +37,7 @@ function BaseAutoCompleteSuggestionsImpl({
const rowHeight = useSharedValue(0);
const prevRowHeightRef = useRef<number>(measuredHeightOfSuggestionRows);
const fadeInOpacity = useSharedValue(0);
const scrollRef = useRef<FlatList<unknown>>(null);
const scrollRef = useRef(null);
/**
* Render a suggestion menu item component.
*/
Expand Down Expand Up @@ -94,7 +94,7 @@ function BaseAutoCompleteSuggestionsImpl({
}
// When using cursor control (moving the cursor with the space bar on the keyboard) on Android, moving the cursor too fast may cause an error.
try {
scrollRef.current.scrollToIndex({index: highlightedSuggestionIndex, animated: true});
(scrollRef.current as {scrollToIndex?: (params: {index: number; animated?: boolean}) => void} | null)?.scrollToIndex?.({index: highlightedSuggestionIndex, animated: true});
} catch (e) {}
}, [highlightedSuggestionIndex]);

Expand Down
5 changes: 5 additions & 0 deletions src/components/DragAndDrop/Consumer/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import type {ReactNode} from 'react';

import type DragAndDropConsumerProps from './types';

function DragAndDropConsumer(props: DragAndDropConsumerProps): ReactNode;
function DragAndDropConsumer() {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type IconAsset from '@src/types/utils/IconAsset';
import type {ImageContentFit} from 'expo-image';
import type {SourceLoadEventPayload} from 'expo-video';
import type LottieView from 'lottie-react-native';
import type {ComponentRef} from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import type {MergeExclusive} from 'type-fest';

Expand Down Expand Up @@ -94,7 +95,7 @@ function Illustration({
const [measuredAspectRatio, setMeasuredAspectRatio] = useState(aspectRatioProp ?? VIDEO_ASPECT_RATIO);
const isInLandscapeMode = isInLandscapeModeUtil(windowWidth, windowHeight);

const animationRef = useRef<LottieView | null>(null);
const animationRef = useRef<ComponentRef<typeof LottieView> | null>(null);
useEffect(() => {
if (isMobile() || !animationRef.current || isReduceMotionEnabled) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/components/FormElement/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import {View} from 'react-native';

import type {FormElementProps} from './index';
import type FormElementProps from './types';

function FormElement({ref, ...props}: FormElementProps) {
return (
Expand Down
9 changes: 1 addition & 8 deletions src/components/FormElement/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import {ACCESSIBILITY_ROLE_FORM} from '@libs/ComponentUtils/index';
import mergeRefs from '@libs/mergeRefs';

import type {ForwardedRef} from 'react';
import type {ViewProps} from 'react-native';

import React, {useEffect, useRef} from 'react';
import {View} from 'react-native';

type FormElementProps = ViewProps & {
ref?: ForwardedRef<View>;
};
import type FormElementProps from './types';

const preventFormDefault = (event: SubmitEvent) => {
// When Enter is pressed, the form is submitted to the action URL (POST /).
Expand Down Expand Up @@ -51,5 +46,3 @@ function FormElement({ref, ...props}: FormElementProps) {
}

export default FormElement;

export type {FormElementProps};
8 changes: 8 additions & 0 deletions src/components/FormElement/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type {ForwardedRef} from 'react';
import type {View, ViewProps} from 'react-native';

type FormElementProps = ViewProps & {
ref?: ForwardedRef<View>;
};

export default FormElementProps;
2 changes: 1 addition & 1 deletion src/components/HeaderWithBackButton/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {ExpensifyIconName} from '@components/Icon/ExpensifyIconLoader';
import type {PopoverMenuItem} from '@components/PopoverMenu';

import type {Action} from '@hooks/useSingleExecution';
import type Action from '@hooks/useSingleExecution/types';

import type CONST from '@src/CONST';
import type {StepCounterParams} from '@src/languages/params';
Expand Down
2 changes: 1 addition & 1 deletion src/components/Icon/PlaceholderIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function PlaceholderIcon({width = 24, height = 24, fill, style, testID}: SvgProp
height={height}
viewBox="0 0 24 24"
fill={fill}
style={style}
style={style ? [style] : undefined}
testID={testID}
>
{/* Completely empty - no visible content, just maintains dimensions */}
Expand Down
6 changes: 3 additions & 3 deletions src/components/Lottie/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import CONST from '@src/CONST';
import {useSplashScreenState} from '@src/SplashScreenStateContext';

import type {AnimationObject, LottieViewProps} from 'lottie-react-native';
import type {ForwardedRef} from 'react';
import type {ComponentRef, ForwardedRef} from 'react';

import {NavigationContainerRefContext, NavigationContext} from '@react-navigation/native';
import LottieView from 'lottie-react-native';
import React, {useContext, useEffect, useRef, useState} from 'react';
import {View} from 'react-native';

type Props = {
ref?: ForwardedRef<LottieView | null>;
ref?: ForwardedRef<ComponentRef<typeof LottieView> | null>;
source: DotLottieAnimation;
shouldLoadAfterInteractions?: boolean;
} & Omit<LottieViewProps, 'source'>;

function Lottie({ref, source, webStyle, shouldLoadAfterInteractions, ...props}: Props) {
const animationRef = useRef<LottieView | null>(null);
const animationRef = useRef<ComponentRef<typeof LottieView> | null>(null);
const appState = useAppState();
const {splashScreenState} = useSplashScreenState();
const styles = useThemeStyles();
Expand Down
6 changes: 4 additions & 2 deletions src/components/MapView/Icons/MapMarkerShadowFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ type MapMarkerShadowFilterProps = {
height: string;
};

const SvgDefs = Defs as React.ComponentType<{children?: React.ReactNode}>;

function MapMarkerShadowFilter({id, width, height}: MapMarkerShadowFilterProps) {
return (
<Defs>
<SvgDefs>
<Filter
id={id}
x="0"
Expand All @@ -26,7 +28,7 @@ function MapMarkerShadowFilter({id, width, height}: MapMarkerShadowFilterProps)
floodOpacity={0.06}
/>
</Filter>
</Defs>
</SvgDefs>
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/MenuItemGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useSingleExecution from '@hooks/useSingleExecution';
import type {Action} from '@hooks/useSingleExecution';
import type Action from '@hooks/useSingleExecution/types';
import useWaitForNavigation from '@hooks/useWaitForNavigation';

import React, {createContext, useContext} from 'react';
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modal/ReanimatedModal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type GestureHandlerProps = {
onSwipeComplete?: () => void;

/** Threshold for swipe gesture. */
swipeThreshold: number;
swipeThreshold?: number;

/** Threshold for swipe gesture. */
swipeDirection?: SwipeDirection | SwipeDirection[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
import type UseSyncModalWithHistory from './types';

// Modal back-guard synchronization with the browser history is only supported for web.
export default function useSyncModalWithHistory() {}
const useSyncModalWithHistory: UseSyncModalWithHistory = () => {};

export default useSyncModalWithHistory;
15 changes: 1 addition & 14 deletions src/components/Modal/useSyncModalWithHistory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,11 @@ import CONST from '@src/CONST';
import {useEffect, useEffectEvent, useId, useRef, useSyncExternalStore} from 'react';

import type {ModalGuardState} from './modalGuardState';
import type {UseSyncModalWithHistoryParams} from './types';

import {EMPTY_MODAL_GUARD_SNAPSHOT_KEY, getModalGuardSnapshotKey, parseModalGuardSnapshotKey} from './modalGuardSnapshot';
import reduceModalGuardState, {getModalGuardEventFromSnapshotChange, MODAL_GUARD_EFFECT, MODAL_GUARD_STATE} from './modalGuardState';

type UseSyncModalWithHistoryParams = {
/** Whether the modal is currently visible */
isVisible: boolean;

/** Whether this modal participates in browser-history back handling */
shouldHandleNavigationBack?: boolean;

/** Called when a browser Back press removes this modal's history entry */
onClose?: () => void;

/** Called when browser Forward navigation restores this modal's history entry while the modal is closed */
onOpen?: () => void;
};

/**
* Web: represents a `shouldHandleNavigationBack` modal's back-guard as a uniquely-tagged history entry in the
* root navigator's `state.history` (dispatched via `TOGGLE_MODAL_WITH_HISTORY`). React Navigation's
Expand Down
18 changes: 18 additions & 0 deletions src/components/Modal/useSyncModalWithHistory/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type UseSyncModalWithHistoryParams = {
/** Whether the modal is currently visible */
isVisible: boolean;

/** Whether this modal participates in browser-history back handling */
shouldHandleNavigationBack?: boolean;

/** Called when a browser Back press removes this modal's history entry */
onClose?: () => void;

/** Called when browser Forward navigation restores this modal's history entry while the modal is closed */
onOpen?: () => void;
};

type UseSyncModalWithHistory = (params: UseSyncModalWithHistoryParams) => void;

export default UseSyncModalWithHistory;
export type {UseSyncModalWithHistoryParams};
2 changes: 1 addition & 1 deletion src/components/PDFView/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ function PDFView({onToggleKeyboard, onLoadComplete, fileName, onPress, isFocused
onError={handleFailureToLoadPDF}
password={password}
onLoadComplete={finishPDFLoad}
onPageSingleTap={onPress}
onPageSingleTap={() => onPress?.()}
onScaleChanged={onScaleChanged}
onPressLink={handlePressLink}
/>
Expand Down
4 changes: 1 addition & 3 deletions src/components/PDFView/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ type PDFViewProps = {
rotation?: RotationDegrees;
};

type PDFViewNativeProps = PDFViewProps & {
onPress?: (page: number, x: number, y: number) => void;
};
type PDFViewNativeProps = PDFViewProps;

export type {PDFViewNativeProps, PDFViewProps};
6 changes: 3 additions & 3 deletions src/components/PopoverProvider/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ type PopoverStateContextType = {
};

type PopoverActionsContextType = {
onOpen: () => void;
close: () => void;
setActivePopoverExtraAnchorRef: () => void;
onOpen: (popoverParams?: unknown) => void;
close: (anchorRef?: unknown) => void;
setActivePopoverExtraAnchorRef: (ref?: unknown) => void;
};

const defaultPopoverActionsContext: PopoverActionsContextType = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ function ReportActionItemImages({images, size, total, isHovered = false, onPress
<Svg
height={triangleWidth}
width={triangleWidth}
style={styles.reportActionItemImagesMoreCornerTriangle}
style={[styles.reportActionItemImagesMoreCornerTriangle]}
>
<Polygon
points={`${triangleWidth},0 ${triangleWidth},${triangleWidth} 0,${triangleWidth}`}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {IsElementHovered, ResetButtonHoverState} from './types';

// No-op on native — hover states don't exist on mobile
function resetButtonHoverState() {}
const resetButtonHoverState: ResetButtonHoverState = () => {};

function isElementHovered(): boolean {
return false;
}
const isElementHovered: IsElementHovered = () => false;

export {resetButtonHoverState, isElementHovered};
Loading
Loading