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
@@ -0,0 +1,231 @@
/**
* @vitest-environment happy-dom
*/
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
const importDocs = vi.fn();
const docsServiceToken = Symbol('DocsService');
const organizeServiceToken = Symbol('OrganizeService');
vi.mock('../../blocksuite/block-suite-editor', () => ({}));
vi.mock('@blocksuite/affine/widgets/linked-doc', () => ({
ZipTransformer: {
importDocs,
},
}));
vi.mock('@affine/templates/onboarding.zip', () => ({
default: '/onboarding.zip',
}));
vi.mock('../../modules/doc', () => ({
DocsService: docsServiceToken,
}));
vi.mock('../../modules/organize', () => ({
OrganizeService: organizeServiceToken,
}));
vi.mock('../../modules/workspace', () => ({
getAFFiNEWorkspaceSchema: () => 'schema',
}));
const originalBuildConfig = globalThis.BUILD_CONFIG;
beforeEach(() => {
localStorage.clear();
importDocs.mockReset();
vi.stubGlobal(
'fetch',
vi.fn(async () => new Response(new Blob()))
);
vi.stubGlobal('BUILD_CONFIG', {
...originalBuildConfig,
isMobileEdition: false,
});
});
afterEach(() => {
vi.unstubAllGlobals();
});
function createWorkspacesService({
existing = [],
createWorkspace,
}: {
existing?: Array<{ id: string; flavour: string }>;
createWorkspace?: (
flavour: string
) => Promise<{ id: string; flavour: string }>;
} = {}) {
const workspaces = [...existing];
const createMock = vi.fn(
createWorkspace ??
(async (flavour: string) => {
const meta = { id: `workspace-${workspaces.length + 1}`, flavour };
workspaces.push(meta);
return meta;
})
);
const waitForDocReady = vi.fn(async () => {});
const create = vi.fn(
async (
flavour: string,
setup: (docCollection: {
meta: { initialize: () => void };
doc: { getMap: () => { set: (key: string, value: string) => void } };
}) => Promise<void>
) => {
const meta = await createMock(flavour);
await setup({
meta: { initialize: vi.fn() },
doc: {
getMap: () => ({
set: vi.fn(),
}),
},
});
return meta;
}
);
const service = {
list: {
['workspaces$']: {
get value() {
return workspaces;
},
},
},
create,
open: ({ metadata }: { metadata: { id: string } }) => ({
workspace: {
id: metadata.id,
engine: {
doc: {
waitForDocReady,
},
},
scope: {
get: (token: symbol) => {
if (token === docsServiceToken) {
return {
list: {
['docs$']: {
value: [
{
id: 'getting-started',
['title$']: { value: 'Getting Started' },
},
],
},
},
};
}
throw new Error('Unexpected service token');
},
},
},
dispose: vi.fn(),
}),
};
return {
service,
createMock,
workspaces,
};
}
describe('createFirstAppData', () => {
test('does not create on desktop when the first-open marker exists', async () => {
localStorage.setItem('is-first-open', 'false');
const { createFirstAppData } = await import('../first-app-data');
const { service, createMock } = createWorkspacesService();
expect(createFirstAppData(service as never)).toBeUndefined();
expect(createMock).not.toHaveBeenCalled();
});
test('creates on mobile when the first-open marker is stale and no workspace exists', async () => {
vi.stubGlobal('BUILD_CONFIG', {
...originalBuildConfig,
isMobileEdition: true,
});
localStorage.setItem('is-first-open', 'false');
const { createFirstAppData } = await import('../first-app-data');
const { service, createMock } = createWorkspacesService();
await expect(createFirstAppData(service as never)).resolves.toMatchObject({
meta: { id: 'workspace-1', flavour: 'local' },
defaultPageId: 'getting-started',
});
expect(createMock).toHaveBeenCalledOnce();
expect(localStorage.getItem('is-first-open')).toBe('false');
});
test('does not create when any workspace already exists', async () => {
vi.stubGlobal('BUILD_CONFIG', {
...originalBuildConfig,
isMobileEdition: true,
});
const { createFirstAppData } = await import('../first-app-data');
const { service, createMock } = createWorkspacesService({
existing: [{ id: 'existing-workspace', flavour: 'local' }],
});
expect(createFirstAppData(service as never)).toBeUndefined();
expect(createMock).not.toHaveBeenCalled();
});
test('coalesces concurrent creation attempts', async () => {
vi.stubGlobal('BUILD_CONFIG', {
...originalBuildConfig,
isMobileEdition: true,
});
const { createFirstAppData } = await import('../first-app-data');
let resolveCreate:
| ((meta: { id: string; flavour: string }) => void)
| undefined;
const { service, createMock, workspaces } = createWorkspacesService({
createWorkspace: flavour =>
new Promise(resolve => {
resolveCreate = meta => {
workspaces.push(meta);
resolve(meta);
};
expect(flavour).toBe('local');
}),
});
const first = createFirstAppData(service as never);
const second = createFirstAppData(service as never);
resolveCreate?.({ id: 'workspace-1', flavour: 'local' });
await expect(Promise.all([first, second])).resolves.toEqual([
{
meta: { id: 'workspace-1', flavour: 'local' },
defaultPageId: 'getting-started',
},
{
meta: { id: 'workspace-1', flavour: 'local' },
defaultPageId: 'getting-started',
},
]);
expect(createMock).toHaveBeenCalledOnce();
});
test('does not persist the first-open marker when creation fails', async () => {
vi.stubGlobal('BUILD_CONFIG', {
...originalBuildConfig,
isMobileEdition: true,
});
const { createFirstAppData } = await import('../first-app-data');
const error = new Error('create failed');
const { service, createMock } = createWorkspacesService({
createWorkspace: async () => {
throw error;
},
});
await expect(createFirstAppData(service as never)).rejects.toThrow(error);
await expect(createFirstAppData(service as never)).rejects.toThrow(error);
expect(createMock).toHaveBeenCalledTimes(2);
expect(localStorage.getItem('is-first-open')).toBeNull();
});
});
@@ -67,16 +67,33 @@ export async function buildShowcaseWorkspace(
const logger = new DebugLogger('createFirstAppData');
export async function createFirstAppData(workspacesService: WorkspacesService) {
if (localStorage.getItem('is-first-open') !== null) {
let firstAppDataPromise:
| Promise<Awaited<ReturnType<typeof buildShowcaseWorkspace>>>
| undefined;
export function createFirstAppData(workspacesService: WorkspacesService) {
if (workspacesService.list.workspaces$.value.length > 0) {
return;
}
localStorage.setItem('is-first-open', 'false');
const { meta, defaultDocId } = await buildShowcaseWorkspace(
if (
!BUILD_CONFIG.isMobileEdition &&
localStorage.getItem('is-first-open') !== null
) {
return;
}
firstAppDataPromise ??= buildShowcaseWorkspace(
workspacesService,
'local',
DEFAULT_WORKSPACE_NAME
);
logger.info('create first workspace', defaultDocId);
return { meta, defaultPageId: defaultDocId };
).finally(() => {
firstAppDataPromise = undefined;
});
return firstAppDataPromise.then(({ meta, defaultDocId }) => {
localStorage.setItem('is-first-open', 'false');
logger.info('create first workspace', defaultDocId);
return { meta, defaultPageId: defaultDocId };
});
}