feat(core): improve mobile perf (#15317)

#### PR Dependency Tree


* **PR #15317** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Virtualized mobile navigation with shell navigation and interactive
swipe menus; coordinated mobile back handling with interactive
phases/state restoration.
* Added shared auth request proxy and message-port based token handling
across mobile and worker flows.
* **Bug Fixes**
  * Hydrated remote worker error stacks for calls and observable errors.
* Improved SQLite FTS/indexer and nbstore optional text handling;
refined docs-search ref parsing and notification loading/retry.
* **Refactor / UX**
* Modal focus-preservation and pointer behavior updates; improved mobile
menu controls and back gesture plugins.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-07-23 00:23:21 +08:00
committed by GitHub
parent 02e75862cc
commit 1d36e2e4b2
160 changed files with 6660 additions and 1890 deletions
+73 -26
View File
@@ -2,7 +2,9 @@ import { notify } from '@affine/component';
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
import { AffineContext } from '@affine/core/components/context';
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
import { MobileModalConfigProvider } from '@affine/core/mobile/components/mobile-modal-config-provider';
import { configureMobileModules } from '@affine/core/mobile/modules';
import { MobileBackCoordinator } from '@affine/core/mobile/modules/back-coordinator';
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
import { router } from '@affine/core/mobile/router';
import { configureCommonModules } from '@affine/core/modules';
@@ -32,6 +34,7 @@ import { WorkspacesService } from '@affine/core/modules/workspace';
import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspace-engine';
import { getWorkerUrl } from '@affine/env/worker';
import { I18n } from '@affine/i18n';
import { serveAuthRequests } from '@affine/mobile-shared/auth/channel';
import { StoreManagerClient } from '@affine/nbstore/worker/client';
import { setTelemetryTransport } from '@affine/track';
import { Container } from '@blocksuite/affine/global/di';
@@ -44,7 +47,13 @@ import { App as CapacitorApp } from '@capacitor/app';
import { Keyboard } from '@capacitor/keyboard';
import { StatusBar, Style } from '@capacitor/status-bar';
import { InAppBrowser } from '@capgo/inappbrowser';
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
import {
Framework,
FrameworkRoot,
getCurrentStore,
useLiveData,
useService,
} from '@toeverything/infra';
import { OpClient } from '@toeverything/infra/op';
import { AsyncCall } from 'async-call-rpc';
import { useTheme } from 'next-themes';
@@ -55,9 +64,14 @@ import { AffineTheme } from './plugins/affine-theme';
import { AIButton } from './plugins/ai-button';
import { Auth } from './plugins/auth';
import { HashCash } from './plugins/hashcash';
import { MobileBack } from './plugins/mobile-back';
import { NbStoreNativeDBApis } from './plugins/nbstore';
import { Preview } from './plugins/preview';
import { clearEndpointSession, getValidAccessToken } from './proxy';
import {
authRequestProvider,
clearEndpointSession,
getValidAccessToken,
} from './proxy';
const storeManagerClient = createStoreManagerClient();
setTelemetryTransport(storeManagerClient.telemetry);
@@ -409,19 +423,67 @@ const ThemeProvider = () => {
return null;
};
const AndroidCapacitorApp = CapacitorApp as typeof CapacitorApp & {
toggleBackButtonHandler(options: { enabled: boolean }): Promise<void>;
};
const AndroidBackAdapter = () => {
const coordinator = useService(MobileBackCoordinator);
const canHandle = useLiveData(coordinator.canHandle$);
useEffect(() => {
Promise.all([
AndroidCapacitorApp.toggleBackButtonHandler({ enabled: !canHandle }),
MobileBack.setEnabled({ enabled: canHandle }),
]).catch(console.error);
}, [canHandle]);
useEffect(() => {
let disposed = false;
let remove = () => {};
MobileBack.addListener('back', event => {
const handled = coordinator.handleInteractivePhase(event.phase);
if (event.phase === 'commit' && !handled) {
coordinator.request('system-back');
}
})
.then(handle => {
if (disposed) handle.remove().catch(console.error);
else
remove = () => {
handle.remove().catch(console.error);
};
})
.catch(console.error);
return () => {
disposed = true;
remove();
Promise.all([
AndroidCapacitorApp.toggleBackButtonHandler({ enabled: true }),
MobileBack.setEnabled({ enabled: false }),
]).catch(console.error);
};
}, [coordinator]);
return null;
};
export function App() {
return (
<Suspense>
<FrameworkRoot framework={frameworkProvider}>
<I18nProvider>
<AffineContext store={getCurrentStore()}>
<ThemeProvider />
<RouterProvider
fallbackElement={<AppFallback />}
router={router}
future={future}
/>
</AffineContext>
<MobileModalConfigProvider>
<AffineContext store={getCurrentStore()}>
<ThemeProvider />
<AndroidBackAdapter />
<RouterProvider
fallbackElement={<AppFallback />}
router={router}
future={future}
/>
</AffineContext>
</MobileModalConfigProvider>
</I18nProvider>
</FrameworkRoot>
</Suspense>
@@ -460,22 +522,7 @@ function createStoreManagerClient() {
const { port1: authTokenChannelServer, port2: authTokenChannelClient } =
new MessageChannel();
authTokenChannelServer.addEventListener('message', event => {
const { id, endpoint } = event.data as { id?: string; endpoint?: string };
if (!id || !endpoint) return;
getValidAccessToken(endpoint)
.then(token => authTokenChannelServer.postMessage({ id, token }))
.catch(error =>
authTokenChannelServer.postMessage({
id,
error:
typeof error === 'object' && error && 'code' in error
? error.code
: 'AUTH_SESSION_TEMPORARILY_UNAVAILABLE',
})
);
});
authTokenChannelServer.start();
serveAuthRequests(authTokenChannelServer, authRequestProvider);
worker.postMessage(
{ type: 'auth-access-token-channel', port: authTokenChannelClient },
[authTokenChannelClient]