Repository: toeverything/AFFiNE
Pull request #12329: feat(mobile): new docs list for mobile
Author: login: CatsJuice
Labels: name: mod:component
name: test
description: Related to test cases
name: app:core
Created: 2025-05-16T09:10:32Z | Merged: 2025-05-23T07:21:28Z

close AF-2514

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

- **New Features**
  - Enhanced document explorer on mobile with live updates, responsive masonry layout, and improved empty state handling for all documents, collections, and tags.
  - Added customization for card height and masonry item width in document explorer views.
  - Extended layout components to support additional flexbox styling options for improved layout flexibility.

- **Refactor**
  - Replaced older static document list and menu components with a unified, context-driven explorer for a more dynamic and interactive experience.
  - Removed obsolete CSS and component files related to the previous document list and menu implementations.

- **Bug Fixes**
  - Updated test selectors to improve stability and accuracy in document interaction and visibility checks.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/packages/frontend/component/src/ui/layout/wrapper.tsx b/packages/frontend/component/src/ui/layout/wrapper.tsx
index 7a9ce850af3f9..5199063b65080 100644
--- a/packages/frontend/component/src/ui/layout/wrapper.tsx
+++ b/packages/frontend/component/src/ui/layout/wrapper.tsx
@@ -16,6 +16,8 @@ export type WrapperProps = {
   marginLeft?: CSSProperties['marginLeft'];
   marginRight?: CSSProperties['marginRight'];
   marginBottom?: CSSProperties['marginBottom'];
+  flexGrow?: CSSProperties['flexGrow'];
+  flexShrink?: CSSProperties['flexShrink'];
 };
 
 export type FlexWrapperProps = {
@@ -64,6 +66,8 @@ export const Wrapper = styled('div', {
   marginLeft,
   marginRight,
   marginBottom,
+  flexGrow,
+  flexShrink,
 }) => {
   return {
     display,
@@ -79,6 +83,8 @@ export const Wrapper = styled('div', {
     marginLeft,
     marginRight,
     marginBottom,
+    flexGrow,
+    flexShrink,
   };
 });
 
diff --git a/packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx b/packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
index 184b0dfbfc07e..fe914da14b681 100644
--- a/packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
+++ b/packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
@@ -69,15 +69,15 @@ const GroupHeader = memo(function GroupHeader({
   return header;
 });
 
-const calcCardHeightById = (id: string) => {
+const calcCardHeightById = (id: string, base = 250, scale = 10) => {
   if (!id) {
-    return 250;
+    return base;
   }
   const max = 5;
   const min = 1;
   const code = id.charCodeAt(0);
   const value = Math.floor((code % (max - min)) + min);
-  return 250 + value * 10;
+  return base + value * scale;
 };
 
 export const DocListItemComponent = memo(function DocListItemComponent({
@@ -93,9 +93,15 @@ export const DocListItemComponent = memo(function DocListItemComponent({
 export const DocsExplorer = ({
   className,
   disableMultiDelete,
+  masonryItemWidthMin,
+  heightBase,
+  heightScale,
 }: {
   className?: string;
   disableMultiDelete?: boolean;
+  masonryItemWidthMin?: number;
+  heightBase?: number;
+  heightScale?: number;
 }) => {
   const t = useI18n();
   const contextValue = useContext(DocExplorerContext);
@@ -126,7 +132,7 @@ export const DocsExplorer = ({
                 ? 42
                 : view === 'grid'
                   ? 280
-                  : calcCardHeightById(docId),
+                  : calcCardHeightById(docId, heightBase, heightScale),
             'data-view': view,
             className: styles.docItem,
           };
@@ -134,7 +140,7 @@ export const DocsExplorer = ({
       } satisfies MasonryGroup;
     });
     return items;
-  }, [groupBy, groups, view]);
+  }, [groupBy, groups, heightBase, heightScale, view]);
 
   const handleCloseFloatingToolbar = useCallback(() => {
     contextValue.selectMode$?.next(false);
@@ -206,7 +212,7 @@ export const DocsExplorer = ({
         groupsGap={12}
         groupHeaderGapWithItems={12}
         columns={view === 'list' ? 1 : undefined}
-        itemWidthMin={220}
+        itemWidthMin={masonryItemWidthMin ?? 220}
         preloadHeight={100}
         itemWidth={'stretch'}
         virtualScroll
diff --git a/packages/frontend/core/src/mobile/pages/workspace/all.tsx b/packages/frontend/core/src/mobile/pages/workspace/all.tsx
index d7ae521e3ed6e..312c01349eb70 100644
--- a/packages/frontend/core/src/mobile/pages/workspace/all.tsx
+++ b/packages/frontend/core/src/mobile/pages/workspace/all.tsx
@@ -1,14 +1,88 @@
-import { useThemeColorV2 } from '@affine/component';
+import { useThemeColorV2, Wrapper } from '@affine/component';
+import { EmptyDocs } from '@affine/core/components/affine/empty';
+import {
+  createDocExplorerContext,
+  DocExplorerContext,
+} from '@affine/core/components/explorer/context';
+import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
+import { CollectionRulesService } from '@affine/core/modules/collection-rules';
+import { useLiveData, useService } from '@toeverything/infra';
+import { useEffect, useState } from 'react';
 
 import { Page } from '../../components/page';
-import { AllDocList, AllDocsHeader } from '../../views';
+import { AllDocsHeader } from '../../views';
+
+const AllDocs = () => {
+  const [explorerContextValue] = useState(() =>
+    createDocExplorerContext({
+      quickFavorite: true,
+      displayProperties: ['createdAt', 'updatedAt', 'tags'],
+      view: 'masonry',
+      showDragHandle: false,
+    })
+  );
+  const collectionRulesService = useService(CollectionRulesService);
+  const groups = useLiveData(explorerContextValue.groups$);
+  const isEmpty =
+    groups.length === 0 ||
+    (groups.length && groups.every(group => !group.items.length));
+
+  useEffect(() => {
+    const subscription = collectionRulesService
+      .watch({
+        filters: [
+          { type: 'system', key: 'trash', method: 'is', value: 'false' },
+        ],
+        extraFilters: [
+          { type: 'system', key: 'trash', method: 'is', value: 'false' },
+          {
+            type: 'system',
+            key: 'empty-journal',
+            method: 'is',
+            value: 'false',
+          },
+        ],
+        orderBy: {
+          type: 'property',
+          key: 'createdAt',
+          desc: true,
+        },
+      })
+      .subscribe({
+        next: result => {
+          explorerContextValue.groups$.next(result.groups);
+        },
+        error: console.error,
+      });
+    return () => subscription.unsubscribe();
+  }, [collectionRulesService, explorerContextValue.groups$]);
+
+  if (isEmpty) {
+    return (
+      <>
+        <EmptyDocs absoluteCenter />
+        <Wrapper height={0} flexGrow={1} />
+      </>
+    );
+  }
+
+  return (
+    <DocExplorerContext.Provider value={explorerContextValue}>
+      <DocsExplorer
+        masonryItemWidthMin={150}
+        heightBase={180}
+        heightScale={12}
+      />
+    </DocExplorerContext.Provider>
+  );
+};
 
 export const Component = () => {
   useThemeColorV2('layer/background/mobile/primary');
 
   return (
     <Page header={<AllDocsHeader />} tab>
-      <AllDocList />
+      <AllDocs />
     </Page>
   );
 };
diff --git a/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx b/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx
index eb4182380c93d..1fb6c8ed7a722 100644
--- a/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx
+++ b/packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx
@@ -1,11 +1,20 @@
-import { EmptyCollectionDetail } from '@affine/core/components/affine/empty';
+import { Wrapper } from '@affine/component';
+import {
+  EmptyCollectionDetail,
+  EmptyDocs,
+} from '@affine/core/components/affine/empty';
+import {
+  createDocExplorerContext,
+  DocExplorerContext,
+} from '@affine/core/components/explorer/context';
+import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
 import { PageHeader } from '@affine/core/mobile/components';
 import { Page } from '@affine/core/mobile/components/page';
 import type { Collection } from '@affine/core/modules/collection';
 import { ViewLayersIcon } from '@blocksuite/icons/rc';
 import { useLiveData } from '@toeverything/infra';
+import { useEffect, useState } from 'react';
 
-import { AllDocList } from '../doc/list';
 import * as styles from './detail.css';
 
 export const DetailHeader = ({ collection }: { collection: Collection }) => {
@@ -20,6 +29,55 @@ export const DetailHeader = ({ collection }: { collection: Collection }) => {
   );
 };
 
+const CollectionDocs = ({ collection }: { collection: Collection }) => {
+  const [explorerContextValue] = useState(() =>
+    createDocExplorerContext({
+      quickFavorite: true,
+      displayProperties: ['createdAt', 'updatedAt', 'tags'],
+      view: 'masonry',
+      showDragHandle: false,
+    })
+  );
+  const groups = useLiveData(explorerContextValue.groups$);
+  const isEmpty =
+    groups.length === 0 ||
+    (groups.length && groups.every(group => !group.items.length));
+
+  useEffect(() => {
+    const subscription = collection.watch().subscribe({
+      next: result => {
+        explorerContextValue.groups$.next([
+          {
+            key: 'collection',
+            items: result,
+          },
+        ]);
+      },
+      error: console.error,
+    });
+    return () => subscription.unsubscribe();
+  }, [collection, explorerContextValue.groups$]);
+
+  if (isEmpty) {
+    return (
+      <>
+        <EmptyDocs absoluteCenter />
+        <Wrapper height={0} flexGrow={1} />
+      </>
+    );
+  }
+
+  return (
+    <DocExplorerContext.Provider value={explorerContextValue}>
+      <DocsExplorer
+        masonryItemWidthMin={150}
+        heightBase={180}
+        heightScale={12}
+      />
+    </DocExplorerContext.Provider>
+  );
+};
+
 export const CollectionDetail = ({
   collection,
 }: {
@@ -38,7 +96,7 @@ export const CollectionDetail = ({
 
   return (
     <Page header={<DetailHeader collection={collection} />}>
-      <AllDocList collection={collection} />
+      <CollectionDocs collection={collection} />
     </Page>
   );
 };
diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/index.ts b/packages/frontend/core/src/mobile/views/all-docs/doc/index.ts
deleted file mode 100644
index 9c183702d6f73..0000000000000
--- a/packages/frontend/core/src/mobile/views/all-docs/doc/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './list';
-export * from './masonry';
-export * from './menu';
diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts b/packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts
deleted file mode 100644
index 0334cebf454a1..0000000000000
--- a/packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { cssVarV2 } from '@toeverything/theme/v2';
-import { globalStyle, style } from '@vanilla-extract/css';
-
-export const groupTitle = style({
-  display: 'flex',
-  alignItems: 'center',
-  padding: '0px 16px',
-  width: '100%',
-});
-// to override style defined in `core`
-globalStyle(`${groupTitle} > div`, {
-  marginRight: -4,
-});
-globalStyle(`${groupTitle} div[data-testid^='group-label']`, {
-  fontSize: `20px !important`,
-  color: `${cssVarV2('text/primary')} !important`,
-  lineHeight: '25px !important',
-});
-export const groupTitleIcon = style({
-  color: cssVarV2('icon/tertiary'),
-  transition: 'transform 0.2s',
-  selectors: {
-    '[data-state="closed"] &': {
-      transform: `rotate(-90deg)`,
-    },
-  },
-});
-export const groups = style({
-  display: 'flex',
-  flexDirection: 'column',
-  gap: 32,
-});
-export const emptySpaceY = style({
-  height: 0,
-  flexGrow: 1,
-});
diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx b/packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx
deleted file mode 100644
index 784998fd64795..0000000000000
--- a/packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { EmptyDocs } from '@affine/core/components/affine/empty';
-import { useBlockSuiteDocMeta } from '@affine/core/components/hooks/use-block-suite-page-meta';
-import {
-  type ItemGroupProps,
-  useAllDocDisplayProperties,
-} from '@affine/core/components/page-list';
-import type { Collection } from '@affine/core/modules/collection';
-import { DocsService } from '@affine/core/modules/doc';
-import type { Tag } from '@affine/core/modules/tag';
-import { WorkspaceService } from '@affine/core/modules/workspace';
-import type { DocMeta } from '@blocksuite/affine/store';
-import { ToggleDownIcon } from '@blocksuite/icons/rc';
-import * as Collapsible from '@radix-ui/react-collapsible';
-import { LiveData, useLiveData, useService } from '@toeverything/infra';
-import { useEffect, useMemo, useState } from 'react';
-
-import * as styles from './list.css';
-import { MasonryDocs } from './masonry';
-
-export const DocGroup = ({ group }: { group: ItemGroupProps<DocMeta> }) => {
-  const [properties] = useAllDocDisplayProperties();
-  const showTags = properties.displayProperties.tags;
-
-  if (group.id === 'all') {
-    return <MasonryDocs items={group.items} showTags={showTags} />;
-  }
-
-  return (
-    <Collapsible.Root defaultOpen>
-      <Collapsible.Trigger className={styles.groupTitle}>
-        {group.label}
-        <ToggleDownIcon className={styles.groupTitleIcon} />
-      </Collapsible.Trigger>
-      <Collapsible.Content>
-        <MasonryDocs items={group.items} showTags={showTags} />
-      </Collapsible.Content>
-    </Collapsible.Root>
-  );
-};
-
-export interface AllDocListProps {
-  collection?: Collection;
-  tag?: Tag;
-  trash?: boolean;
-}
-
-export const AllDocList = ({ trash, collection, tag }: AllDocListProps) => {
-  const [properties] = useAllDocDisplayProperties();
-  const workspace = useService(WorkspaceService).workspace;
-  const allPageMetas = useBlockSuiteDocMeta(workspace.docCollection);
-  const docsService = useService(DocsService);
-
-  const allTrashPageIds = useLiveData(
-    LiveData.from(docsService.allTrashDocIds$(), [])
-  );
-
-  const tagPageIds = useLiveData(tag?.pageIds$);
-
-  const [filteredPageIds, setFilteredPageIds] = useState<string[]>([]);
-
-  useEffect(() => {
-    const subscription = collection?.watch().subscribe(docIds => {
-      setFilteredPageIds(docIds);
-    });
-    return () => subscription?.unsubscribe();
-  }, [collection]);
-
-  const finalPageMetas = useMemo(() => {
-    const collectionFilteredPageMetas = collection
-      ? allPageMetas.filter(page => filteredPageIds.includes(page.id))
-      : allPageMetas;
-
-    const filteredPageMetas = collectionFilteredPageMetas.filter(
-      page => allTrashPageIds.includes(page.id) === !!trash
-    );
-
-    if (tag) {
-      const pageIdsSet = new Set(tagPageIds);
-      return filteredPageMetas.filter(page => pageIdsSet.has(page.id));
-    }
-    return filteredPageMetas;
-  }, [
-    allPageMetas,
-    allTrashPageIds,
-    collection,
-    filteredPageIds,
-    tag,
-    tagPageIds,
-    trash,
-  ]);
-
-  if (!finalPageMetas.length) {
-    return (
-      <>
-        <EmptyDocs absoluteCenter tagId={tag?.id} />
-        <div className={styles.emptySpaceY} />
-      </>
-    );
-  }
-
-  return (
-    <MasonryDocs
-      items={finalPageMetas}
-      showTags={properties.displayProperties.tags}
-    />
-  );
-};
diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts b/packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts
deleted file mode 100644
index b5b40bd58c3ab..0000000000000
--- a/packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { style } from '@vanilla-extract/css';
-
-export const paddingX = 16;
-export const columnGap = 17;
-
-export const columns = style({
-  padding: `16px ${paddingX}px`,
-  display: 'flex',
-  gap: columnGap,
-});
-
-export const column = style({
-  display: 'flex',
-  flexDirection: 'column',
-  gap: 10,
-  width: 0,
-  flex: 1,
-});
diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx b/packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx
deleted file mode 100644
index b7fd815a89b07..0000000000000
--- a/packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Masonry } from '@affine/component';
-import type { DocMeta } from '@blocksuite/affine/store';
-import { useMemo } from 'react';
-
-import { calcRowsById, DocCard } from '../../../components';
-
-const fullStyle = {
-  width: '100%',
-  height: '100%',
-};
-
-export const MasonryDocs = ({
-  items,
-  showTags,
-}: {
-  items: DocMeta[];
-  showTags?: boolean;
-}) => {
-  const masonryItems = useMemo(
-    () =>
-      items.map(item => {
-        return {
-          id: item.id,
-          height: calcRowsById(item.id) * 18 + 95,
-          children: (
-            <DocCard style={fullStyle} meta={item} showTags={showTags} />
-          ),
-        };
-      }),
-    [items, showTags]
-  );
-  return (
-    <Masonry
-      style={fullStyle}
-      itemWidthMin={160}
-      gapX={17}
-      gapY={10}
-      paddingX={16}
-      paddingY={16}
-      virtualScroll
-      items={masonryItems}
-    />
-  );
-};
diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts b/packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts
deleted file mode 100644
index 9a0dafe726996..0000000000000
--- a/packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { bodyEmphasized, bodyRegular } from '@toeverything/theme/typography';
-import { cssVarV2 } from '@toeverything/theme/v2';
-import { style } from '@vanilla-extract/css';
-
-export const root = style({
-  display: 'flex',
-  flexDirection: 'column',
-  gap: 10,
-});
-export const head = style([
-  bodyEmphasized,
-  {
-    padding: '10px 20px',
-    color: cssVarV2('text/primary'),
-  },
-]);
-
-export const item = style([
-  bodyRegular,
-  {
-    display: 'flex',
-    justifyContent: 'space-between',
-    alignItems: 'center',
-    gap: 4,
-    height: 34,
-    padding: '0 20px',
-  },
-]);
-export const itemSuffix = style({
-  display: 'flex',
-  gap: 8,
-  alignItems: 'center',
-});
-export const itemSuffixText = style({
-  color: cssVarV2('text/secondary'),
-});
-export const itemSuffixIcon = style({
-  color: cssVarV2('icon/primary'),
-  fontSize: 20,
-});
-export const divider = style({
-  width: '100%',
-  height: 16,
-  display: 'flex',
-  alignItems: 'center',
-  ':before': {
-    content: "''",
-    height: 0.5,
-    width: '100%',
-    backgroundColor: cssVarV2('layer/insideBorder/border'),
-  },
-});
-export const propertiesList = style({
-  padding: '4px 20px',
-  display: 'flex',
-  gap: 8,
-});
-export const propertyButton = style({
-  opacity: 0.4,
-  selectors: {
-    '&[data-selected="true"]': {
-      opacity: 1,
-    },
-  },
-});
diff --git a/packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx b/packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx
deleted file mode 100644
index 6fa1b55258844..0000000000000
--- a/packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { Button, MobileMenu, MobileMenuItem } from '@affine/component';
-import {
-  getGroupOptions,
-  useAllDocDisplayProperties,
-} from '@affine/core/components/page-list';
-import { useI18n } from '@affine/i18n';
-import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
-import { useMemo } from 'react';
-
-import * as styles from './menu.css';
-
-export const AllDocsMenu = () => {
-  const t = useI18n();
-
-  const [properties, setProperties] = useAllDocDisplayProperties();
-  const groupOptions = useMemo(() => getGroupOptions(t), [t]);
-
-  const activeGroup = useMemo(
-    () => groupOptions.find(g => g.value === properties.groupBy),
-    [groupOptions, properties.groupBy]
-  );
-
-  return (
-    <div className={styles.root}>
-      <header className={styles.head}>Display Settings</header>
-      <MobileMenu
-        items={
-          <>
-            <div className={styles.divider} />
-            {groupOptions.map(group => (
-              <MobileMenuItem
-                onSelect={() => setProperties('groupBy', group.value)}
-                selected={properties.groupBy === group.value}
-                key={group.value}
-              >
-                {group.label}
-              </MobileMenuItem>
-            ))}
-          </>
-        }
-      >
-        <div className={styles.item}>
-          <span>{t['com.affine.page.display.grouping']()}</span>
-          <div className={styles.itemSuffix}>
-            <span className={styles.itemSuffixText}>{activeGroup?.label}</span>
-            <ArrowRightSmallIcon className={styles.itemSuffixIcon} />
-          </div>
-        </div>
-      </MobileMenu>
-
-      <div className={styles.divider} />
-
-      <div className={styles.item}>
-        {t['com.affine.page.display.display-properties']()}
-      </div>
-      <div className={styles.propertiesList}>
-        <Button
-          size="large"
-          className={styles.propertyButton}
-          data-selected={properties.displayProperties.tags}
-          onClick={() =>
-            setProperties('displayProperties', {
-              ...properties.displayProperties,
-              tags: !properties.displayProperties.tags,
-            })
-          }
-        >
-          Tag
-        </Button>
-      </div>
-    </div>
-  );
-};
diff --git a/packages/frontend/core/src/mobile/views/all-docs/index.ts b/packages/frontend/core/src/mobile/views/all-docs/index.ts
index 7cc2e75cfc1c5..200e45b17ecd8 100644
--- a/packages/frontend/core/src/mobile/views/all-docs/index.ts
+++ b/packages/frontend/core/src/mobile/views/all-docs/index.ts
@@ -1,4 +1,3 @@
 export * from './collection';
-export * from './doc/';
 export * from './header';
 export * from './tag';
diff --git a/packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx b/packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx
index f11b68292ad18..7110a17511bf8 100644
--- a/packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx
+++ b/packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx
@@ -1,13 +1,93 @@
+import { Wrapper } from '@affine/component';
+import { EmptyDocs } from '@affine/core/components/affine/empty';
+import {
+  createDocExplorerContext,
+  DocExplorerContext,
+} from '@affine/core/components/explorer/context';
+import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
 import { Page } from '@affine/core/mobile/components/page';
+import { CollectionRulesService } from '@affine/core/modules/collection-rules';
 import type { Tag } from '@affine/core/modules/tag';
+import { useLiveData, useService } from '@toeverything/infra';
+import { useEffect, useState } from 'react';
 
-import { AllDocList } from '../doc';
 import { TagDetailHeader } from './detail-header';
 
+const TagDocs = ({ tag }: { tag: Tag }) => {
+  const [explorerContextValue] = useState(() =>
+    createDocExplorerContext({
+      quickFavorite: true,
+      displayProperties: ['createdAt', 'updatedAt', 'tags'],
+      view: 'masonry',
+      showDragHandle: false,
+    })
+  );
+  const collectionRulesService = useService(CollectionRulesService);
+  const groups = useLiveData(explorerContextValue.groups$);
+  const isEmpty =
+    groups.length === 0 ||
+    (groups.length && groups.every(group => !group.items.length));
+
+  useEffect(() => {
+    const subscription = collectionRulesService
+      .watch({
+        filters: [
+          { type: 'system', key: 'trash', method: 'is', value: 'false' },
+          {
+            type: 'system',
+            key: 'tags',
+            method: 'include-all',
+            value: tag.id,
+          },
+        ],
+        extraFilters: [
+          { type: 'system', key: 'trash', method: 'is', value: 'false' },
+          {
+            type: 'system',
+            key: 'empty-journal',
+            method: 'is',
+            value: 'false',
+          },
+        ],
+        orderBy: {
+          type: 'property',
+          key: 'createdAt',
+          desc: true,
+        },
+      })
+      .subscribe({
+        next: result => {
+          explorerContextValue.groups$.next(result.groups);
+        },
+        error: console.error,
+      });
+    return () => subscription.unsubscribe();
+  }, [collectionRulesService, explorerContextValue.groups$, tag.id]);
+
+  if (isEmpty) {
+    return (
+      <>
+        <EmptyDocs absoluteCenter tagId={tag.id} />
+        <Wrapper height={0} flexGrow={1} />
+      </>
+    );
+  }
+
+  return (
+    <DocExplorerContext.Provider value={explorerContextValue}>
+      <DocsExplorer
+        masonryItemWidthMin={150}
+        heightBase={180}
+        heightScale={12}
+      />
+    </DocExplorerContext.Provider>
+  );
+};
+
 export const TagDetail = ({ tag }: { tag: Tag }) => {
   return (
     <Page header={<TagDetailHeader tag={tag} />} tab>
-      <AllDocList tag={tag} />
+      <TagDocs tag={tag} />
     </Page>
   );
 };
diff --git a/tests/affine-mobile/e2e/detail.spec.ts b/tests/affine-mobile/e2e/detail.spec.ts
index 2311be725927e..71fcdb7ce1fea 100644
--- a/tests/affine-mobile/e2e/detail.spec.ts
+++ b/tests/affine-mobile/e2e/detail.spec.ts
@@ -13,7 +13,7 @@ test.beforeEach(async ({ page }) => {
   const docsTab = page.locator('#app-tabs').getByRole('tab', { name: 'all' });
   await expect(docsTab).toBeVisible();
   await docsTab.click();
-  await page.getByTestId('doc-card').first().click();
+  await page.getByTestId('doc-list-item').first().click();
   await expect(page.locator('.affine-page-viewport')).toBeVisible();
 });
 
@@ -53,7 +53,5 @@ test('can add text property', async ({ page }) => {
   ).toBeVisible();
   await page.getByTestId('mobile-menu-back-button').last().click();
 
-  await expect(page.getByTestId('mobile-menu-back-button')).toContainText(
-    'Getting Started'
-  );
+  await expect(page.getByTestId('mobile-menu-back-button')).toBeVisible();
 });
diff --git a/tests/affine-mobile/e2e/home.spec.ts b/tests/affine-mobile/e2e/home.spec.ts
index 1f4b4cfe2c647..1b419fb4ec828 100644
--- a/tests/affine-mobile/e2e/home.spec.ts
+++ b/tests/affine-mobile/e2e/home.spec.ts
@@ -47,6 +47,6 @@ test('all tab', async ({ page }) => {
 
   await docsTab.click();
 
-  const todayDocs = page.getByTestId('doc-card');
+  const todayDocs = page.getByTestId('doc-list-item');
   expect(await todayDocs.count()).toBeGreaterThan(0);
 });


Reviews:
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T02:50:50Z
user: login: EYHN
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-22T02:25:24Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx (1)</summary><blockquote>

`76-84`: **Expose a `data-testid` for E2E / unit tests**

Previous feedback (see workspace/all.tsx) asked for a way to target individual cards rendered by `DocsExplorer`.  If `DocsExplorer` now supports an `itemTestId` (or similar) prop, pass it here to keep tag-detail tests consistent.

If not yet implemented, please expose one in `DocsExplorer` and propagate it from callers.

</blockquote></details>
<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (1)</summary><blockquote>

`71-75`: **Expose a `data-testid` prop to `DocsExplorer` for automated tests**

CI logs previously failed because test suites couldn’t target individual doc cards.  Please add / forward a `data-testid` such as:

```tsx
<DocsExplorer
  itemTestId="doc-card"
  masonryItemWidthMin={150}
  heightBase={180}
  heightScale={12}
/>
```

(if the prop name differs, adjust accordingly).

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (11)</summary><blockquote>

<details>
<summary>packages/frontend/component/src/ui/layout/wrapper.tsx (3)</summary><blockquote>

`19-21`: **Consider adding `flexBasis` for completeness**

Now that `flexGrow` and `flexShrink` are supported, callers may reasonably expect to be able to set `flexBasis` as well.  Adding it keeps the API symmetrical with the three-value flex shorthand and prevents ad-hoc style overrides downstream.

```diff
-  flexGrow?: CSSProperties['flexGrow'];
-  flexShrink?: CSSProperties['flexShrink'];
+  flexGrow?: CSSProperties['flexGrow'];
+  flexShrink?: CSSProperties['flexShrink'];
+  flexBasis?: CSSProperties['flexBasis'];
```

Remember to update the `shouldForwardProp` filter and the style object in the same way.

---

`34-53`: **`shouldForwardProp` list is starting to grow – extract to a reusable constant**

Both `Wrapper` and `FlexWrapper` maintain near-identical arrays of filtered props.  Keeping them in sync is error-prone (e.g. if you add `flexBasis` later).  Consider hoisting the list into a shared constant and re-using it in each styled component:

```ts
const COMMON_FILTERED_PROPS = [
  'display', 'width', 'height', 'padding', 'margin',
  /* … */
] as const;
```

Then:

```ts
shouldForwardProp: prop => !COMMON_FILTERED_PROPS.includes(prop as string),
```

This avoids subtle drift across wrappers.

---

`69-88`: **Runtime fallback for `flexGrow` / `flexShrink`**

`CSSProperties['flexGrow']` is `number | string | undefined`.  
Passing an empty string or `undefined` to the style object is harmless, but passing `null` (from uncontrolled data) throws.  Defensive casting prevents accidental runtime errors:

```diff
-    flexGrow,
-    flexShrink,
+    ...(flexGrow !== undefined ? { flexGrow } : {}),
+    ...(flexShrink !== undefined ? { flexShrink } : {}),
```

Not critical, but shields you from noisy React warnings when props are conditionally omitted.

</blockquote></details>
<details>
<summary>packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx (3)</summary><blockquote>

`16-30`: **`isEmpty` expression can be simplified & typed**

The current expression works but is a bit hard to scan and relies on JS truthiness of numbers:

```ts
const isEmpty =
  groups.length === 0 ||
  (groups.length && groups.every(group => !group.items.length));
```

A clearer (and strictly boolean-typed) alternative:

```diff
-const isEmpty =
-  groups.length === 0 ||
-  (groups.length && groups.every(group => !group.items.length));
+const isEmpty = groups.every(group => group.items.length === 0);
```

`Array.prototype.every` already returns `true` on an empty array, so the first clause is redundant.

---

`31-65`: **Subscription cleanup dependency may fire unnecessarily**

`useEffect` lists `explorerContextValue.groups$` in its dependency array.  
Because `groups$` is a **Subject** property whose identity is stable, this is harmless, but React’s exhaustive-deps eslint rule will keep flagging the entire effect when you refactor.  You can safely omit it:

```diff
-}, [collectionRulesService, explorerContextValue.groups$, tag.id]);
+}, [collectionRulesService, tag.id]);
```

This also prevents accidental re-subscription if the context implementation ever replaces `groups$`.

---

`67-74`: **Spacer div can reuse the new `flexBasis` prop**

Once `Wrapper` supports `flexBasis`, consider replacing `height={0}` with a semantic basis:

```tsx
<Wrapper flexBasis={0} flexGrow={1} />
```

Minor readability win.

</blockquote></details>
<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (1)</summary><blockquote>

`60-66`: **Accessibility: empty state wrapper needs `role="presentation"`**

The zero-height wrapper used as a flex spacer is purely decorative.  Adding `role="presentation"` (or removing it entirely in favor of modern flex utilities) prevents screen readers from announcing an empty element.

```diff
-<Wrapper height={0} flexGrow={1} />
+<Wrapper height={0} flexGrow={1} role="presentation" />
```

</blockquote></details>
<details>
<summary>packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx (2)</summary><blockquote>

`93-105`: **Provide default values at the destructuring site**

Because `heightBase`/`heightScale` are forwarded to `calcCardHeightById`, callers that omit them currently forward `undefined`, relying on the function’s defaults. Moving the defaults one level up keeps the prop contract explicit, improves IntelliSense, and prevents accidental `undefined` leaking elsewhere.

```diff
-export const DocsExplorer = ({
-  className,
-  disableMultiDelete,
-  masonryItemWidthMin,
-  heightBase,
-  heightScale,
-}: {
+export const DocsExplorer = ({
+  className,
+  disableMultiDelete,
+  masonryItemWidthMin,
+  heightBase = 250,
+  heightScale = 10,
+}: {
```

After this change the optional props still remain optional while the rest of the component can treat them as guaranteed numbers.  
 <!-- review_comment_end -->

---

`205-225`: **Tune `itemWidthMin` for tablet/desktop break-points**

You added `itemWidthMin={masonryItemWidthMin ?? 220}`. On small phones this can still yield only one column and large gutters. If the design intends at least two columns for ≥ 360 px wide screens, consider a responsive function similar to the `paddingX` prop:

```diff
- itemWidthMin={masonryItemWidthMin ?? 220}
+ itemWidthMin={
+   masonryItemWidthMin ??
+   ((w: number) => (w > 600 ? 260 : w > 400 ? 200 : 160))
+ }
```

That keeps the grid dense without the caller having to fine-tune the prop each time.  
 <!-- review_comment_end -->

</blockquote></details>
<details>
<summary>packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx (2)</summary><blockquote>

`32-45`: **`isEmpty` check has a truthy short-circuit bug**

`groups.length && groups.every(...)` returns a boolean *or* a number (when `length` is `0`). In the `0`-length case the expression is `0`, which is falsy, so the first branch covers the empty state; still, it’s brittle and confusing.

```diff
-const isEmpty =
-  groups.length === 0 ||
-  (groups.length && groups.every(group => !group.items.length));
+const isEmpty =
+  groups.length === 0 ||
+  groups.every(group => group.items.length === 0);
```

This removes the redundant `groups.length &&` guard and guarantees the resulting type is boolean.  
 <!-- review_comment_end -->

---

`71-77`: **Expose customisation props to parent for consistency**

Hard-coding `masonryItemWidthMin={150}`, `heightBase={180}`, `heightScale={12}` here diverges from the defaults defined in `DocsExplorer`. If another screen wants different numbers it now has to fork this component. Accept these values as props (`<CollectionDetail … cardSize={…} />`) or, if they are meant to be design constants, extract them to a theme token so other mobile views can reuse them.  
 <!-- review_comment_end -->

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6d662b8a54eddbfffca43d670edbcdfddce4a9d1 and a14c0aa5592b5fa5b0c65306094e00c288a45b4a.

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `packages/frontend/component/src/ui/layout/wrapper.tsx` (3 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx` (4 hunks)
* `packages/frontend/core/src/mobile/pages/workspace/all.tsx` (1 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx` (3 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/index.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/index.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (8)</summary>

* packages/frontend/core/src/mobile/views/all-docs/index.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/index.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx
* packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx
* packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (56)</summary>

* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Native Unit Test
* GitHub Check: test-build-mobile-app / build-ios-web
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: E2E Test (6)
* GitHub Check: Run native tests
* GitHub Check: loom thread test
* GitHub Check: y-octo binding test on x86_64-pc-windows-msvc
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: y-octo binding test on aarch64-apple-darwin
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E Test (10)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: E2E Test (4)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Build Server native
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Typecheck

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx (1)</summary>

`128-136`: **`heightBase` / `heightScale` not memoised – risk of unnecessary re-computations**

Because both numbers are primitive values, every render where the parent passes a new inline literal (e.g. `<DocsExplorer heightBase={180} />`) will cause `masonryItems` to recompute even if the number hasn’t changed semantically. Consider wrapping the props with `useMemo` in the parent or deriving them here with `useRef` if they are constants.  
 <!-- review_comment_end -->

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T10:01:28Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx (1)</summary><blockquote>

`16-85`: **Address code duplication with AllDocs component.**

This `TagDocs` component shares nearly identical logic with `AllDocs` in the workspace/all.tsx file. The duplication includes explorer context creation, service subscription patterns, isEmpty calculation, and rendering logic.



As noted in the previous review, consider extracting this shared logic into a reusable hook like `useDocsExplorer` that accepts filters as parameters to reduce bundle size and prevent logic divergence.

</blockquote></details>
<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (2)</summary><blockquote>

`15-78`: **Code duplication remains unaddressed.**

This `AllDocs` component continues to duplicate logic with `TagDocs` and `CollectionDocs` as noted in the previous review. The shared pattern includes context creation, service subscription, isEmpty calculation, and rendering logic.



Please consider implementing the suggested `useDocsExplorer` hook to centralize this logic and reduce maintenance overhead.

---

`71-75`: **Add missing test ID attribute for DocsExplorer.**

Based on previous review feedback, the `DocsExplorer` component needs a `data-testid='doc-card'` attribute for testing purposes.



Check the `DocsExplorer` component implementation for the appropriate prop (likely `itemTestId` or `testId`) to add this test identifier.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx (2)</summary><blockquote>

`32-65`: **Improve error handling and subscription management.**

The current error handling only logs to console, which may not provide adequate user feedback for network or service failures.



Consider enhancing error handling:

```diff
  useEffect(() => {
    const subscription = collectionRulesService
      .watch({
        // ... filters
      })
      .subscribe({
        next: result => {
          explorerContextValue.groups$.next(result.groups);
        },
-       error: console.error,
+       error: (error) => {
+         console.error('Failed to load tag documents:', error);
+         // Consider showing user-friendly error state
+       },
      });
    return () => subscription.unsubscribe();
  }, [collectionRulesService, explorerContextValue.groups$, tag.id]);
```

---

`34-51`: **Simplify filter structure for better maintainability.**

The current filter configuration has redundant trash filters in both `filters` and `extraFilters` arrays.



Consider consolidating the filters:

```diff
  .watch({
    filters: [
-     { type: 'system', key: 'trash', method: 'is', value: 'false' },
      {
        type: 'system',
        key: 'tags',
        method: 'include-all',
        value: tag.id,
      },
    ],
    extraFilters: [
      { type: 'system', key: 'trash', method: 'is', value: 'false' },
      {
        type: 'system',
        key: 'empty-journal',
        method: 'is',
        value: 'false',
      },
    ],
    // ... rest
  })
```

</blockquote></details>
<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (1)</summary><blockquote>

`30-58`: **Consistent error handling needed.**

Similar to the TagDocs component, the error handling here only logs to console without providing user feedback.



Consider implementing consistent error handling across all document explorer components to improve user experience during service failures.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a14c0aa5592b5fa5b0c65306094e00c288a45b4a and 14151d820cf1155e3e011b496b3269d47b810803.

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `packages/frontend/component/src/ui/layout/wrapper.tsx` (3 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx` (4 hunks)
* `packages/frontend/core/src/mobile/pages/workspace/all.tsx` (1 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx` (3 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/index.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/index.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (8)</summary>

* packages/frontend/core/src/mobile/views/all-docs/index.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/index.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx
* packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx
* packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx
* packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* packages/frontend/component/src/ui/layout/wrapper.tsx
* packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
* packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (81)</summary>

* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: Native Unit Test
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: y-octo binding test on x86_64-pc-windows-msvc
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: loom thread test
* GitHub Check: Build @affine/electron renderer
* GitHub Check: fuzzing
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Typecheck
* GitHub Check: Lint

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (1)</summary>

`1-14`: **Clean import organization and appropriate component structure.**

The imports are well-organized and the component follows React best practices with proper hook usage and effect cleanup.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T02:21:08Z
user: login: CatsJuice
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T02:32:50Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T02:33:17Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T05:10:40Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (2)</summary><blockquote>

`24-58`: _⚠️ Potential issue_

**Critical: Duplicate logic needs extraction as previously identified.**

This code duplicates the exact same pattern mentioned in previous reviews across `AllDocs`, `TagDocs`, and `CollectionDocs` components. The subscription logic, context creation, and isEmpty calculation should be extracted into a reusable hook as suggested in the past review.



The useEffect dependency array also has a potential issue - `explorerContextValue.groups$` might cause unnecessary re-renders. Consider using a ref or memoizing the subscription logic.

Apply this refactor to extract the duplicated logic:

```diff
+function useDocsExplorer(filters: WatchFilters) {
+  const [ctx] = useState(() => createDocExplorerContext({
+    quickFavorite: true,
+    displayProperties: ['createdAt', 'updatedAt', 'tags'],
+    view: 'masonry',
+    showDragHandle: false,
+  }));
+  const service = useService(CollectionRulesService);
+  const groups = useLiveData(ctx.groups$);
+  const isEmpty = groups.length === 0 || 
+    (groups.length && groups.every(group => !group.items.length));
+
+  useEffect(() => {
+    const sub = service.watch(filters).subscribe({
+      next: r => ctx.groups$.next(r.groups),
+      error: console.error,
+    });
+    return () => sub.unsubscribe();
+  }, [service, JSON.stringify(filters)]);
+
+  return { ctx, isEmpty };
+}

const AllDocs = () => {
-  const [explorerContextValue] = useState(() =>
-    createDocExplorerContext({
-      quickFavorite: true,
-      displayProperties: ['createdAt', 'updatedAt', 'tags'],
-      view: 'masonry',
-      showDragHandle: false,
-    })
-  );
-  const collectionRulesService = useService(CollectionRulesService);
-  const groups = useLiveData(explorerContextValue.groups$);
-  const isEmpty =
-    groups.length === 0 ||
-    (groups.length && groups.every(group => !group.items.length));
-
-  useEffect(() => {
-    const subscription = collectionRulesService
-      .watch({
-        filters: [
-          { type: 'system', key: 'trash', method: 'is', value: 'false' },
-        ],
-        extraFilters: [
-          { type: 'system', key: 'trash', method: 'is', value: 'false' },
-          {
-            type: 'system',
-            key: 'empty-journal',
-            method: 'is',
-            value: 'false',
-          },
-        ],
-        orderBy: {
-          type: 'property',
-          key: 'createdAt',
-          desc: true,
-        },
-      })
-      .subscribe({
-        next: result => {
-          explorerContextValue.groups$.next(result.groups);
-        },
-        error: console.error,
-      });
-    return () => subscription.unsubscribe();
-  }, [collectionRulesService, explorerContextValue.groups$]);
+  const { ctx: explorerContextValue, isEmpty } = useDocsExplorer({
+    filters: [
+      { type: 'system', key: 'trash', method: 'is', value: 'false' },
+    ],
+    extraFilters: [
+      { type: 'system', key: 'trash', method: 'is', value: 'false' },
+      {
+        type: 'system',
+        key: 'empty-journal',
+        method: 'is',
+        value: 'false',
+      },
+    ],
+    orderBy: {
+      type: 'property',
+      key: 'createdAt',
+      desc: true,
+    },
+  });
```

---

`71-75`: _⚠️ Potential issue_

**Critical: Missing test ID attribute as previously identified.**

The `DocsExplorer` component still lacks the required `data-testid='doc-card'` attribute that was flagged in previous reviews. This is needed for proper testing.



Add the missing test ID attribute to the `DocsExplorer` component. Check the `DocsExplorer` component's props interface to determine the correct prop name (likely `itemTestId` or `testId`).

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (2)</summary><blockquote>

`16-23`: **Consider extracting explorer configuration to constants.**

The explorer context configuration contains hardcoded values that could benefit from being extracted to named constants for better maintainability and reusability.


Consider extracting the configuration:

```diff
+const DEFAULT_EXPLORER_CONFIG = {
+  quickFavorite: true,
+  displayProperties: ['createdAt', 'updatedAt', 'tags'] as const,
+  view: 'masonry' as const,
+  showDragHandle: false,
+};

const AllDocs = () => {
  const [explorerContextValue] = useState(() =>
-    createDocExplorerContext({
-      quickFavorite: true,
-      displayProperties: ['createdAt', 'updatedAt', 'tags'],
-      view: 'masonry',
-      showDragHandle: false,
-    })
+    createDocExplorerContext(DEFAULT_EXPLORER_CONFIG)
  );
```

---

`60-67`: **Verify empty state accessibility and UX.**

The empty state implementation looks clean, but consider adding proper accessibility attributes and ensuring the empty state provides helpful guidance to users.


Consider enhancing the empty state:

```diff
  if (isEmpty) {
    return (
-      <>
+      <div role="main" aria-label="No documents available">
        <EmptyDocs absoluteCenter />
        <Wrapper height={0} flexGrow={1} />
-      </>
+      </div>
    );
  }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ac37b257885bddc6257eab82092fec5909adc8a8 and a96cd3eb0a7f077cbb87d0d90b1e5e32f9c237ea.

</details>

<details>
<summary>📒 Files selected for processing (15)</summary>

* `packages/frontend/component/src/ui/layout/wrapper.tsx` (3 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx` (4 hunks)
* `packages/frontend/core/src/mobile/pages/workspace/all.tsx` (1 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx` (3 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/index.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/index.ts` (0 hunks)
* `packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx` (1 hunks)
* `tests/affine-mobile/e2e/detail.spec.ts` (2 hunks)
* `tests/affine-mobile/e2e/home.spec.ts` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (8)</summary>

* packages/frontend/core/src/mobile/views/all-docs/index.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/index.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/menu.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/menu.tsx
* packages/frontend/core/src/mobile/views/all-docs/doc/masonry.tsx
* packages/frontend/core/src/mobile/views/all-docs/doc/masonry.css.ts
* packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (6)</summary>

* tests/affine-mobile/e2e/home.spec.ts
* tests/affine-mobile/e2e/detail.spec.ts
* packages/frontend/component/src/ui/layout/wrapper.tsx
* packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx
* packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
* packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (655)</summary>

* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: Run native tests
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: test-build-mobile-app / android
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test with Elasticsearch
* GitHub Check: Server E2E Test
* GitHub Check: Check Git Status
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (2)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: fuzzing
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/mobile/pages/workspace/all.tsx (2)</summary>

`1-13`: **LGTM: Imports are well-organized and appropriate.**

The imports are logically grouped and include all necessary dependencies for the new document explorer implementation.

---

`80-88`: **LGTM: Component export is clean and follows established patterns.**

The main Component export properly sets the theme and renders the page structure with the new AllDocs component.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T07:12:10Z

Review comments:
diff_hunk: @@ -1,14 +1,88 @@
-import { useThemeColorV2 } from '@affine/component';
+import { useThemeColorV2, Wrapper } from '@affine/component';
+import { EmptyDocs } from '@affine/core/components/affine/empty';
+import {
+  createDocExplorerContext,
+  DocExplorerContext,
+} from '@affine/core/components/explorer/context';
+import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
+import { CollectionRulesService } from '@affine/core/modules/collection-rules';
+import { useLiveData, useService } from '@toeverything/infra';
+import { useEffect, useState } from 'react';
 
 import { Page } from '../../components/page';
-import { AllDocList, AllDocsHeader } from '../../views';
+import { AllDocsHeader } from '../../views';
+
+const AllDocs = () => {
+  const [explorerContextValue] = useState(() =>
+    createDocExplorerContext({
+      quickFavorite: true,
+      displayProperties: ['createdAt', 'updatedAt', 'tags'],
+      view: 'masonry',
+      showDragHandle: false,
+    })
+  );
+  const collectionRulesService = useService(CollectionRulesService);
+  const groups = useLiveData(explorerContextValue.groups$);
+  const isEmpty =
+    groups.length === 0 ||
+    (groups.length && groups.every(group => !group.items.length));
+
+  useEffect(() => {
+    const subscription = collectionRulesService
+      .watch({
+        filters: [
+          { type: 'system', key: 'trash', method: 'is', value: 'false' },
+        ],
+        extraFilters: [
+          { type: 'system', key: 'trash', method: 'is', value: 'false' },
+          {
+            type: 'system',
+            key: 'empty-journal',
+            method: 'is',
+            value: 'false',
+          },
+        ],
+        orderBy: {
+          type: 'property',
+          key: 'createdAt',
+          desc: true,
+        },
+      })
+      .subscribe({
+        next: result => {
+          explorerContextValue.groups$.next(result.groups);
+        },
+        error: console.error,
+      });
+    return () => subscription.unsubscribe();
+  }, [collectionRulesService, explorerContextValue.groups$]);
+
+  if (isEmpty) {
+    return (
+      <>
+        <EmptyDocs absoluteCenter />
+        <Wrapper height={0} flexGrow={1} />
+      </>
+    );
+  }
+
+  return (
+    <DocExplorerContext.Provider value={explorerContextValue}>
+      <DocsExplorer
+        masonryItemWidthMin={150}
+        heightBase={180}
+        heightScale={12}
+      />
path: packages/frontend/core/src/mobile/pages/workspace/all.tsx
user: login: graphite-app[bot]
body: The DocsExplorer component needs a data-testid='doc-card' attribute or a prop that would add this test ID to its items. Without seeing the implementation of DocsExplorer, I can't be certain of the exact prop name, but it might be something like 'itemTestId' or 'testId'. Check the DocsExplorer component implementation for the appropriate prop to use.

*Spotted by [Diamond](https://app.graphite.dev/diamond/?org=toeverything&ref=ai-review-comment) (based on CI logs)*<i class='graphite__hidden'><br /><br />Is this helpful? React 👍 or 👎 to let us know.</i>
created_at: 2025-05-19T02:50:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,14 +1,88 @@
-import { useThemeColorV2 } from '@affine/component';
+import { useThemeColorV2, Wrapper } from '@affine/component';
+import { EmptyDocs } from '@affine/core/components/affine/empty';
+import {
+  createDocExplorerContext,
+  DocExplorerContext,
+} from '@affine/core/components/explorer/context';
+import { DocsExplorer } from '@affine/core/components/explorer/docs-view/docs-list';
+import { CollectionRulesService } from '@affine/core/modules/collection-rules';
+import { useLiveData, useService } from '@toeverything/infra';
+import { useEffect, useState } from 'react';
 
 import { Page } from '../../components/page';
-import { AllDocList, AllDocsHeader } from '../../views';
+import { AllDocsHeader } from '../../views';
+
+const AllDocs = () => {
+  const [explorerContextValue] = useState(() =>
+    createDocExplorerContext({
+      quickFavorite: true,
+      displayProperties: ['createdAt', 'updatedAt', 'tags'],
+      view: 'masonry',
+      showDragHandle: false,
+    })
+  );
+  const collectionRulesService = useService(CollectionRulesService);
+  const groups = useLiveData(explorerContextValue.groups$);
+  const isEmpty =
+    groups.length === 0 ||
+    (groups.length && groups.every(group => !group.items.length));
+
+  useEffect(() => {
path: packages/frontend/core/src/mobile/pages/workspace/all.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Duplicate explorer wiring – extract a reusable hook**

`AllDocs`, `TagDocs`, and `CollectionDocs` all share identical logic:

1. `createDocExplorerContext`
2. `collectionRulesService.watch(...).subscribe(...)`
3. `isEmpty` calculation

This duplication risks divergence and inflates bundle size.  A small hook,

```ts
function useDocsExplorer(filters: WatchFilters) {
  const [ctx] = useState(() => createDocExplorerContext(/* … */));
  const service = useService(CollectionRulesService);
  useEffect(() => {
    const sub = service.watch(filters).subscribe({
      next: r => ctx.groups$.next(r.groups),
      error: console.error,
    });
    return () => sub.unsubscribe();
  }, [service, JSON.stringify(filters)]);
  return ctx;
}
```

would let every screen remain a few lines long and centralize the watch parameters.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/frontend/core/src/mobile/pages/workspace/all.tsx around lines 24 to
30, the code duplicates the explorer wiring logic including context creation,
service subscription, and isEmpty calculation across multiple components.
Refactor by extracting this shared logic into a reusable hook named
useDocsExplorer that accepts filters, creates the context, subscribes to the
service watch with proper cleanup, and returns the context. Replace the
duplicated code in AllDocs, TagDocs, and CollectionDocs with calls to this new
hook to centralize logic and reduce bundle size.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-22T10:01:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -69,15 +69,15 @@ const GroupHeader = memo(function GroupHeader({
   return header;
 });
 
-const calcCardHeightById = (id: string) => {
+const calcCardHeightById = (id: string, base = 250, scale = 10) => {
   if (!id) {
-    return 250;
+    return base;
   }
   const max = 5;
   const min = 1;
   const code = id.charCodeAt(0);
   const value = Math.floor((code % (max - min)) + min);
-  return 250 + value * 10;
+  return base + value * scale;
 };
path: packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Guard `base`/`scale` to avoid negative or `NaN` heights**

Allowing callers to pass arbitrary numbers (or even `undefined`, `null`, or `NaN`) can yield negative or non-finite heights which would break the Masonry virtual-scroll algorithm and cause layout thrashing.

```diff
-const calcCardHeightById = (id: string, base = 250, scale = 10) => {
+const clamp = (n: number, min: number, max: number) =>
+  Number.isFinite(n) ? Math.min(Math.max(n, min), max) : min;
+
+const calcCardHeightById = (
+  id: string,
+  base: number = 250,
+  scale: number = 10
+) => {
   if (!id) {
-    return base;
+    return clamp(base, 100, 600);
   }
@@
-  return base + value * scale;
+  return clamp(base, 100, 600) + value * clamp(scale, 4, 40);
 }
```

Clamping (or at least validating) keeps the masonry stable and avoids degenerate DOM updates.  
 <!-- review_comment_end -->



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
const clamp = (n: number, min: number, max: number) =>
  Number.isFinite(n) ? Math.min(Math.max(n, min), max) : min;

const calcCardHeightById = (
  id: string,
  base: number = 250,
  scale: number = 10
) => {
  if (!id) {
    return clamp(base, 100, 600);
  }
  const max = 5;
  const min = 1;
  const code = id.charCodeAt(0);
  const value = Math.floor((code % (max - min)) + min);
  return clamp(base, 100, 600) + value * clamp(scale, 4, 40);
};
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx around
lines 72 to 81, the function calcCardHeightById does not validate or clamp the
base and scale parameters, which can lead to negative or NaN heights causing
layout issues. Add validation to ensure base and scale are finite numbers and
clamp them to sensible minimum values (e.g., base >= 0 and scale >= 0) before
using them in calculations to prevent invalid heights and maintain Masonry
stability.
```

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-22T10:01:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,6 +29,55 @@ export const DetailHeader = ({ collection }: { collection: Collection }) => {
   );
 };
 
+const CollectionDocs = ({ collection }: { collection: Collection }) => {
+  const [explorerContextValue] = useState(() =>
+    createDocExplorerContext({
+      quickFavorite: true,
+      displayProperties: ['createdAt', 'updatedAt', 'tags'],
+      view: 'masonry',
+      showDragHandle: false,
+    })
+  );
+  const groups = useLiveData(explorerContextValue.groups$);
+  const isEmpty =
+    groups.length === 0 ||
+    (groups.length && groups.every(group => !group.items.length));
+
+  useEffect(() => {
+    const subscription = collection.watch().subscribe({
+      next: result => {
+        explorerContextValue.groups$.next([
+          {
+            key: 'collection',
+            items: result,
+          },
+        ]);
+      },
+      error: console.error,
+    });
+    return () => subscription.unsubscribe();
+  }, [collection, explorerContextValue.groups$]);
+
path: packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Stale `explorerContextValue` when `collection` prop changes**

`useState(() => createDocExplorerContext(...))` initialises the context only once. If `<CollectionDetail>` ever receives a new `collection` prop (e.g. the router re-uses the component), the old context – holding subscriptions, selected IDs, etc. – persists, leaking memory and mixing documents from two collections.

Guard against this by re-creating the context when the `collection` instance changes:

```diff
-const [explorerContextValue] = useState(() =>
-  createDocExplorerContext({ … })
-);
+const explorerContextValue = useMemo(
+  () =>
+    createDocExplorerContext({
+      quickFavorite: true,
+      displayProperties: ['createdAt', 'updatedAt', 'tags'],
+      view: 'masonry',
+      showDragHandle: false,
+    }),
+  [collection.id] // or another stable identifier
+);
```

Remember to clean up the previous context’s subscriptions in a return callback of another `useEffect` if necessary.  
 <!-- review_comment_end -->



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
// Replace the useState-based context creation with useMemo so it re-initializes
// whenever the collection ID changes.

import { useEffect, useMemo } from 'react';
import { createDocExplorerContext } from 'path/to/docExplorer';

function CollectionDetail({ collection }) {
  const explorerContextValue = useMemo(
    () =>
      createDocExplorerContext({
        quickFavorite: true,
        displayProperties: ['createdAt', 'updatedAt', 'tags'],
        view: 'masonry',
        showDragHandle: false,
      }),
    [collection.id]
  );

  useEffect(() => {
    const subscription = collection.watch().subscribe({
      next: result => {
        explorerContextValue.groups$.next([
          {
            key: 'collection',
            items: result,
          },
        ]);
      },
      error: console.error,
    });
    return () => subscription.unsubscribe();
  }, [collection, explorerContextValue.groups$]);

  // …rest of component
}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx around
lines 46 to 60, the explorerContextValue is created once and not updated when
the collection prop changes, causing stale context and potential memory leaks.
Modify the code to recreate the explorerContextValue whenever the collection
instance changes by using a useEffect or useMemo hook that depends on the
collection prop. Also, ensure to clean up any subscriptions or resources from
the previous context in a cleanup function within a useEffect to prevent leaks.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-22T10:01:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -69,15 +69,15 @@ const GroupHeader = memo(function GroupHeader({
   return header;
 });
 
-const calcCardHeightById = (id: string) => {
+const calcCardHeightById = (id: string, base = 250, scale = 10) => {
   if (!id) {
-    return 250;
+    return base;
   }
   const max = 5;
   const min = 1;
   const code = id.charCodeAt(0);
   const value = Math.floor((code % (max - min)) + min);
-  return 250 + value * 10;
+  return base + value * scale;
 };
path: packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
user: login: CatsJuice
body: deprecated in down-stack pr
created_at: 2025-05-23T02:32:50Z
author_association: MEMBER
diff_hunk: @@ -69,15 +69,15 @@ const GroupHeader = memo(function GroupHeader({
   return header;
 });
 
-const calcCardHeightById = (id: string) => {
+const calcCardHeightById = (id: string, base = 250, scale = 10) => {
   if (!id) {
-    return 250;
+    return base;
   }
   const max = 5;
   const min = 1;
   const code = id.charCodeAt(0);
   const value = Math.floor((code % (max - min)) + min);
-  return 250 + value * 10;
+  return base + value * scale;
 };
path: packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
user: login: coderabbitai[bot]
body: @CatsJuice Thanks for letting me know that this code is deprecated in a down-stack PR. I'll close this thread since the suggested changes won't be necessary for code that's being removed. This is a good example of efficient workflow in stacked PRs.

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20layout%20thrashing%20in%20Masonry%20by%20clamping%20%60base%60%20and%20%60scale%60%20heights%20to%20valid%20ranges!%20Prevent%20NaN%2Fnegative%20values%20breaking%20your%20UI.%20Stability%20matters.%20Try%20%40coderabbitai%20for%20smart%20code%20fixes!%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2Ftoeverything%2FAFFiNE%2Fpull%2F12329%23discussion_r2102160095)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@CatsJuice Thanks for letting me know that this code is deprecated in a down-stack PR. I'll close this thread since the suggested changes won't be necessary for code that's being removed. This is a good example of efficient workflow in stacked PRs.

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20layout%20thrashing%20in%20Masonry%20by%20clamping%20%60base%60%20and%20%60scale%60%20heights%20to%20valid%20ranges!%20Prevent%20NaN%2Fnegative%20values%20breaking%20your%20UI.%20Stability%20matters.%20Try%20%40coderabbitai%20for%20smart%20code%20fixes!%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2Ftoeverything%2FAFFiNE%2Fpull%2F12329%23discussion_r2102160095)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-23T02:33:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -69,15 +69,15 @@ const GroupHeader = memo(function GroupHeader({
   return header;
 });
 
-const calcCardHeightById = (id: string) => {
+const calcCardHeightById = (id: string, base = 250, scale = 10) => {
   if (!id) {
-    return 250;
+    return base;
   }
   const max = 5;
   const min = 1;
   const code = id.charCodeAt(0);
   const value = Math.floor((code % (max - min)) + min);
path: packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx
user: login: graphite-app[bot]
body: The randomization logic has a range calculation issue. Currently, `Math.floor((code % (max - min)) + min)` produces values between `min` and `max-1`, excluding `max` itself. If the intention is to include the maximum value in the possible outcomes, the calculation should be adjusted to `Math.floor((code % (max - min + 1)) + min)`. This would ensure the full range from `min` to `max` inclusive is covered in the randomization.
```suggestion
   const max = 5;
   const min = 1;
   const code = id.charCodeAt(0);
   const value = Math.floor((code % (max - min + 1)) + min);
```
  

*Spotted by [Diamond](https://app.graphite.dev/diamond/?org=toeverything&ref=ai-review-comment)*<i class='graphite__hidden'><br /><br />Is this helpful? React 👍 or 👎 to let us know.</i>
created_at: 2025-05-23T05:10:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-16T09:10:41Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

```markdown
## Walkthrough

This update refactors the mobile document listing UI by removing legacy list, menu, and masonry components, and replacing them with a new, context-driven document explorer for collections, tags, and the all-docs view. It also introduces parameterization for card heights and masonry widths, and enhances the `Wrapper` component with flexbox properties.

## Changes

| File(s)                                                                                 | Change Summary                                                                                                                                           |
|-----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `packages/frontend/component/src/ui/layout/wrapper.tsx`                                 | Extended `WrapperProps` to include `flexGrow` and `flexShrink` props; updated styled component and prop forwarding logic accordingly.                     |
| `packages/frontend/core/src/components/explorer/docs-view/docs-list.tsx`                | Parameterized card height and masonry item width; updated `DocsExplorer` props and internal logic for dynamic layout configuration.                       |
| `packages/frontend/core/src/mobile/pages/workspace/all.tsx`                             | Replaced `AllDocList` with new `AllDocs` component using document explorer context and live collection updates.                                           |
| `packages/frontend/core/src/mobile/views/all-docs/collection/detail.tsx`                | Replaced `AllDocList` with new `CollectionDocs` component using explorer context and live collection data; updated imports and hooks.                     |
| `packages/frontend/core/src/mobile/views/all-docs/tag/detail.tsx`                       | Replaced `AllDocList` with new `TagDocs` component using explorer context and live tag-filtered document updates.                                         |
| `packages/frontend/core/src/mobile/views/all-docs/doc/index.ts`                         | Deleted re-export file for `list`, `masonry`, and `menu` modules.                                                                                        |
| `packages/frontend/core/src/mobile/views/all-docs/doc/list.css.ts`,<br>`.../masonry.css.ts`,<br>`.../menu.css.ts` | Deleted CSS modules for document list, masonry, and menu styles.                                                                                          |
| `packages/frontend/core/src/mobile/views/all-docs/doc/list.tsx`,<br>`.../masonry.tsx`,<br>`.../menu.tsx`         | Deleted React components for document list, masonry layout, and menu UI.                                                                                  |
| `packages/frontend/core/src/mobile/views/all-docs/index.ts`                            | Removed export for deleted `./doc/` module.                                                                                                               |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant AllDocs/CollectionDocs/TagDocs
    participant CollectionRulesService/Collection
    participant ExplorerContext
    participant DocsExplorer

    User->>AllDocs/CollectionDocs/TagDocs: View document list
    AllDocs/CollectionDocs/TagDocs->>CollectionRulesService/Collection: Subscribe to live document updates
    CollectionRulesService/Collection-->>AllDocs/CollectionDocs/TagDocs: Emit filtered document groups
    AllDocs/CollectionDocs/TagDocs->>ExplorerContext: Update groups observable
    AllDocs/CollectionDocs/TagDocs->>DocsExplorer: Render with context and layout parameters
    DocsExplorer-->>User: Display masonry/grid of documents
```

## Assessment against linked issues

| Objective (Issue)                           | Addressed | Explanation                                                                 |
|---------------------------------------------|-----------|-----------------------------------------------------------------------------|
| Adjust sorting of all-docs tab to avoid onboarding doc always being first (AF-2514) | ✅        |                                                                             |

## Suggested labels

`mod:env`

## Poem

> A bunny hopped through docs anew,  
> Old lists and menus bid adieu!  
> With context live and layouts spry,  
> Cards now flex and heights can fly.  
> Tags and collections, all in flow—  
> Explorer magic, on we go!  
> 🐇✨
```

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKNwSPbABsvkCiQBHbGlcSHFcLzpIACIAMxJqAApmQXhIgEoucgB3SFp8BmQvJFDY/D4UtUjoyGy0ZAcBZnUaejkw2A9sREpIAGFqRAApbHgGD0x6VHglDHFY+Cj66zsMRwFegEYAJgBmbYBOFCxcTr8SbnxEdXLZDRgz23R4ZmQCY9wKRWxx9EgcvIFRzsSDFRClBK4bD+ezcUTwBYMNA+eRlPinDyVNIebheaho5j3dx5aQMCjwbjifBYfCxDoeJ7wDAMLzYJTINDneL+Zked4sq5Moj0lCIBweACCADEwNsAKybAAsABpjrQxtQhR1qB1UE80LRaP4xdJ+HxjfgvFI3rAddxnOIGN5nKLxfcAHL4dCG9TwanIkm4bReZDlSAkAAeuMwmupHIE+DwIpeuJIbDmsZpfAYtowpA5MN4+AkMyiTJF7LJFKpGA05ksfRY6dwDUczGcrgMxKYrHYoZOCGQT14L2caXkTAw13B/DpaDwLGoUTYYrQ+cgpHIVFakHaGMgAHEqNwEDQwvhLarsggc6KPiQiNuotl1LARf5LtcCC5z5B2xg1xxbxfH8IIQg5DB6DYCh8yJM5ul6AYWxGMYPFtZB/G5Mhxig7xxFTfgMT4bhgPOMDwWQRJbGQMwdkVAA2AAOVVaO2RU5Xoli6LlOUuLYuUVUgVjFUVTY+JEgAGcTdgOaTmKEujdl2aTtmksSFLY3YpI0xVDj4/ZZI0/ZtNYw55MmDTtgAdkY9JalfPJ4H8MRQSZABrN4vQxJzD2PU8cUAxB7glFI83pHpVSeViDIUChjUuSDPJFJ04pBJ5sEg3p1UQJ0xT9Ws3DOHtmw5EMvSZFk2Q8JlwQoH4a37SBYHwXJ3gQkUjzQE91A8ABZShSEgABFYJgkgNE/wGrVTj1Gx7gAZThBgEQ1FFVXUcMozxGrtVCA11TC/c8Q2Xxomg0gahfHxvXofcnnefcBDQBg3NnEVzo8MDgivBBIhu6azmOkhTua3AFgjS60l8Edyl9AAvPlHjsCboU3Up4AjaRVUQNyKW4AHPtGkh7gASVCVAMHwHdTh1TlOu6s9nqYDLyYwwJRn8W6vXambkA+yAvpIK8HM5a4iAwMBsG4VyMFeosSyUWg6wMCUfRrANKZoPn2Hy3b0CzIhMHgOHM29ZosHQ8MAIESJbrOen/MgfqYI8EaSDG8t9w/QVv1kVULMnT54AEBcKALCZaBLHp6E139+fVZyIlRL5mEPdQAAlsAEX80AkfAZnOcEHS1QOmWwRNkCZGhH1NxI+hJuyi3GE1Guov8Y1INp5H3fnBeVqUmWRLw/eSps+0YPFyVibvbVCXn7CDF63pI67QOCCi73/QCu98rr/OF04ZY839ImcLAUhhFREzns4i5erUm+kCD6AdnqAHIGkoEtm7gygJhhSmjlE5NUcJgBQvY5ihj4NlXK1w4znDxNTLyRVqQ0DmGac4JYSC5FpCKRkLZgaxGVvoYw4AoBkHoLg+cBBiBkGUDuYq7AuC8H4MIUQ4hrS7gnIoZQqh1BaB0CQkwUA4DTAgjgGhm56FREYXMLgVBcgOCcD+doTAlBUD4ZobQugwCGFIaYAw9oXqBQAPSxC+HMChJiewJXYCYxAFAGAmNGCYvEshr4mOyMeOEFANAtgjBwAw0RgkGAsJACUJNaFbiXPQJR7Yfy4JzJgfMhUPAAAMADq3jKBWC+NwRAaSwiyDhLUZYkY0GK1/BVVkSgRRpNiJECMR4WqFIsvUxp81YDklloUvo815o8DyZQcQ0h7iNlSogBKB0iDD1VPuTJ2SKCFPBLIW24DbHoLqMgKWtAYk5wYOMSk4U+TZC9ACIs+SuH/UOp0VOD0zjsk+PVaEUQLnP2OJVaZ9JU6ezOP4KEFByCxNwKsjwggRBiGCmrfKQ8R7zMQM1bwtApTlDqBQWguT8DcEKbEDKYhdYvhmgOdJWSuo+MKTY6kIItl/kUCtKI7xIyfPSQ0yMzTsitMgpAdpkZOndLcjilOu4SBajRGiyp9yuiZQoMPLUAARAA8r1cMkRmzKzCRKLwNBtz5SSvuJQLJnCZlDHSSMlwKA7jDCRG2YwrbhEWIgIwUBVaVJ5U0r42QAD8XA+nzUxT4kZiAADab9WXupam/AAupy+gbq+XuW9f0fp/rhkOpDWG+Nsso2FMldy0l3AfH+oKUUuExDIAAFVuC7OpmcBZZLKDLJBWsyl5B0HvCZhcPaXKyXD25WG9lMa+0dK6e5Qpby6zBOiE6gxRi3KmPMagqxTB/D2McdYlgGyWwmLNV4colATH5EKGALB2QD0FEQGAMEmh/GBMnaEywESonSNiW2Dsb0kl5mkKk8aeKazcqRF4BgAx0XpxFUQWAuAABCsgSa0EKTSlI6oFgMq9B2o5uBTn8EpDC6GRq2DasQKqNJT0eiDrSTlZEJA0mqiULEecWrppenlBJfW9BNgsfiuwyQwMR4fjxMtG5OJ/Algrr2206K1EyLjEGSBv9zgAqwBIZEY1Ka5AQjaaQAUqD4coByZAxH6geAANSQCU6yDwAAqewAGqPHHBAkShdJwZRDM+vOTaS5XnoAKJbT3Us9ZVLNllIjBU58DlTj+A8ACLF6toZ5MQFwNJ7ZEDUhcGTNMGSZinF6kyaj3LOjwHA1BwzeW2kFaK/NGzaTf49EGVi8OkAEJc0YN0AgzQEbvSZC8RwKAaCpxfLQQ+vzMT1FS/INx19WMihIxMLlFG/qudNBNJE6KmpgYg4wZETpEH5TkwB3w7w0kAaA84WgoHCsQeg7BwpNVSx1OS2N9LrxClsBSP8FqPB6g9H0+Vy7JWptpN+7gSrlGbtThoAaN63RRUYxc8p0ZDwPBKDhJlZk8hnBUHkLg+er2vQ0p2Xs941Sqr5fW8V0jAOgcg8iNVxH3LeqjYwC4ClG7Au4A/tynqzBMuDdgDljAY68mlOQB+zuv4eZnABElxnaW+s8+y7lur0sER1YVnQGjJA6N4UY5AbY2wWNU06BQF8PRlb1nCVq+hurfz6tEJPY1b0zXlEtcRLOxQGB2t9F+gwUAjtbeA2dsnV3aCJBmFwWqQp0iFMAEmE/7/enfO0V4PofaDh6DnmVUM3E1rGYBsCg2MbPZ/WJQKP9lD4xZw597TJACPcpm2R+btn9pRESLR+j4hDpMblCxiy7H0g++5Z5woPncR+ZZ8wTdSvtlVsJ+VZkNSSCBMgLoblD2mcwbl1lvnTIi+54bQYZfvugeQcM7vvPaSD8r8B2T6nJAz/77vT7mdz0535jMRYip66V0OKcViSIJj7Q39TkKAPIjESATEh4/FEAAkgkQkNVIkpEnwX1lEsc6RRdvduxcxBo+NnpTR9xeASARNuhe0mtuVNUvAh8AAZEocfSfQlN8TkKXcgofYtGwBIFyXFZkWLALVtG+HUFMVVceTkWgWQACZoD3Q9YEdBHdPzRra4MKYQugDUHcGQ/wbMRdELOTAEFtEELrcQZEY2U0YQoEZsTaUfNQhQSxELMvN8CveBYnL5IIMYV6OjPOckGgGjJAaMeQC5VNU0RIMkSEOgCUXAVUAnVoEIuZNcRAdIVUNfH8E9f2LlbKFQSIZAI0NcJqSYNI0mUIdTOpRsHwLjakGwbwaQeaL+VCHNL0RoHKckDYE+bjQEJ0Uw6tTkBYS3TmX8JlBfMIKgBFJYLlNMSkWQMAIQRMQFAMLnQjM0dRKIVRfwU2atJHUkChIUOTWoqsbDakRrGfLWEUVQ3oQOcpDnR8RMS5QQHoCgJTG2DwegkUeIXAHMKIM4qWIKOnHQ9BJQbVc2U0FXfVEwkEV4y5ZwDwYYkFK5Z4++MKFXIeDcL4N4rIqQd7XrNMd5ScdUWLXtHkdRcRcE+QIuM8ctEmSARINJLzCfEFFg+DEWPNRZZZMAigOyFXFLe4BVIiE3IWXrVXUsDTMw3dCw446w9tQfbzXzNQ2gtnSwhYIgF5ege4+I8bNAdxJMe0avAjdzRsCfNnQpJ3C1d7XIHE3TXUZAJgnwakng3QqcO7fAwCGwg4iMEoLUToA0XoCyDENgBQAUuCVAHA5uEUa4CfSIPgQk21K9e0zkYoKQSWPYoUVUIU3AMAI0bjLASQ0ww43xIwc3TVbVB3XNA1e3BqR3KMZ3KIK1N3W1HWQNJ1cJQ0JYf4bBSANg56UoX9SvT40INJZg89MHblWdedD/JdPdVdX/VIf/QA6QTxcoUA3AiAnwKAiMarAfXqOlZDO2MEksi1KINJLUzdXstJfst/BdSxSCL/cAn/ExP/cAicxAKckAyZWcyA/xao84aVblYAbswoSAExPQMHezSHXBNJD880goagmcH8pcx/CAMAIwQ8yc48z/Zdc8tdK8kxE9W8oeJM89ddIo/FakA9GvYMBc29OAh9BAuhJA+wV9BJNArAjAh5BEOkP0vAs4Agog/TT8sC3ASU3g44Aoy0SIPCjAOVQitIQpE9CMhs3IHcgS4o4Snsy0uYe4GS3CmsC0wIpcDkZoqQ0IDMywmgawycGUuU+0pwpeVw2GM8MgVI9XWEeERERySZNxJXPwyiI7RYiI7iojcI4Iry7lIMIgApWI9uFLdfUzRYbIJImOL0DI4UXMWgSIXIqigQOo4OPAr0KMroPYpbIVfcJgVS3WEsTkI7WSoSjQOoJ42ARIKPMI2MwTfk2QhM04hEy5e4o6TS0IfKwSv9aYpK/ItJBCagqQOVagNAF8z4F/ZKaEHkUIYE95b4ygX4yuOkPK0qnq5AfEyEzoaE4UWE66OapEqLcqPrKBFAFao3f+I6+E8494kmU1SkkeDaI0sOfWblCkkY9S1nXi8YSxLo+49oxpYOP6LxetXxSAdko3Tk9aUIeWXkh08wo4jQueVDUU4fcUhtRSzq6kYyv6hyRU0EZUybNUtAHTF6skxUp7eXbfAXIjY/ErWmm/KrdIOTFMZ3BrHy5rBwxGKLRshMqbPSjsmY5slyZqfAY+AanoLzWIeIMQMjBCeaIMGgUrLleZfNclTGuTYsZQa6XdIgW1BFBEFscaIVUWAQpHQE9BcMkUxY/FKQeMpGpM8kKQVMi23S9Gvge4zK3Y5Y95TawktCbI9Y7MzovM5BFYw1HVeBXBPUl3TwG1D3Ksh1Gsl1esgEYWrG7U3ilS7q/KC0+4sWACAFRfZfS/Eq8HfoNa3O89SAAAXlJIAG8vSc6diABfLgRurquSn1Su1uuyGuvQSAIYeaAADQ0C80ELmEKUgEv3LAPJfwHMXVPKQpHMvLHPAPQrnK8CwsKBwubowAIqDDSAXIv2XNXMWHXO5UKL3pEsPq8B4qtL7PnqPMHKXuHIvNQo3swsPVvM7qEoPqIufN/GYrkLtMAs4poPtOzrkotPLHUDZjfOvhIk0BLuX3mkK0LpeXOHbBqi4GnuXzSRjssJnCvugdEt8DrsSA7p7qwDbsgCoYKupG7oYZob7oHqHtHvHrTHYAvyguf2MWfsXtoDPJXo/oiowp8G3tvMPRMSZCUAjCgJIqnXgKfUoriTfUSTosdS7DOA6PSTgtvIQqHO/xQrXrQrEc3skbPScVkcjCgPg2WCUEiFaB9OQF0cGUIL9GIPkH8DABjqWGuhjtcdyq6RIExG+DSMSw0FcRoKIyicVOVtjTibIGwFp2g3OF8c3I72FAxFq0QzKJmI2iuKkHoGWE5B+omt8DXEfAfFNkuCrl/EDNxARHkFZotQ2pDGwQur4tWqUBmzk38BSDMzennjceBheEHn2Pniqf8ENj/TcUoDiISAwABh1EnCUWNKul8EAV3U/T4AaJsqqW1P1JybCdoHyY6ARPAxNPsCFD+nYB/DqaUqzPvQt1zKLPzLtyNSLOjsybLNd3js92rIH2JDyb+jnv4fgpfqEeXvftMc/oke/qsZkcynkZbHgxvDfB8b8ZKYCcyaCZYG5SiavTy2q0vJl1kAScJcvOSfsbZkGaiB1n8GHgnRCSfxgsMSfshcEeEdhaqHXvMa/uwukavQ0EKCChbEUZecfUQL2TUZosYE0e/UZY8F9QXlWS1FBY8AmgBJaJBHDIkuG1pT5eNsEeFywa1qVgeFQE1ZJAWCBTVbSLkK1CU2WZ8DQAyYmpclVYmh6CkCoF8GJMxoSz+EDm0C3HGjDGBLCHUEdfuLDQJpVK7XoHtB9DzAAG5+A/XyR2QI3sxfp6B1QJB24YIdoLJJl7KwyVBgYI30FrgEZ7aBSoqZY0IycM2wExgdiVlgaHJ8qwwLJOQvhFbdYJqpwCRdxDNKEsBhDRqwB/b0BcAg4Q4aA239Kw3egJoDqMdkkywp3TNU0AMvTHAsB86jEhQ23u1xpAbbjwwHrYRcCVUuHNkHIEYvg1sLtQgl3L3IwgaPAzjshTgNim0/j58qo2hDcgF2EHXw7CzrcLIZlBAAwu2wUs24agwXYsmGz7N6BgZH2jaLI2j53F28AEdiQBniwAwxnzZNL0BroTmoPbW9DYOVaCs+AvsCh4A9kJs8AG3ygm2R2vxdYNhbQRMw4zcpWQ73mw6SQI6HcfnzVY7rV3dAWk6B8vM5gnIPAyOhm5PSz6BVWkOiGZMJXuVgToAY2qMiNTPzOSZJwSW5rKWCGHrFpcCABNQVAl8F1/Llk86Ft+kxvlsx7BcRrexF4VkoUVsUOxllqdNl2CzlgxqFnl/z7EQL7IYLyxsL8EYi2ApRsilR2V6i1AhV7drR4kRxmvKINxs25ses9OzGsAGbegQNiabKbwrUSMkoN6NM8ee44E9rrlDo7VEuLqFQNIL3W68mEDiVTDbBrAQWxLIfZpKWMjcB8EJcgwDzAoJb7FV8uYkppurqa4a9qN3Bbr2TSAO6kUKNkmOVM16IIeZUGoDaVr+Zgt12yuXd/Grj0IBOdhZl8GjkpALkjaEGgtBlB5d7vispgSw7n91dpkXof688IgGZPkckFH3oBFFqAma6qWAmk6KbdtycOTL7wmpMDss10g94Z6isd71UOwgCFEewZqF8Q6aI8d6OfgLABCaBLw5y3wi1B1ZWLskChgLi2lv4BAcDSgS9Qg6tinmmUILeMXQbygfrgtvn5UrUM7o2xrrnrDbgt5LgaH5h/2MINcJtzkCahFS9tcPqzn0W4+anmvckOX2oach834fDA0UamjV2+wSo8YJtr2gj2qBIV4XNvo+oN8HXi7uVd5AK2dpaFaD3CcuPib5K1KjYZrX+v9dApKWbw+3d1Xro8M07/3m7oKb9CnmxYji+u1gMMvukWP73gj9oEv9r66HXjkA5coL5XNXP3WV7PPA27gOZfot8Qk7oPjzI4f3TBAbge35cNMfOE2P9d4CvdrTT0QLbZ0BqUmOkQBbv9uSq8HrVtIAjaG3bv+EprAP2xWjwCn+4hngMAK/S8pNkwHiKbk56s/8aC/m/tpRKhQ8QqY2BNtfGxgs9po7PFXNZVuJKxv0bjZEClhQBHMdwFkUgkpnJAVxGs4gYoIGimyBtBaVFW8MsC6qw9r2gcL4CGHWhrNVQIfUak1AvAeQm2+nIDu8WJC61bUXNegDbQ4RZUfav4WPlehkATt9eDAoMFNh573gqAQlGYvcRZAJAQyFwL5rrESTUhxggKGQDXmyChMsAfXDPP/06Jxkpsz1IOlmQ1QSdrcHzGTt81NS/NKE/zJTonW9xQA1O4gGEG4084L0fOSXUcgF3hYhchWBQaJll0AbldWgqoLTjjzKA+BseYUbFsp2kBL4r8i3FqoUjq4dlL8vuVbtxSbLsEM6k+CyHAynwyC6MvwEXhQVAolAi0PDVltBTi4QsEu3LGFsl3/yBCMuIQxUhF3FYJYcuUrcitEh3Bysiu+fb9BEMq7YgGO9rNgaCm2TyFsmtaAAAIusoY7rcpLINwDWIxQhSYoAICoA/gJonIUnom0xogCry4VbBElQLKglbomGHPGrw9xrMjOrjMMM1HJBwxUEAYFNl8jJI/ChQw9WupAE2D0Q0kdkAOJaCPYbguopJEqqyGYAYADwMIuupsCshgikqiQjDDUSA6sIIUxnOEUe2LRtJ8qR7WnMSAJEIji0BnHtOfU+ypthQ9xP1o6G+H7QYcmMEpqEBBE8BYcIYKbO8ONhfDcM9Ip1vVTnr0jh6LOcHJgFCIB8b4SOTXt3C9BhpLeBaYoPWUNjSx2Yr/KThSMRFdRJRRcJSnTl1GNpQUsoisAqN/DxskeTI1aIewREQchKZvDUW9HYzcjMYvIjYBhj0GolXgZvAbOXjpAvt8ATbMNMQA9Tmj3g6kOAcUDChho4eJIh0XWy1D0FDWobBHmDXcC1YkOIuSulIF7RUDZKF9b7lNk95agqEf4bXGAETHnxyW8JGYGJ0sFvNrBUnG4ZHSnDFl5OfzOOs4PU5AsoAbBelthwcGGdpRj9cUYUkSA5488dkBdKnG8ECNfBrQ/wSlw6Ghcuh5LHoXYwHyDiLWZhXTqOPQS6ikRO3KccXiZImt5x+jd/C0L84rj2hArBFsEN/ybixW24gcSv2Kb7itywKM0bqOLRzjH6TQm8UuLvGr0Ahj4oITvWkbdC3xaLHcZ+IZYjiDOJoy8UBK87NDQJxje8fyyC4WN1xL40Ki4C3HwTeG7La8YY1frYTwJq4yCZ0MIljZsud6ZRjK2GGFd30irbRisScaTC/otGBHvtzq6cEhKAYCnmIW3IM4iJsgC0or2v5dFjh5LWdjiMfAFxy+urdBCtloAZ80M6AqdnFGVJdd/erfRgeCnYRuVpiYIwnjSG2KM9dwF4U+FgAaSZEySWPbINAGiLoiPiX1EEKQQlokB+oKQcalQFHblA7kZwJkEg19EfcRSBIHUA4HUAHNtWtaSSWNnvroJAJYCcpJQFsmBs9hBwu4JAC8zPQ3wXOM1u2DB7NYwEpklyOmOWYKEsAaSGYKVnWR19Sc77QpHrx2KchOgXgHxD+i4K6w/cgGGwC1EQDB5KWxVHMGkCNBkBdSE9UIGYIUKo0A8aU0IHG1IgBiGCXKIHA63WLGiUp6+VaWayMqFYTKcbWHFBC6zMAespUzaaqH5GfCMwlTLlLaIPYaiZi/wwwRZBjEuYnIUIRDmSAEqiorUlAWKbyE1p+s4SJYjomsn3C5xgwBzTaXyLJx7ZvJQWZACRCZY+FjQ7ATMMiCiE8ZyxWAN5FNlxzGxTYE0TXIiEWDoJFpRARsQ+isHwIbBMHKOvYK7GOCexlZPsSpzcEjjMhaMhaYhNTz05yWedByAXWoCYM8G3KShlFK4BD5+oQYINJGkgEtR3JgVRNAmEtBLNIALdVhoPRHpj15p+5CiYl2XE0SHxeEwVtBI3FSTj6/Q2Lhy2AmUTfO1E0RtbKfG2zf8yTEiX0OYl5dWJUQEYRxJK5Kt1OMIVVja34l1ThQOYyPscLXpACQQ6YbAGUPvY0otOdAFxrSjOZ/RMBRsN1h602FJp5oYAJkGACHoNMcRWAzxsgCIGCyPunyHHl8Cpjw8tw90hzAszVCSAZg2AKYidWhppgqK0tWHDMULalgw4TbAXhCSXYEApwgHOYR8gXz0ASxM8h1CQIYKuNGkjlbwqqHja/cnR0I/JKqE+lEBO577S3sUHFhqoYAxSfADXBPAEl2BtQP+OgFVG0jocMJNAWf09IEA3IZAV4XwGGLoRDCt/XgQ+GdB7NFA3cD/hXQFKmZ4cyAXQTCBEJiFVovaHoHkQWGlzEF5IGyvi1ThW9bkHgRALIHsypxtW5SWgXGCbYJh0UumOTBPPUT0ceBfwfJO7HyC+N5pCcpgR8MFHNs5MM8+QHPM7Y4i2FosHDmIDDDtpaAQgVrFhmejqARFYgrqXkEYHUAiOZ4WRsoS1A9A96C8JcGyUhk+A5kZwG1rDUqSiwnkYgOUmYq4ZgA0AaKUhU2j0U148eRwo1lMJb7JMK0JMBma8ytzMzWxnzdsSam/EKcKyCdHma4MKkRyVW/SXOWUXQk+DEKYEj2Wl3wnPjqWawP2RLwmG0ACZgzTvr4ESEGdnh0ovoYfm5StzuK2Q0nAaAvzVLGpfWJpVfi5zzRsAo8xcvUpaVphOl3S6AOUjaW+4OlXS8GDZ2pAjLuUzC/fM0vXnSBxevS4RZBjwDzy6hMXBoc7IwkgS0l7suFnRIIk5LsATE0iuEkGHPoqKKBUOZ+lK4PIcOO4NxjHNq4FD+pIk3wGJJJrblPyiAfqGsBzSzweSVirxZEDADmIaZCVeQKnPTm/Bls2NU6d0mFAvcDJWCrJmzEQQoZk56CAwcKHw6WiFlGfUgo7ySj/JXeyJDAdlV3n88hkgvIwlyirjZS8ZxQBGLdDgXdhBZLUncDioN5McL6KUGar2hbJNEo2QMLwATOlTtc1srpVjhezSQrk+WfylJvaX0XsIDm3Khnu8QqI50lpUbDmldxaoPwaVIKe4JBmBgfYe4yTK/sitkD1kegQlSPgQBR6xikV+Ko1Q6mxjJ9EQsKP4KIpOBeQH5qPEUFHGDhjcIS2OaIqjMzo+TOeUc8JktjDBdt1ey80Dq9Q7Z+qe5SIM8NCoZWbDdsXkuYNQMvYfYaUtUbQEVjFSnZ5EFCNXmFEDY4dgBnUubtNRBBzsKV1aPRQ/317SCc1Qq6kL/BhA0pAEO6MYBtEoBfA+A8VZ1QTNlRhQdi6dDnIaN2SrYhOucP0DKvoDErI+c7ZXrhwCU5kglHYlmaoLZkRLuxinbmfaliUj4DxAsqNbTOFmJYfliq02fF12VGNkKOE1LulyOWpyHZZExoTstdl+DLZuEzJTbNvI2NUW/ss5dKwooFdrlGjMOVxIiWGK+sIIJKac2SVvwom0jN+Gayzn0BAJ+4SDUYOJgW5fAhuXoIQ39rACBmYbRrMyDoqFL9KZcHXFi0yZoTsNu9OSm/FVBcaXS6iXjVNi40BVsNVrEXHRXOBnN/S+4LBW9E1ZswMmHMq5PPBI1uN2gPRNkFqC424b1UjM5scEorChLZO7Mg8eWQBYuCtGH4ocREsSyEMrMgEnTSELfgvqXZ5s9JQcs9lQSINKLbcf+u2WpL31IjTzWBq9m3kAq/9I+jen6EsS4NbEhDbRSQ3Eg2KdcsgqL3F5nC6ZWKo2p4rf6ZyLg/GeYujikrcoNZn1O9XwU6oeU6VUEDuEYWy0NVBSSNZVZ6ttTWqXKtK95ApKkmXDsgylMrQpXyLzISGQlUomkQqLXEqiDTLOFnz5AZUmiPlcJcfxL5FaRQb/G7qqE0398J+gxbDre3GLQhGeMxCyH30AELFIQusZYiSByhrE8wGxGbVsT/SLb4aApRGlYXZzIADqlxL+DZUjWT4oSx8f4hD3UmzUWqDWTahCMgi+h/QTPZ6niVvZzsyS71Kkj2Tsie0yee0ORa1hKgH9stG1R0uCCv6WKWKG5BGuoXe05xUaiAEfK9v8w194VspXGofBOFE08MFXUTsaLK1kMjpqmOSfpgG2FA/yEORzGlqqFi8aCe6pmYepCW2DrcOnH8RgnPXRLL1VmiuhVvODRgcIiWLnbfVc2Ab3N+yiCV5ssYRbviADaAoUl51w7Sta4GBuDgcxvRKhVBCXQPndC80OVzeEWWkgF3FpZ6Zs28YbtonG7EWpushsfXtIky0kjdAKlwA1l6zIK9QgxFrC3RoBR55AMAKhRIDbBwCZuo+uWwYAKMYtgcuLcHPYmIbbl36ZPbKLx6GtiMmuPdEVJzCFJHeZUs+pipZDOF9ewhRin/GkI8LrwZALDGQElU68ForW/bHClvhSLvwchTFaWHU7TwHiTkGcLH35Du5Xo6BQjUKjSSHpqxp2F8jvoKCXoSg5c1pTnORC15rwYwN8HWp4XTBfqLZMLIfA34o5aeIOwMMGF63Co0QYJYqWEBCCZ4kwsmmbezBBBmpVV6/QFbaUGgnp5Od4YNdezo07Q8UTG5WCTGJT/6ZwSIWrNECRBTtDQ/+6wsIse5L7bJX2VNDsRpSb60JfrZpiszlHjsl4vq9uXyQMqhBogB4GvBhwVoOg6ANQd4I0wLHbVcYNyHUI9EmrMHUACBxKuJoQRoBMYfJDEtDppAYGnoTBtZdSA5xsH3+myX6CNirhhtJVCB0NROGEOS6DN0uozbLpPWJDzNvYlXUYE9DkBou06dlsnowpp6SAGe0xlnvALNQ2AGgfPYXoDnnL8u8W+JKMM4nEgq9Kq6RXwENb3daOKgfgyEGOlMa0JaHUgBh3rU9dwsZwKvTdxmVH7NJ41ZwFkYBhXA7ieRvkKkcKOH6GAx+8EKfrTBkjBw3tJcOgGlpmSRQu6TNWGCp5ehmYbaYHTpXSIKiyyGBxI14H4MqBPspAD0F6Eo18AuBDAf2CaEF60KMEhY3wA0mLVvzEM9KBAeJwsN6p7l1hjsfLsiUWaYlWjJw8TEdkkIDAwiK2CLuoSEAg59AWRJwD8BOKrlERyEjwg0RqAtEgiXRE8bITgJmguAAAPozBEA0J4TBFToDQmi4+pR488bQBKgGAEkNAGgB4gHBtgAgOUHRjlACAJIDAeiHKC0j0QJIBwRUCQAkjkntgjERiGgHYgCBFQnIIRBCaVCbAFQtARiPrgYCxBNgfJuUCQF2AMnRTHJg4PRAED7B6IBwWgIqCsgCBGI7GRiBJF2A6JDAzxg4LEEVCxABA0tAQAwEYgym2TBwXYJsFiCqnFQapkgPqcxPZ6GAuwNAFZHojbB6ItAKyDqfBMQBxoFJtAAcEYhWRU9uJok2ad0hoB+IAgAU7sCRC7AFQwZ2IAcAYC0AOI8oP0xiddOqn5QNkRiKScNAUn8zCQNU/rgJMy05QBwWkwaDNNoBGI2ZiE8GfojpnJTZJ907EAkhWQrIDAVQKGdoASRaANZgQJsBIDin9gqZhgHsCsgJA/T/pqAMVHUCwmtJCJjxtgmRMUIdTQAA== -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=toeverything/AFFiNE&utm_content=12329):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: CatsJuice
created_at: 2025-05-16T09:10:50Z
body: * **#12468** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12468?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12456** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12456?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12455** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12455?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12454** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12454?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12441** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12441?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12440** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12440?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12439** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12439?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12438** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12438?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12433** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12433?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12432** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12432?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12431** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12431?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12430** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12430?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12429** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12429?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12329** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12329?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12329?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#12320** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12320?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12298** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12298?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12278** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12278?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `canary`
<hr />

## How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:
- <kbd>merge</kbd> - adds this PR to the back of the merge queue
- <kbd>hotfix</kbd> - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using [this link](https://app.graphite.dev/invite/github/toeverything?ref=merge-queue-instructions-in-stack-comment&prId=12329).

<h2></h2>
<sub>

An organization admin has enabled the [Graphite Merge Queue](https://graphite.dev/docs/graphite-merge-queue) in this repository.

</sub>

<sub>

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

</sub>

<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-05-16T09:16:46Z
body: ## [Codecov](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12329?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 55.29%. Comparing base [(`d0539fd`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/d0539fde229da6b64f95939dde91ebf42639e6f6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) to head [(`a96cd3e`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/a96cd3eb0a7f077cbb87d0d90b1e5e32f9c237ea?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).
> Report is 1 commits behind head on canary.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           canary   #12329      +/-   ##
==========================================
- Coverage   55.29%   55.29%   -0.01%     
==========================================
  Files        2625     2625              
  Lines      123031   123031              
  Branches    19566    19566              
==========================================
- Hits        68031    68028       -3     
- Misses      53298    53301       +3     
  Partials     1702     1702              
```

| [Flag](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12329/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | Coverage Δ | |
|---|---|---|
| [server-test](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12329/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `78.72% <ø> (ø)` | |
| [unittest](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12329/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `31.44% <ø> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12329?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: graphite-app[bot]
created_at: 2025-05-23T07:06:34Z
body: ### Merge activity

* **May 23, 7:06 AM UTC**: `CatsJuice` added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).
* **May 23, 7:21 AM UTC**: Merged by the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).

author_association: CONTRIBUTOR