diff --git a/packages/desktop-client/e2e/page-models/mobile-account-page.js b/packages/desktop-client/e2e/page-models/mobile-account-page.js
index 9e58489fb74eb2ca7e494015cd0c44006bbc745f..0d831015c3bdd94f8e74b6758416f959f9b92bda 100644
--- a/packages/desktop-client/e2e/page-models/mobile-account-page.js
+++ b/packages/desktop-client/e2e/page-models/mobile-account-page.js
@@ -5,7 +5,7 @@ export class MobileAccountPage {
     this.page = page;
 
     this.heading = page.getByRole('heading');
-    this.balance = page.getByTestId('account-balance');
+    this.balance = page.getByTestId('transactions-balance');
     this.noTransactionsFoundError = page.getByText('No transactions');
     this.searchBox = page.getByPlaceholder(/^Search/);
     this.transactionList = page.getByLabel('transaction list');
diff --git a/packages/desktop-client/src/components/App.tsx b/packages/desktop-client/src/components/App.tsx
index 5fc229e140bda313fdf690bb95b98f1e55d52266..5a63cdd58b740295ed26147bbdf4ab1ecf67103f 100644
--- a/packages/desktop-client/src/components/App.tsx
+++ b/packages/desktop-client/src/components/App.tsx
@@ -27,7 +27,7 @@ import { DevelopmentTopBar } from './DevelopmentTopBar';
 import { FatalError } from './FatalError';
 import { FinancesApp } from './FinancesApp';
 import { ManagementApp } from './manager/ManagementApp';
-import { MobileWebMessage } from './MobileWebMessage';
+import { MobileWebMessage } from './mobile/MobileWebMessage';
 import { UpdateNotification } from './UpdateNotification';
 
 type AppInnerProps = {
diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx
index 2a1bc50a06705a66a37b6c86fba169959da07a6e..4718a93d46536fb3ad1ebd2b5219990f0581ee08 100644
--- a/packages/desktop-client/src/components/FinancesApp.tsx
+++ b/packages/desktop-client/src/components/FinancesApp.tsx
@@ -30,6 +30,7 @@ import { BudgetMonthCountProvider } from './budget/BudgetMonthCountContext';
 import { View } from './common/View';
 import { GlobalKeys } from './GlobalKeys';
 import { ManageRulesPage } from './ManageRulesPage';
+import { Category } from './mobile/budget/Category';
 import { MobileNavTabs } from './mobile/MobileNavTabs';
 import { TransactionEdit } from './mobile/transactions/TransactionEdit';
 import { Modals } from './Modals';
@@ -210,7 +211,7 @@ function FinancesAppWithoutContext() {
                 />
 
                 <Route
-                  path="/accounts/:id/transactions/:transactionId"
+                  path="/transactions/:transactionId"
                   element={
                     <WideNotSupported>
                       <TransactionEdit />
@@ -219,18 +220,10 @@ function FinancesAppWithoutContext() {
                 />
 
                 <Route
-                  path="/accounts/:id/transactions/new"
+                  path="/categories/:id"
                   element={
                     <WideNotSupported>
-                      <TransactionEdit />
-                    </WideNotSupported>
-                  }
-                />
-                <Route
-                  path="/transactions/new"
-                  element={
-                    <WideNotSupported>
-                      <TransactionEdit />
+                      <Category />
                     </WideNotSupported>
                   }
                 />
diff --git a/packages/desktop-client/src/components/accounts/Account.jsx b/packages/desktop-client/src/components/accounts/Account.jsx
index 010949fc6cb58463e4456ba356c0cb3edbf6b6ce..25bef745319603d09b305b92ccfc6e63fb53ceb1 100644
--- a/packages/desktop-client/src/components/accounts/Account.jsx
+++ b/packages/desktop-client/src/components/accounts/Account.jsx
@@ -8,10 +8,7 @@ import { bindActionCreators } from 'redux';
 import { validForTransfer } from 'loot-core/client/transfer';
 import * as actions from 'loot-core/src/client/actions';
 import { useFilters } from 'loot-core/src/client/data-hooks/filters';
-import {
-  SchedulesProvider,
-  useCachedSchedules,
-} from 'loot-core/src/client/data-hooks/schedules';
+import { SchedulesProvider } from 'loot-core/src/client/data-hooks/schedules';
 import * as queries from 'loot-core/src/client/queries';
 import { runQuery, pagedQuery } from 'loot-core/src/client/query-helpers';
 import { send, listen } from 'loot-core/src/platform/client/fetch';
@@ -33,6 +30,7 @@ import { useDateFormat } from '../../hooks/useDateFormat';
 import { useFailedAccounts } from '../../hooks/useFailedAccounts';
 import { useLocalPref } from '../../hooks/useLocalPref';
 import { usePayees } from '../../hooks/usePayees';
+import { usePreviewTransactions } from '../../hooks/usePreviewTransactions';
 import { SelectedProviderWithItems } from '../../hooks/useSelected';
 import {
   SplitsExpandedProvider,
@@ -94,38 +92,14 @@ function AllTransactions({
   filtered,
   children,
 }) {
-  const { id: accountId } = account;
-  const scheduleData = useCachedSchedules();
+  const accountId = account.id;
+  const prependTransactions = usePreviewTransactions().map(trans => ({
+    ...trans,
+    _inverse: accountId ? accountId !== trans.account : false,
+  }));
 
   transactions ??= [];
 
-  const schedules = useMemo(
-    () =>
-      scheduleData
-        ? scheduleData.schedules.filter(
-            s =>
-              !s.completed &&
-              ['due', 'upcoming', 'missed'].includes(
-                scheduleData.statuses.get(s.id),
-              ),
-          )
-        : [],
-    [scheduleData],
-  );
-
-  const prependTransactions = useMemo(() => {
-    return schedules.map(schedule => ({
-      id: `preview/${schedule.id}`,
-      payee: schedule._payee,
-      account: schedule._account,
-      amount: schedule._amount,
-      date: schedule.next_date,
-      notes: scheduleData.statuses.get(schedule.id),
-      schedule: schedule.id,
-      _inverse: accountId ? accountId !== schedule._account : false,
-    }));
-  }, [schedules, accountId]);
-
   let runningBalance = useMemo(() => {
     if (!showBalances) {
       return 0;
@@ -172,7 +146,7 @@ function AllTransactions({
     return balances;
   }, [filtered, prependBalances, balances]);
 
-  if (scheduleData == null) {
+  if (!prependTransactions) {
     return children(transactions, balances);
   }
   return children(allTransactions, allBalances);
diff --git a/packages/desktop-client/src/components/common/Link.tsx b/packages/desktop-client/src/components/common/Link.tsx
index d624bdaf6821722eeb85cfc87c99a1659c656a1f..1fd6a027742d283f85eb03d74bfa8aad5eda583f 100644
--- a/packages/desktop-client/src/components/common/Link.tsx
+++ b/packages/desktop-client/src/components/common/Link.tsx
@@ -102,7 +102,9 @@ const ButtonLink = ({ to, style, activeStyle, ...props }: ButtonLinkProps) => {
       {...props}
       onClick={e => {
         props.onClick?.(e);
-        navigate(path);
+        if (!e.defaultPrevented) {
+          navigate(path);
+        }
       }}
     />
   );
diff --git a/packages/desktop-client/src/components/MobileBackButton.tsx b/packages/desktop-client/src/components/mobile/MobileBackButton.tsx
similarity index 76%
rename from packages/desktop-client/src/components/MobileBackButton.tsx
rename to packages/desktop-client/src/components/mobile/MobileBackButton.tsx
index d13b504dc7222043566f226e6da169eb6f9c246e..f563dcab83457070cf84fb33fde1e48c51ff7175 100644
--- a/packages/desktop-client/src/components/MobileBackButton.tsx
+++ b/packages/desktop-client/src/components/mobile/MobileBackButton.tsx
@@ -1,11 +1,10 @@
 import React from 'react';
 
-import { useNavigate } from '../hooks/useNavigate';
-import { SvgCheveronLeft } from '../icons/v1';
-import { type CSSProperties, styles, theme } from '../style';
-
-import { Button } from './common/Button';
-import { Text } from './common/Text';
+import { useNavigate } from '../../hooks/useNavigate';
+import { SvgCheveronLeft } from '../../icons/v1';
+import { type CSSProperties, styles, theme } from '../../style';
+import { Button } from '../common/Button';
+import { Text } from '../common/Text';
 
 type MobileBackButtonProps = {
   style?: CSSProperties;
@@ -16,6 +15,7 @@ export function MobileBackButton({ style }: MobileBackButtonProps) {
   return (
     <Button
       type="bare"
+      aria-label="Back"
       style={{
         color: theme.mobileHeaderText,
         justifyContent: 'center',
diff --git a/packages/desktop-client/src/components/MobileWebMessage.tsx b/packages/desktop-client/src/components/mobile/MobileWebMessage.tsx
similarity index 88%
rename from packages/desktop-client/src/components/MobileWebMessage.tsx
rename to packages/desktop-client/src/components/mobile/MobileWebMessage.tsx
index ae4b17ebaacf3f78ea26d11fdf9bc8717208d15c..edf8d5ad0b96be6f32baa22b219e01b4237ef0e6 100644
--- a/packages/desktop-client/src/components/MobileWebMessage.tsx
+++ b/packages/desktop-client/src/components/mobile/MobileWebMessage.tsx
@@ -1,13 +1,12 @@
 import React, { useState } from 'react';
 
-import { useLocalPref } from '../hooks/useLocalPref';
-import { useResponsive } from '../ResponsiveProvider';
-import { theme, styles } from '../style';
-
-import { Button } from './common/Button';
-import { Text } from './common/Text';
-import { View } from './common/View';
-import { Checkbox } from './forms';
+import { useLocalPref } from '../../hooks/useLocalPref';
+import { useResponsive } from '../../ResponsiveProvider';
+import { theme, styles } from '../../style';
+import { Button } from '../common/Button';
+import { Text } from '../common/Text';
+import { View } from '../common/View';
+import { Checkbox } from '../forms';
 
 const buttonStyle = { border: 0, fontSize: 15, padding: '10px 13px' };
 
diff --git a/packages/desktop-client/src/components/mobile/accounts/Account.jsx b/packages/desktop-client/src/components/mobile/accounts/Account.jsx
index 4a07c391e6ede2b600d98f746b7ca4f4e882dd8d..20ad5bc9bb9cc8350cb9e338bd904108a1ec9fca 100644
--- a/packages/desktop-client/src/components/mobile/accounts/Account.jsx
+++ b/packages/desktop-client/src/components/mobile/accounts/Account.jsx
@@ -1,193 +1,36 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
+import React from 'react';
+import { useSelector } from 'react-redux';
 import { useParams } from 'react-router-dom';
 
-import memoizeOne from 'memoize-one';
-import { useDebounceCallback } from 'usehooks-ts';
-
-import * as actions from 'loot-core/src/client/actions';
-import {
-  SchedulesProvider,
-  useCachedSchedules,
-} from 'loot-core/src/client/data-hooks/schedules';
-import * as queries from 'loot-core/src/client/queries';
-import { pagedQuery } from 'loot-core/src/client/query-helpers';
-import { listen, send } from 'loot-core/src/platform/client/fetch';
-import {
-  isPreviewId,
-  ungroupTransactions,
-} from 'loot-core/src/shared/transactions';
-
-import { useAccounts } from '../../../hooks/useAccounts';
-import { useCategories } from '../../../hooks/useCategories';
-import { useDateFormat } from '../../../hooks/useDateFormat';
+import { useAccount } from '../../../hooks/useAccount';
 import { useFailedAccounts } from '../../../hooks/useFailedAccounts';
 import { useLocalPref } from '../../../hooks/useLocalPref';
-import { useLocalPrefs } from '../../../hooks/useLocalPrefs';
 import { useNavigate } from '../../../hooks/useNavigate';
-import { usePayees } from '../../../hooks/usePayees';
 import { useSetThemeColor } from '../../../hooks/useSetThemeColor';
 import { theme, styles } from '../../../style';
 import { Button } from '../../common/Button';
 import { Text } from '../../common/Text';
 import { View } from '../../common/View';
 
-import { AccountDetails } from './AccountDetails';
-
-const getSchedulesTransform = memoizeOne((id, hasSearch) => {
-  let filter = queries.getAccountFilter(id, '_account');
-
-  // Never show schedules on these pages
-  if (hasSearch) {
-    filter = { id: null };
-  }
-
-  return q => {
-    q = q.filter({ $and: [filter, { '_account.closed': false }] });
-    return q.orderBy({ next_date: 'desc' });
-  };
-});
-
-function PreviewTransactions({ children }) {
-  const scheduleData = useCachedSchedules();
-
-  if (scheduleData == null) {
-    return children(null);
-  }
-
-  const schedules = scheduleData.schedules.filter(
-    s =>
-      !s.completed &&
-      ['due', 'upcoming', 'missed'].includes(scheduleData.statuses.get(s.id)),
-  );
-
-  return children(
-    schedules.map(schedule => ({
-      id: 'preview/' + schedule.id,
-      payee: schedule._payee,
-      account: schedule._account,
-      amount: schedule._amount,
-      date: schedule.next_date,
-      notes: scheduleData.statuses.get(schedule.id),
-      schedule: schedule.id,
-    })),
-  );
-}
-
-export function Account(props) {
-  const accounts = useAccounts();
-  const payees = usePayees();
+import { AccountTransactions } from './AccountTransactions';
 
+export function Account() {
   const failedAccounts = useFailedAccounts();
   const syncingAccountIds = useSelector(state => state.account.accountsSyncing);
 
   const navigate = useNavigate();
-  const [transactions, setTransactions] = useState([]);
-  const [isSearching, setIsSearching] = useState(false);
-  const [currentQuery, setCurrentQuery] = useState();
 
-  const newTransactions = useSelector(state => state.queries.newTransactions);
-  const prefs = useLocalPrefs();
-  const dateFormat = useDateFormat() || 'MM/dd/yyyy';
   const [_numberFormat] = useLocalPref('numberFormat');
   const numberFormat = _numberFormat || 'comma-dot';
   const [hideFraction = false] = useLocalPref('hideFraction');
 
-  const state = {
-    payees,
-    newTransactions,
-    prefs,
-    dateFormat,
-  };
-
-  const dispatch = useDispatch();
-
   const { id: accountId } = useParams();
 
-  const makeRootQuery = useCallback(
-    () => queries.makeTransactionsQuery(accountId),
-    [accountId],
-  );
-
-  const paged = useRef(null);
-
-  const updateQuery = useCallback(query => {
-    paged.current?.unsubscribe();
-    paged.current = pagedQuery(
-      query.options({ splits: 'grouped' }).select('*'),
-      data => setTransactions(data),
-      { pageCount: 10, mapper: ungroupTransactions },
-    );
-  }, []);
-
-  const fetchTransactions = useCallback(async () => {
-    const query = makeRootQuery();
-    setCurrentQuery(query);
-    updateQuery(query);
-  }, [makeRootQuery, updateQuery]);
-
-  const refetchTransactions = () => {
-    paged.current?.run();
-  };
-
-  useEffect(() => {
-    let unlisten;
-
-    async function setUpAccount() {
-      unlisten = listen('sync-event', ({ type, tables }) => {
-        if (type === 'applied') {
-          if (
-            tables.includes('transactions') ||
-            tables.includes('category_mapping') ||
-            tables.includes('payee_mapping')
-          ) {
-            refetchTransactions();
-          }
-
-          if (tables.includes('payees') || tables.includes('payee_mapping')) {
-            dispatch(actions.getPayees());
-          }
-        }
-      });
-
-      await fetchTransactions();
-
-      dispatch(actions.markAccountRead(accountId));
-    }
-
-    setUpAccount();
-
-    return () => unlisten();
-  }, [accountId, dispatch, fetchTransactions]);
-
-  // Load categories if necessary.
-  const categories = useCategories();
-
-  const updateSearchQuery = useDebounceCallback(
-    useCallback(
-      searchText => {
-        if (searchText === '' && currentQuery) {
-          updateQuery(currentQuery);
-        } else if (searchText && currentQuery) {
-          updateQuery(
-            queries.makeTransactionSearchQuery(
-              currentQuery,
-              searchText,
-              dateFormat,
-            ),
-          );
-        }
-
-        setIsSearching(searchText !== '');
-      },
-      [currentQuery, dateFormat, updateQuery],
-    ),
-    150,
-  );
-
   useSetThemeColor(theme.mobileViewTheme);
 
-  if (!accounts || !accounts.length) {
+  const account = useAccount(accountId);
+
+  if (!account) {
     return null;
   }
 
@@ -212,77 +55,14 @@ export function Account(props) {
     );
   }
 
-  const account = accounts.find(acct => acct.id === accountId);
-
-  const isNewTransaction = id => {
-    return state.newTransactions.includes(id);
-  };
-
-  const onSearch = text => {
-    updateSearchQuery(text);
-  };
-
-  const onSelectTransaction = transaction => {
-    // details of how the native app used to handle preview transactions here can be found at commit 05e58279
-    if (!isPreviewId(transaction.id)) {
-      navigate(`transactions/${transaction.id}`);
-    } else {
-      dispatch(
-        actions.pushModal('scheduled-transaction-menu', {
-          transactionId: transaction.id,
-          onPost: async transactionId => {
-            const parts = transactionId.split('/');
-            await send('schedule/post-transaction', { id: parts[1] });
-            refetchTransactions();
-            dispatch(actions.collapseModals('scheduled-transaction-menu'));
-          },
-          onSkip: async transactionId => {
-            const parts = transactionId.split('/');
-            await send('schedule/skip-next-date', { id: parts[1] });
-            dispatch(actions.collapseModals('scheduled-transaction-menu'));
-          },
-        }),
-      );
-    }
-  };
-
-  const balance = queries.accountBalance(account);
-  const balanceCleared = queries.accountBalanceCleared(account);
-  const balanceUncleared = queries.accountBalanceUncleared(account);
-
   return (
-    <SchedulesProvider
-      transform={getSchedulesTransform(accountId, isSearching)}
-    >
-      <PreviewTransactions accountId={props.accountId}>
-        {prependTransactions =>
-          prependTransactions == null ? null : (
-            <AccountDetails
-              // This key forces the whole table rerender when the number
-              // format changes
-              {...state}
-              key={numberFormat + hideFraction}
-              account={account}
-              pending={syncingAccountIds.includes(account.id)}
-              failed={failedAccounts && failedAccounts.has(account.id)}
-              accounts={accounts}
-              categories={categories.list}
-              payees={state.payees}
-              transactions={transactions}
-              prependTransactions={prependTransactions || []}
-              balance={balance}
-              balanceCleared={balanceCleared}
-              balanceUncleared={balanceUncleared}
-              isNewTransaction={isNewTransaction}
-              onLoadMore={() => {
-                paged.current?.fetchNext();
-              }}
-              onSearch={onSearch}
-              onSelectTransaction={onSelectTransaction}
-            />
-          )
-        }
-      </PreviewTransactions>
-    </SchedulesProvider>
+    <AccountTransactions
+      // This key forces the whole table rerender when the number
+      // format changes
+      key={numberFormat + hideFraction}
+      account={account}
+      pending={syncingAccountIds.includes(account.id)}
+      failed={failedAccounts && failedAccounts.has(account.id)}
+    />
   );
 }
diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountDetails.jsx b/packages/desktop-client/src/components/mobile/accounts/AccountDetails.jsx
deleted file mode 100644
index ad653191a38232e258a6749ad09590dc94e3933e..0000000000000000000000000000000000000000
--- a/packages/desktop-client/src/components/mobile/accounts/AccountDetails.jsx
+++ /dev/null
@@ -1,297 +0,0 @@
-import React, { useState, useMemo } from 'react';
-import { useDispatch } from 'react-redux';
-
-import {
-  openAccountCloseModal,
-  pushModal,
-  reopenAccount,
-  syncAndDownload,
-  updateAccount,
-} from 'loot-core/client/actions';
-import { send } from 'loot-core/platform/client/fetch';
-
-import { SvgAdd } from '../../../icons/v1';
-import { SvgSearchAlternate } from '../../../icons/v2';
-import { styles, theme } from '../../../style';
-import { InputWithContent } from '../../common/InputWithContent';
-import { Label } from '../../common/Label';
-import { Link } from '../../common/Link';
-import { Text } from '../../common/Text';
-import { View } from '../../common/View';
-import { MobileBackButton } from '../../MobileBackButton';
-import { Page } from '../../Page';
-import { CellValue } from '../../spreadsheet/CellValue';
-import { useSheetValue } from '../../spreadsheet/useSheetValue';
-import { PullToRefresh } from '../PullToRefresh';
-import { TransactionList } from '../transactions/TransactionList';
-
-function TransactionSearchInput({ accountName, onSearch }) {
-  const [text, setText] = useState('');
-
-  return (
-    <View
-      style={{
-        flexDirection: 'row',
-        alignItems: 'center',
-        backgroundColor: theme.mobilePageBackground,
-        padding: 10,
-        width: '100%',
-      }}
-    >
-      <InputWithContent
-        leftContent={
-          <SvgSearchAlternate
-            style={{
-              width: 13,
-              height: 13,
-              flexShrink: 0,
-              color: text ? theme.formInputTextHighlight : 'inherit',
-              margin: 5,
-              marginRight: 0,
-            }}
-          />
-        }
-        value={text}
-        onChangeValue={text => {
-          setText(text);
-          onSearch(text);
-        }}
-        placeholder={`Search ${accountName}`}
-        style={{
-          backgroundColor: theme.tableBackground,
-          border: `1px solid ${theme.formInputBorder}`,
-          flex: 1,
-          height: styles.mobileMinHeight,
-        }}
-      />
-    </View>
-  );
-}
-
-function AccountName({ account, pending, failed }) {
-  const dispatch = useDispatch();
-
-  const onSave = account => {
-    dispatch(updateAccount(account));
-  };
-
-  const onSaveNotes = async (id, notes) => {
-    await send('notes-save', { id, note: notes });
-  };
-
-  const onEditNotes = () => {
-    dispatch(
-      pushModal('notes', {
-        id: account.id,
-        name: account.name,
-        onSave: onSaveNotes,
-      }),
-    );
-  };
-
-  const onCloseAccount = () => {
-    dispatch(openAccountCloseModal(account.id));
-  };
-
-  const onReopenAccount = () => {
-    dispatch(reopenAccount(account.id));
-  };
-
-  const onClick = () => {
-    dispatch(
-      pushModal('account-menu', {
-        accountId: account.id,
-        onSave,
-        onEditNotes,
-        onCloseAccount,
-        onReopenAccount,
-      }),
-    );
-  };
-  return (
-    <View
-      style={{
-        flexDirection: 'row',
-      }}
-    >
-      {account.bankId && (
-        <div
-          style={{
-            margin: 'auto',
-            marginRight: 5,
-            width: 8,
-            height: 8,
-            borderRadius: 8,
-            backgroundColor: pending
-              ? theme.sidebarItemBackgroundPending
-              : failed
-                ? theme.sidebarItemBackgroundFailed
-                : theme.sidebarItemBackgroundPositive,
-            transition: 'transform .3s',
-          }}
-        />
-      )}
-      <Text
-        style={{ ...styles.underlinedText, ...styles.lineClamp(2) }}
-        onClick={onClick}
-      >
-        {`${account.closed ? 'Closed: ' : ''}${account.name}`}
-      </Text>
-    </View>
-  );
-}
-
-export function AccountDetails({
-  account,
-  pending,
-  failed,
-  prependTransactions,
-  transactions,
-  accounts,
-  categories,
-  payees,
-  balance,
-  balanceCleared,
-  balanceUncleared,
-  isNewTransaction,
-  onLoadMore,
-  onSearch,
-  onSelectTransaction,
-}) {
-  const allTransactions = useMemo(() => {
-    return prependTransactions.concat(transactions);
-  }, [prependTransactions, transactions]);
-
-  const dispatch = useDispatch();
-  const onRefresh = async () => {
-    await dispatch(syncAndDownload(account.id));
-  };
-
-  return (
-    <Page
-      title={
-        <AccountName account={account} pending={pending} failed={failed} />
-      }
-      headerLeftContent={<MobileBackButton />}
-      headerRightContent={
-        <Link
-          variant="button"
-          to="transactions/new"
-          type="bare"
-          aria-label="Add Transaction"
-          style={{
-            justifyContent: 'center',
-            color: theme.mobileHeaderText,
-            margin: 10,
-          }}
-          hoveredStyle={{
-            color: theme.mobileHeaderText,
-            background: theme.mobileHeaderTextHover,
-          }}
-          activeStyle={{ background: 'transparent' }}
-        >
-          <SvgAdd width={20} height={20} />
-        </Link>
-      }
-      padding={0}
-      style={{
-        flex: 1,
-        backgroundColor: theme.mobilePageBackground,
-      }}
-    >
-      <View
-        style={{
-          alignItems: 'center',
-          flexShrink: 0,
-          marginTop: 10,
-        }}
-      >
-        <View
-          style={{
-            flexDirection: 'row',
-            boxSizing: 'content-box',
-            width: '100%',
-            justifyContent: 'space-evenly',
-          }}
-        >
-          <View
-            style={{
-              visibility:
-                useSheetValue(balanceUncleared) === 0 ? 'hidden' : 'visible',
-              width: '33%',
-            }}
-          >
-            <Label
-              title="CLEARED"
-              style={{ textAlign: 'center', fontSize: 12 }}
-            />
-            <CellValue
-              binding={balanceCleared}
-              type="financial"
-              style={{
-                fontSize: 12,
-                textAlign: 'center',
-                fontWeight: '500',
-              }}
-              data-testid="account-balance-cleared"
-            />
-          </View>
-          <View style={{ width: '33%' }}>
-            <Label title="BALANCE" style={{ textAlign: 'center' }} />
-            <CellValue
-              binding={balance}
-              type="financial"
-              style={{
-                fontSize: 18,
-                textAlign: 'center',
-                fontWeight: '500',
-              }}
-              getStyle={value => ({
-                color: value < 0 ? theme.errorText : theme.pillTextHighlighted,
-              })}
-              data-testid="account-balance"
-            />
-          </View>
-          <View
-            style={{
-              visibility:
-                useSheetValue(balanceUncleared) === 0 ? 'hidden' : 'visible',
-              width: '33%',
-            }}
-          >
-            <Label
-              title="UNCLEARED"
-              style={{ textAlign: 'center', fontSize: 12 }}
-            />
-            <CellValue
-              binding={balanceUncleared}
-              type="financial"
-              style={{
-                fontSize: 12,
-                textAlign: 'center',
-                fontWeight: '500',
-              }}
-              data-testid="account-balance-uncleared"
-            />
-          </View>
-        </View>
-        <TransactionSearchInput
-          accountName={account.name}
-          onSearch={onSearch}
-        />
-      </View>
-      <PullToRefresh onRefresh={onRefresh}>
-        <TransactionList
-          account={account}
-          transactions={allTransactions}
-          categories={categories}
-          accounts={accounts}
-          payees={payees}
-          isNew={isNewTransaction}
-          onLoadMore={onLoadMore}
-          onSelect={onSelectTransaction}
-        />
-      </PullToRefresh>
-    </Page>
-  );
-}
diff --git a/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.jsx b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cf9c1c2e2781d945efe4a6bf3c074adf18e03790
--- /dev/null
+++ b/packages/desktop-client/src/components/mobile/accounts/AccountTransactions.jsx
@@ -0,0 +1,261 @@
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from 'react';
+import { useDispatch } from 'react-redux';
+
+import memoizeOne from 'memoize-one';
+import { useDebounceCallback } from 'usehooks-ts';
+
+import {
+  getPayees,
+  markAccountRead,
+  openAccountCloseModal,
+  pushModal,
+  reopenAccount,
+  syncAndDownload,
+  updateAccount,
+} from 'loot-core/client/actions';
+import { SchedulesProvider } from 'loot-core/client/data-hooks/schedules';
+import * as queries from 'loot-core/client/queries';
+import { pagedQuery } from 'loot-core/client/query-helpers';
+import { listen, send } from 'loot-core/platform/client/fetch';
+import { isPreviewId } from 'loot-core/shared/transactions';
+
+import { useDateFormat } from '../../../hooks/useDateFormat';
+import { useLocalPref } from '../../../hooks/useLocalPref';
+import { useNavigate } from '../../../hooks/useNavigate';
+import { usePreviewTransactions } from '../../../hooks/usePreviewTransactions';
+import { styles, theme } from '../../../style';
+import { Text } from '../../common/Text';
+import { View } from '../../common/View';
+import { Page } from '../../Page';
+import { MobileBackButton } from '../MobileBackButton';
+import { AddTransactionButton } from '../transactions/AddTransactionButton';
+import { TransactionListWithBalances } from '../transactions/TransactionListWithBalances';
+
+export function AccountTransactions({ account, pending, failed }) {
+  return (
+    <Page
+      title={
+        <AccountName account={account} pending={pending} failed={failed} />
+      }
+      headerLeftContent={<MobileBackButton />}
+      headerRightContent={<AddTransactionButton accountId={account.id} />}
+      padding={0}
+      style={{
+        flex: 1,
+        backgroundColor: theme.mobilePageBackground,
+      }}
+    >
+      <SchedulesProvider transform={getSchedulesTransform(account.id)}>
+        <TransactionListWithPreviews account={account} />
+      </SchedulesProvider>
+    </Page>
+  );
+}
+
+function AccountName({ account, pending, failed }) {
+  const dispatch = useDispatch();
+
+  const onSave = account => {
+    dispatch(updateAccount(account));
+  };
+
+  const onSaveNotes = async (id, notes) => {
+    await send('notes-save', { id, note: notes });
+  };
+
+  const onEditNotes = () => {
+    dispatch(
+      pushModal('notes', {
+        id: account.id,
+        name: account.name,
+        onSave: onSaveNotes,
+      }),
+    );
+  };
+
+  const onCloseAccount = () => {
+    dispatch(openAccountCloseModal(account.id));
+  };
+
+  const onReopenAccount = () => {
+    dispatch(reopenAccount(account.id));
+  };
+
+  const onClick = () => {
+    dispatch(
+      pushModal('account-menu', {
+        accountId: account.id,
+        onSave,
+        onEditNotes,
+        onCloseAccount,
+        onReopenAccount,
+      }),
+    );
+  };
+  return (
+    <View
+      style={{
+        flexDirection: 'row',
+      }}
+    >
+      {account.bankId && (
+        <div
+          style={{
+            margin: 'auto',
+            marginRight: 5,
+            width: 8,
+            height: 8,
+            borderRadius: 8,
+            backgroundColor: pending
+              ? theme.sidebarItemBackgroundPending
+              : failed
+                ? theme.sidebarItemBackgroundFailed
+                : theme.sidebarItemBackgroundPositive,
+            transition: 'transform .3s',
+          }}
+        />
+      )}
+      <Text
+        style={{ ...styles.underlinedText, ...styles.lineClamp(2) }}
+        onClick={onClick}
+      >
+        {`${account.closed ? 'Closed: ' : ''}${account.name}`}
+      </Text>
+    </View>
+  );
+}
+
+const getSchedulesTransform = memoizeOne(id => {
+  const filter = queries.getAccountFilter(id, '_account');
+
+  return q => {
+    q = q.filter({ $and: [filter, { '_account.closed': false }] });
+    return q.orderBy({ next_date: 'desc' });
+  };
+});
+
+function TransactionListWithPreviews({ account }) {
+  const [currentQuery, setCurrentQuery] = useState();
+  const [isSearching, setIsSearching] = useState(false);
+  const [transactions, setTransactions] = useState([]);
+  const prependTransactions = usePreviewTransactions();
+  const allTransactions = useMemo(
+    () =>
+      !isSearching ? prependTransactions.concat(transactions) : transactions,
+    [isSearching, prependTransactions, transactions],
+  );
+
+  const dateFormat = useDateFormat() || 'MM/dd/yyyy';
+  const [_numberFormat] = useLocalPref('numberFormat');
+  const dispatch = useDispatch();
+  const navigate = useNavigate();
+
+  const onRefresh = async () => {
+    await dispatch(syncAndDownload(account.id));
+  };
+
+  const makeRootQuery = useCallback(
+    () => queries.makeTransactionsQuery(account.id).options({ splits: 'none' }),
+    [account.id],
+  );
+
+  const paged = useRef(null);
+
+  const updateQuery = useCallback(query => {
+    paged.current?.unsubscribe();
+    paged.current = pagedQuery(
+      query.options({ splits: 'none' }).select('*'),
+      data => setTransactions(data),
+      { pageCount: 10 },
+    );
+  }, []);
+
+  const fetchTransactions = useCallback(() => {
+    const query = makeRootQuery();
+    setCurrentQuery(query);
+    updateQuery(query);
+  }, [makeRootQuery, updateQuery]);
+
+  useEffect(() => {
+    const unlisten = listen('sync-event', ({ type, tables }) => {
+      if (type === 'applied') {
+        if (
+          tables.includes('transactions') ||
+          tables.includes('category_mapping') ||
+          tables.includes('payee_mapping')
+        ) {
+          paged.current?.run();
+        }
+
+        if (tables.includes('payees') || tables.includes('payee_mapping')) {
+          dispatch(getPayees());
+        }
+      }
+    });
+
+    fetchTransactions();
+    dispatch(markAccountRead(account.id));
+    return () => unlisten();
+  }, [account.id, dispatch, fetchTransactions]);
+
+  const updateSearchQuery = useDebounceCallback(
+    useCallback(
+      searchText => {
+        if (searchText === '' && currentQuery) {
+          updateQuery(currentQuery);
+        } else if (searchText && currentQuery) {
+          updateQuery(
+            queries.makeTransactionSearchQuery(
+              currentQuery,
+              searchText,
+              dateFormat,
+            ),
+          );
+        }
+
+        setIsSearching(searchText !== '');
+      },
+      [currentQuery, dateFormat, updateQuery],
+    ),
+    150,
+  );
+
+  const onSearch = text => {
+    updateSearchQuery(text);
+  };
+
+  const onSelectTransaction = transaction => {
+    // details of how the native app used to handle preview transactions here can be found at commit 05e58279
+    if (!isPreviewId(transaction.id)) {
+      navigate(`/transactions/${transaction.id}`);
+    }
+  };
+
+  const onLoadMore = () => {
+    paged.current?.fetchNext();
+  };
+
+  const balance = queries.accountBalance(account);
+  const balanceCleared = queries.accountBalanceCleared(account);
+  const balanceUncleared = queries.accountBalanceUncleared(account);
+
+  return (
+    <TransactionListWithBalances
+      transactions={allTransactions}
+      balance={balance}
+      balanceCleared={balanceCleared}
+      balanceUncleared={balanceUncleared}
+      onLoadMore={onLoadMore}
+      searchPlaceholder={`Search ${account.name}`}
+      onSearch={onSearch}
+      onSelectTransaction={onSelectTransaction}
+      onRefresh={onRefresh}
+    />
+  );
+}
diff --git a/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx b/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
index 9f75e6d9ba1b4050cbab49eb5506d7115af87fc2..3ecc0867080434cf412204a726e091f56b1cd021 100644
--- a/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
+++ b/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx
@@ -1,11 +1,10 @@
-import React, { useState } from 'react';
+import React from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 
 import { replaceModal, syncAndDownload } from 'loot-core/src/client/actions';
 import * as queries from 'loot-core/src/client/queries';
 
 import { useAccounts } from '../../../hooks/useAccounts';
-import { useCategories } from '../../../hooks/useCategories';
 import { useFailedAccounts } from '../../../hooks/useFailedAccounts';
 import { useLocalPref } from '../../../hooks/useLocalPref';
 import { useNavigate } from '../../../hooks/useNavigate';
@@ -247,25 +246,17 @@ function AccountList({
 export function Accounts() {
   const dispatch = useDispatch();
   const accounts = useAccounts();
-  const newTransactions = useSelector(state => state.queries.newTransactions);
   const updatedAccounts = useSelector(state => state.queries.updatedAccounts);
   const [_numberFormat] = useLocalPref('numberFormat');
   const numberFormat = _numberFormat || 'comma-dot';
   const [hideFraction = false] = useLocalPref('hideFraction');
 
-  const { list: categories } = useCategories();
-
-  const transactions = useState({});
   const navigate = useNavigate();
 
   const onSelectAccount = id => {
     navigate(`/accounts/${id}`);
   };
 
-  const onSelectTransaction = transaction => {
-    navigate(`/transaction/${transaction}`);
-  };
-
   const onAddAccount = () => {
     dispatch(replaceModal('add-account'));
   };
@@ -283,16 +274,12 @@ export function Accounts() {
         // format changes
         key={numberFormat + hideFraction}
         accounts={accounts.filter(account => !account.closed)}
-        categories={categories}
-        transactions={transactions || []}
         updatedAccounts={updatedAccounts}
-        newTransactions={newTransactions}
         getBalanceQuery={queries.accountBalance}
         getOnBudgetBalance={queries.budgetedAccountBalance}
         getOffBudgetBalance={queries.offbudgetAccountBalance}
         onAddAccount={onAddAccount}
         onSelectAccount={onSelectAccount}
-        onSelectTransaction={onSelectTransaction}
         onSync={onSync}
       />
     </View>
diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx
index 5b8798d439c8f7950544685f88f9de17bc212190..4eb5cad56261600eaf4b083dd73d153776783bf2 100644
--- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx
+++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx
@@ -8,6 +8,7 @@ import { rolloverBudget, reportBudget } from 'loot-core/src/client/queries';
 import * as monthUtils from 'loot-core/src/shared/months';
 
 import { useLocalPref } from '../../../hooks/useLocalPref';
+import { useNavigate } from '../../../hooks/useNavigate';
 import {
   SingleActiveEditFormProvider,
   useSingleActiveEditForm,
@@ -317,6 +318,18 @@ const ExpenseCategory = memo(function ExpenseCategory({
 
   const listItemRef = useRef();
 
+  const _onBudgetAction = (monthIndex, action, arg) => {
+    onBudgetAction?.(
+      monthUtils.getMonthFromIndex(monthUtils.getYear(month), monthIndex),
+      action,
+      arg,
+    );
+  };
+  const navigate = useNavigate();
+  const onShowActivity = () => {
+    navigate(`/categories/${category.id}?month=${month}`);
+  };
+
   const content = (
     <ListItem
       style={{
@@ -379,10 +392,12 @@ const ExpenseCategory = memo(function ExpenseCategory({
             binding={spent}
             style={{
               ...styles.smallText,
+              ...styles.underlinedText,
               textAlign: 'right',
             }}
             getStyle={makeAmountGrey}
             type="financial"
+            onClick={onShowActivity}
           />
         </View>
         <View
@@ -1398,8 +1413,7 @@ function MonthSelector({ month, monthBounds, onPrevMonth, onNextMonth }) {
           fontWeight: 500,
         }}
       >
-        {/* eslint-disable-next-line rulesdir/typography */}
-        {monthUtils.format(month, "MMMM ''yy")}
+        {monthUtils.format(month, 'MMMM ‘yy')}
       </Text>
       <Button
         type="bare"
diff --git a/packages/desktop-client/src/components/mobile/budget/Category.tsx b/packages/desktop-client/src/components/mobile/budget/Category.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..da31db63e336b7e438cb3c6043c9787b168ad14d
--- /dev/null
+++ b/packages/desktop-client/src/components/mobile/budget/Category.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { useParams, useSearchParams } from 'react-router-dom';
+
+import * as monthUtils from 'loot-core/src/shared/months';
+
+import { useCategories } from '../../../hooks/useCategories';
+import { useLocalPref } from '../../../hooks/useLocalPref';
+import { useSetThemeColor } from '../../../hooks/useSetThemeColor';
+import { theme } from '../../../style';
+
+import { CategoryTransactions } from './CategoryTransactions';
+
+export function Category() {
+  useSetThemeColor(theme.mobileViewTheme);
+  const [_numberFormat] = useLocalPref('numberFormat');
+  const numberFormat = _numberFormat || 'comma-dot';
+  const [hideFraction = false] = useLocalPref('hideFraction');
+
+  const { id: categoryId } = useParams();
+  const [searchParams] = useSearchParams();
+  const month = searchParams.get('month') || monthUtils.currentMonth();
+  const { list: categories } = useCategories();
+  const category = categories.find(c => c.id === categoryId);
+
+  if (category == null) {
+    return null;
+  }
+
+  return (
+    <CategoryTransactions
+      // This key forces the whole table rerender when the number
+      // format changes
+      key={numberFormat + hideFraction}
+      category={category}
+      month={month}
+    />
+  );
+}
diff --git a/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..620a886ad8bfc268e4d72c40f0fb05bf44d7bb21
--- /dev/null
+++ b/packages/desktop-client/src/components/mobile/budget/CategoryTransactions.jsx
@@ -0,0 +1,156 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { useDispatch } from 'react-redux';
+
+import { useDebounceCallback } from 'usehooks-ts';
+
+import { getPayees } from 'loot-core/client/actions';
+import * as queries from 'loot-core/client/queries';
+import { pagedQuery } from 'loot-core/client/query-helpers';
+import { listen } from 'loot-core/platform/client/fetch';
+import * as monthUtils from 'loot-core/shared/months';
+import { q } from 'loot-core/shared/query';
+import { isPreviewId } from 'loot-core/shared/transactions';
+
+import { useDateFormat } from '../../../hooks/useDateFormat';
+import { useLocalPref } from '../../../hooks/useLocalPref';
+import { useNavigate } from '../../../hooks/useNavigate';
+import { theme } from '../../../style';
+import { TextOneLine } from '../../common/TextOneLine';
+import { View } from '../../common/View';
+import { Page } from '../../Page';
+import { MobileBackButton } from '../MobileBackButton';
+import { AddTransactionButton } from '../transactions/AddTransactionButton';
+import { TransactionListWithBalances } from '../transactions/TransactionListWithBalances';
+
+export function CategoryTransactions({ category, month }) {
+  const dispatch = useDispatch();
+  const navigate = useNavigate();
+  const [currentQuery, setCurrentQuery] = useState();
+  const [transactions, setTransactions] = useState([]);
+
+  const dateFormat = useDateFormat() || 'MM/dd/yyyy';
+  const [_numberFormat] = useLocalPref('numberFormat');
+
+  const makeRootQuery = useCallback(
+    () =>
+      q('transactions')
+        .options({ splits: 'inline' })
+        .filter(getCategoryMonthFilter(category, month)),
+    [category, month],
+  );
+
+  const paged = useRef(null);
+
+  const updateQuery = useCallback(query => {
+    paged.current?.unsubscribe();
+    paged.current = pagedQuery(
+      query.options({ splits: 'inline' }).select('*'),
+      data => setTransactions(data),
+      { pageCount: 10 },
+    );
+  }, []);
+
+  const fetchTransactions = useCallback(async () => {
+    const query = makeRootQuery();
+    setCurrentQuery(query);
+    updateQuery(query);
+  }, [makeRootQuery, updateQuery]);
+
+  useEffect(() => {
+    function setup() {
+      return listen('sync-event', ({ type, tables }) => {
+        if (type === 'applied') {
+          if (
+            tables.includes('transactions') ||
+            tables.includes('category_mapping') ||
+            tables.includes('payee_mapping')
+          ) {
+            paged.current?.run();
+          }
+
+          if (tables.includes('payees') || tables.includes('payee_mapping')) {
+            dispatch(getPayees());
+          }
+        }
+      });
+    }
+
+    fetchTransactions();
+    return setup();
+  }, [dispatch, fetchTransactions]);
+
+  const updateSearchQuery = useDebounceCallback(
+    useCallback(
+      searchText => {
+        if (searchText === '' && currentQuery) {
+          updateQuery(currentQuery);
+        } else if (searchText && currentQuery) {
+          updateQuery(
+            queries.makeTransactionSearchQuery(
+              currentQuery,
+              searchText,
+              dateFormat,
+            ),
+          );
+        }
+      },
+      [currentQuery, dateFormat, updateQuery],
+    ),
+    150,
+  );
+
+  const onSearch = text => {
+    updateSearchQuery(text);
+  };
+
+  const onLoadMore = () => {
+    paged.current?.fetchNext();
+  };
+
+  const onSelectTransaction = transaction => {
+    // details of how the native app used to handle preview transactions here can be found at commit 05e58279
+    if (!isPreviewId(transaction.id)) {
+      navigate(`/transactions/${transaction.id}`);
+    }
+  };
+
+  const balance = queries.categoryBalance(category, month);
+  const balanceCleared = queries.categoryBalanceCleared(category, month);
+  const balanceUncleared = queries.categoryBalanceUncleared(category, month);
+
+  return (
+    <Page
+      title={
+        <View>
+          <TextOneLine>{category.name}</TextOneLine>
+          <TextOneLine>({monthUtils.format(month, 'MMMM ‘yy')})</TextOneLine>
+        </View>
+      }
+      headerLeftContent={<MobileBackButton />}
+      headerRightContent={<AddTransactionButton categoryId={category.id} />}
+      padding={0}
+      style={{
+        flex: 1,
+        backgroundColor: theme.mobilePageBackground,
+      }}
+    >
+      <TransactionListWithBalances
+        transactions={transactions}
+        balance={balance}
+        balanceCleared={balanceCleared}
+        balanceUncleared={balanceUncleared}
+        searchPlaceholder={`Search ${category.name}`}
+        onSearch={onSearch}
+        onLoadMore={onLoadMore}
+        onSelectTransaction={onSelectTransaction}
+      />
+    </Page>
+  );
+}
+
+function getCategoryMonthFilter(category, month) {
+  return {
+    category: category.id,
+    date: { $transform: '$month', $eq: month },
+  };
+}
diff --git a/packages/desktop-client/src/components/mobile/transactions/AddTransactionButton.tsx b/packages/desktop-client/src/components/mobile/transactions/AddTransactionButton.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a37822163f2b41c374ed6e171986ddb9eb438854
--- /dev/null
+++ b/packages/desktop-client/src/components/mobile/transactions/AddTransactionButton.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+
+import { useNavigate } from '../../../hooks/useNavigate';
+import { SvgAdd } from '../../../icons/v1';
+import { theme } from '../../../style';
+import { Button } from '../../common/Button';
+
+type AddTransactionButtonProps = {
+  to: string;
+  accountId?: string;
+  categoryId?: string;
+};
+
+export function AddTransactionButton({
+  to = '/transactions/new',
+  accountId,
+  categoryId,
+}: AddTransactionButtonProps) {
+  const navigate = useNavigate();
+  return (
+    <Button
+      type="bare"
+      aria-label="Add transaction"
+      style={{
+        justifyContent: 'center',
+        color: theme.mobileHeaderText,
+        margin: 10,
+      }}
+      hoveredStyle={{
+        color: theme.mobileHeaderText,
+        background: theme.mobileHeaderTextHover,
+      }}
+      onClick={() => {
+        navigate(to, { state: { accountId, categoryId } });
+      }}
+    >
+      <SvgAdd width={20} height={20} />
+    </Button>
+  );
+}
diff --git a/packages/desktop-client/src/components/mobile/transactions/ListBox.jsx b/packages/desktop-client/src/components/mobile/transactions/ListBox.jsx
index 028c1787e3eaa251f43376ec13ad425eb345ab99..402a950d8d337582177f58a3a71a880690a0ac08 100644
--- a/packages/desktop-client/src/components/mobile/transactions/ListBox.jsx
+++ b/packages/desktop-client/src/components/mobile/transactions/ListBox.jsx
@@ -15,7 +15,7 @@ export function ListBox(props) {
   const { loadMore } = props;
 
   const { hasScrolledToBottom } = useScroll();
-  const scrolledToBottom = hasScrolledToBottom();
+  const scrolledToBottom = hasScrolledToBottom(5);
   const prevScrolledToBottom = usePrevious(scrolledToBottom);
 
   if (!prevScrolledToBottom && scrolledToBottom) {
diff --git a/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx b/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx
index 055239cc7d6ddcd4687e2d0904f3b04927db4dfd..855c7f44a63382d24fc1acc1493f93f75a4d858e 100644
--- a/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx
+++ b/packages/desktop-client/src/components/mobile/transactions/Transaction.jsx
@@ -1,9 +1,13 @@
-import React, { memo, useMemo } from 'react';
+import React, { memo } from 'react';
 
 import { getScheduledAmount } from 'loot-core/src/shared/schedules';
 import { isPreviewId } from 'loot-core/src/shared/transactions';
-import { integerToCurrency, groupById } from 'loot-core/src/shared/util';
+import { integerToCurrency } from 'loot-core/src/shared/util';
 
+import { useAccount } from '../../../hooks/useAccount';
+import { useCategories } from '../../../hooks/useCategories';
+import { usePayee } from '../../../hooks/usePayee';
+import { SvgSplit } from '../../../icons/v0';
 import {
   SvgArrowsSynchronize,
   SvgCheckCircle1,
@@ -41,28 +45,29 @@ ListItem.displayName = 'ListItem';
 
 export const Transaction = memo(function Transaction({
   transaction,
-  account,
-  accounts,
-  categories,
-  payees,
   added,
   onSelect,
   style,
 }) {
-  const accountsById = useMemo(() => groupById(accounts), [accounts]);
-  const payeesById = useMemo(() => groupById(payees), [payees]);
+  const { list: categories } = useCategories();
 
   const {
     id,
     payee: payeeId,
     amount: originalAmount,
     category: categoryId,
+    account: accountId,
     cleared,
     is_parent: isParent,
+    is_child: isChild,
     notes,
     schedule,
   } = transaction;
 
+  const payee = usePayee(payeeId);
+  const account = useAccount(accountId);
+  const transferAcct = useAccount(payee?.transfer_acct);
+
   const isPreview = isPreviewId(id);
   let amount = originalAmount;
   if (isPreview) {
@@ -71,10 +76,6 @@ export const Transaction = memo(function Transaction({
 
   const categoryName = lookupName(categories, categoryId);
 
-  const payee = payeesById && payeeId && payeesById[payeeId];
-  const transferAcct =
-    payee && payee.transfer_acct && accountsById[payee.transfer_acct];
-
   const prettyDescription = getDescriptionPretty(
     transaction,
     payee,
@@ -173,6 +174,15 @@ export const Transaction = memo(function Transaction({
                   }}
                 />
               )}
+              {(isParent || isChild) && (
+                <SvgSplit
+                  style={{
+                    width: 12,
+                    height: 12,
+                    marginRight: 5,
+                  }}
+                />
+              )}
               <TextOneLine
                 style={{
                   fontSize: 11,
diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx
index 42be12c67ea85e0e5e869cf9651f74803d990cb2..b800f9595ccabeb9b1ef33122e1ea515d8bbab62 100644
--- a/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx
+++ b/packages/desktop-client/src/components/mobile/transactions/TransactionEdit.jsx
@@ -7,7 +7,7 @@ import React, {
   useMemo,
 } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
-import { useParams } from 'react-router-dom';
+import { useLocation, useParams } from 'react-router-dom';
 
 import {
   format as formatDate,
@@ -56,9 +56,9 @@ import { styles, theme } from '../../../style';
 import { Button } from '../../common/Button';
 import { Text } from '../../common/Text';
 import { View } from '../../common/View';
-import { MobileBackButton } from '../../MobileBackButton';
 import { Page } from '../../Page';
 import { AmountInput } from '../../util/AmountInput';
+import { MobileBackButton } from '../MobileBackButton';
 import { FieldLabel, TapField, InputField, BooleanField } from '../MobileForms';
 
 import { FocusableAmountInput } from './FocusableAmountInput';
@@ -947,21 +947,28 @@ function isTemporary(transaction) {
   return transaction.id.indexOf('temp') === 0;
 }
 
-function makeTemporaryTransactions(currentAccountId, lastDate) {
+function makeTemporaryTransactions(accountId, categoryId, lastDate) {
   return [
     {
       id: 'temp',
       date: lastDate || monthUtils.currentDay(),
-      account: currentAccountId,
+      account: accountId,
+      category: categoryId,
       amount: 0,
       cleared: false,
     },
   ];
 }
 
-function TransactionEditUnconnected(props) {
-  const { categories, accounts, payees, lastTransaction, dateFormat } = props;
-  const { id: accountId, transactionId } = useParams();
+function TransactionEditUnconnected({
+  categories,
+  accounts,
+  payees,
+  lastTransaction,
+  dateFormat,
+}) {
+  const { transactionId } = useParams();
+  const { state: locationState } = useLocation();
   const navigate = useNavigate();
   const dispatch = useDispatch();
   const [transactions, setTransactions] = useState([]);
@@ -991,7 +998,7 @@ function TransactionEditUnconnected(props) {
       setTransactions(fetchedTransactions);
       setFetchedTransactions(fetchedTransactions);
     }
-    if (transactionId) {
+    if (transactionId !== 'new') {
       fetchTransaction();
     } else {
       adding.current = true;
@@ -1002,12 +1009,13 @@ function TransactionEditUnconnected(props) {
     if (adding.current) {
       setTransactions(
         makeTemporaryTransactions(
-          accountId || lastTransaction?.account || null,
+          locationState?.accountId || lastTransaction?.account || null,
+          locationState?.categoryId || lastTransaction?.category || null,
           lastTransaction?.date,
         ),
       );
     }
-  }, [accountId, lastTransaction]);
+  }, [locationState?.accountId, locationState?.categoryId, lastTransaction]);
 
   if (
     categories.length === 0 ||
diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
index e76fe396d028bc56b9c605a27685ba3d1c5d8a06..332d45c1002dba3bede8b241f1718fdec06b6f4b 100644
--- a/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
+++ b/packages/desktop-client/src/components/mobile/transactions/TransactionList.jsx
@@ -12,10 +12,6 @@ import { ListBox } from './ListBox';
 import { Transaction } from './Transaction';
 
 export function TransactionList({
-  account,
-  accounts,
-  categories,
-  payees,
   transactions,
   isNew,
   onSelect,
@@ -45,9 +41,7 @@ export function TransactionList({
         });
       }
 
-      if (!transaction.is_child) {
-        sections[sections.length - 1].data.push(transaction);
-      }
+      sections[sections.length - 1].data.push(transaction);
     });
     return sections;
   }, [transactions]);
@@ -98,12 +92,8 @@ export function TransactionList({
                   >
                     <Transaction
                       transaction={transaction}
-                      account={account}
-                      categories={categories}
-                      accounts={accounts}
-                      payees={payees}
                       added={isNew(transaction.id)}
-                      onSelect={onSelect} // onSelect(transaction)}
+                      onSelect={onSelect}
                     />
                   </Item>
                 );
diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1eddb932d2fac53cde303f5883e8b92127360ac4
--- /dev/null
+++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx
@@ -0,0 +1,165 @@
+import React, { useState } from 'react';
+import { useSelector } from 'react-redux';
+
+import { SvgSearchAlternate } from '../../../icons/v2';
+import { styles, theme } from '../../../style';
+import { InputWithContent } from '../../common/InputWithContent';
+import { Label } from '../../common/Label';
+import { View } from '../../common/View';
+import { CellValue } from '../../spreadsheet/CellValue';
+import { useSheetValue } from '../../spreadsheet/useSheetValue';
+import { PullToRefresh } from '../PullToRefresh';
+
+import { TransactionList } from './TransactionList';
+
+function TransactionSearchInput({ placeholder, onSearch }) {
+  const [text, setText] = useState('');
+
+  return (
+    <View
+      style={{
+        flexDirection: 'row',
+        alignItems: 'center',
+        backgroundColor: theme.mobilePageBackground,
+        padding: 10,
+        width: '100%',
+      }}
+    >
+      <InputWithContent
+        leftContent={
+          <SvgSearchAlternate
+            style={{
+              width: 13,
+              height: 13,
+              flexShrink: 0,
+              color: text ? theme.formInputTextHighlight : 'inherit',
+              margin: 5,
+              marginRight: 0,
+            }}
+          />
+        }
+        value={text}
+        onChangeValue={text => {
+          setText(text);
+          onSearch(text);
+        }}
+        placeholder={placeholder}
+        style={{
+          backgroundColor: theme.tableBackground,
+          border: `1px solid ${theme.formInputBorder}`,
+          flex: 1,
+          height: styles.mobileMinHeight,
+        }}
+      />
+    </View>
+  );
+}
+
+export function TransactionListWithBalances({
+  transactions,
+  balance,
+  balanceCleared,
+  balanceUncleared,
+  searchPlaceholder = 'Search...',
+  onSearch,
+  onLoadMore,
+  onSelectTransaction,
+  onRefresh,
+}) {
+  const newTransactions = useSelector(state => state.queries.newTransactions);
+
+  const isNewTransaction = id => {
+    return newTransactions.includes(id);
+  };
+
+  const unclearedAmount = useSheetValue(balanceUncleared);
+
+  return (
+    <>
+      <View
+        style={{
+          flexShrink: 0,
+          marginTop: 10,
+        }}
+      >
+        <View
+          style={{
+            flexDirection: 'row',
+            justifyContent: 'space-evenly',
+          }}
+        >
+          <View
+            style={{
+              display: !unclearedAmount ? 'none' : undefined,
+              flexBasis: '33%',
+            }}
+          >
+            <Label
+              title="CLEARED"
+              style={{ textAlign: 'center', fontSize: 12 }}
+            />
+            <CellValue
+              binding={balanceCleared}
+              type="financial"
+              style={{
+                fontSize: 12,
+                textAlign: 'center',
+                fontWeight: '500',
+              }}
+              data-testid="transactions-balance-cleared"
+            />
+          </View>
+          <View style={{ flexBasis: '33%' }}>
+            <Label title="BALANCE" style={{ textAlign: 'center' }} />
+            <CellValue
+              binding={balance}
+              type="financial"
+              style={{
+                fontSize: 18,
+                textAlign: 'center',
+                fontWeight: '500',
+              }}
+              getStyle={value => ({
+                color: value < 0 ? theme.errorText : theme.pillTextHighlighted,
+              })}
+              data-testid="transactions-balance"
+            />
+          </View>
+          <View
+            style={{
+              display: !unclearedAmount ? 'none' : undefined,
+              flexBasis: '33%',
+            }}
+          >
+            <Label
+              title="UNCLEARED"
+              style={{ textAlign: 'center', fontSize: 12 }}
+            />
+            <CellValue
+              binding={balanceUncleared}
+              type="financial"
+              style={{
+                fontSize: 12,
+                textAlign: 'center',
+                fontWeight: '500',
+              }}
+              data-testid="transactions-balance-uncleared"
+            />
+          </View>
+        </View>
+        <TransactionSearchInput
+          placeholder={searchPlaceholder}
+          onSearch={onSearch}
+        />
+      </View>
+      <PullToRefresh isPullable={!!onRefresh} onRefresh={onRefresh}>
+        <TransactionList
+          transactions={transactions}
+          isNew={isNewTransaction}
+          onLoadMore={onLoadMore}
+          onSelect={onSelectTransaction}
+        />
+      </PullToRefresh>
+    </>
+  );
+}
diff --git a/packages/desktop-client/src/hooks/usePreviewTransactions.ts b/packages/desktop-client/src/hooks/usePreviewTransactions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4f4f57b3ef02c4db93fb84b5247e0987371ae894
--- /dev/null
+++ b/packages/desktop-client/src/hooks/usePreviewTransactions.ts
@@ -0,0 +1,40 @@
+import { useMemo } from 'react';
+
+import {
+  type ScheduleStatuses,
+  useCachedSchedules,
+} from 'loot-core/client/data-hooks/schedules';
+import { type ScheduleEntity } from 'loot-core/types/models';
+
+export function usePreviewTransactions() {
+  const scheduleData = useCachedSchedules();
+
+  return useMemo(() => {
+    if (!scheduleData) {
+      return [];
+    }
+
+    const schedules =
+      scheduleData.schedules.filter(s =>
+        isForPreview(s, scheduleData.statuses),
+      ) || [];
+
+    return schedules.map(schedule => ({
+      id: 'preview/' + schedule.id,
+      payee: schedule._payee,
+      account: schedule._account,
+      amount: schedule._amount,
+      date: schedule.next_date,
+      notes: scheduleData.statuses.get(schedule.id),
+      schedule: schedule.id,
+    }));
+  }, [scheduleData]);
+}
+
+function isForPreview(schedule: ScheduleEntity, statuses: ScheduleStatuses) {
+  const status = statuses.get(schedule.id);
+  return (
+    !schedule.completed &&
+    (status === 'due' || status === 'upcoming' || status === 'missed')
+  );
+}
diff --git a/packages/loot-core/src/client/data-hooks/schedules.tsx b/packages/loot-core/src/client/data-hooks/schedules.tsx
index 9902efac09c72eb8226c8e2636bd2f997af6abc5..2f6117dea84da4984e0b5dd33fc328185989ea6a 100644
--- a/packages/loot-core/src/client/data-hooks/schedules.tsx
+++ b/packages/loot-core/src/client/data-hooks/schedules.tsx
@@ -25,14 +25,14 @@ function loadStatuses(schedules: ScheduleEntity[], onData) {
 }
 
 type UseSchedulesArgs = { transform?: (q: Query) => Query };
-type UseSchedulesReturnType = {
+type UseSchedulesResult = {
   schedules: ScheduleEntity[];
   statuses: ScheduleStatuses;
 } | null;
 export function useSchedules({
   transform,
-}: UseSchedulesArgs = {}): UseSchedulesReturnType {
-  const [data, setData] = useState<UseSchedulesReturnType>(null);
+}: UseSchedulesArgs = {}): UseSchedulesResult {
+  const [data, setData] = useState<UseSchedulesResult>(null);
 
   useEffect(() => {
     const query = q('schedules').select('*');
@@ -66,7 +66,11 @@ export function useSchedules({
   return data;
 }
 
-const SchedulesContext = createContext(null);
+type SchedulesContextValue = UseSchedulesResult;
+
+const SchedulesContext = createContext<SchedulesContextValue | undefined>(
+  undefined,
+);
 
 export function SchedulesProvider({ transform, children }) {
   const data = useSchedules({ transform });
diff --git a/packages/loot-core/src/client/queries.ts b/packages/loot-core/src/client/queries.ts
index f8ac400a04ef1895b7cee5d57ebc7ab418c02c13..d4320ee52be9308635ba98ac8fd4032a2a1d792c 100644
--- a/packages/loot-core/src/client/queries.ts
+++ b/packages/loot-core/src/client/queries.ts
@@ -151,6 +151,47 @@ export function offbudgetAccountBalance() {
   };
 }
 
+export function categoryBalance(category, month) {
+  return {
+    name: `balance-${category.id}`,
+    query: q('transactions')
+      .filter({
+        category: category.id,
+        date: { $transform: '$month', $eq: month },
+      })
+      .options({ splits: 'inline' })
+      .calculate({ $sum: '$amount' }),
+  };
+}
+
+export function categoryBalanceCleared(category, month) {
+  return {
+    name: `balanceCleared-${category.id}`,
+    query: q('transactions')
+      .filter({
+        category: category.id,
+        date: { $transform: '$month', $eq: month },
+        cleared: true,
+      })
+      .options({ splits: 'inline' })
+      .calculate({ $sum: '$amount' }),
+  };
+}
+
+export function categoryBalanceUncleared(category, month) {
+  return {
+    name: `balanceUncleared-${category.id}`,
+    query: q('transactions')
+      .filter({
+        category: category.id,
+        date: { $transform: '$month', $eq: month },
+        cleared: false,
+      })
+      .options({ splits: 'inline' })
+      .calculate({ $sum: '$amount' }),
+  };
+}
+
 const uncategorizedQuery = q('transactions').filter({
   'account.offbudget': false,
   category: null,
diff --git a/upcoming-release-notes/2531.md b/upcoming-release-notes/2531.md
new file mode 100644
index 0000000000000000000000000000000000000000..2dbc168090e7782d0c30e11a76f6b57b0ec7bc35
--- /dev/null
+++ b/upcoming-release-notes/2531.md
@@ -0,0 +1,6 @@
+---
+category: Features
+authors: [joel-jeremy]
+---
+
+Drill down category transactions by clicking on spent amount in mobile budget page.