Repository: toeverything/AFFiNE
Pull request #12188: perf(core): optimize rendering of all docs
Author: login: CatsJuice
Labels: name: app:core
Created: 2025-05-08T05:37:58Z | Merged: 2025-05-10T07:15:40Z

close AF-2605

[CleanShot 2025-05-08 at 13.56.38.mp4 <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/LakojjjzZNf6ogjOVwKE/4e36e838-7c7f-4f0a-89a8-fd582c2ef573.mp4" />](https://app.graphite.dev/media/video/LakojjjzZNf6ogjOVwKE/4e36e838-7c7f-4f0a-89a8-fd582c2ef573.mp4)



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

- **Refactor**
  - Centralized state management in the document explorer using a reactive context with observable streams for preferences, selection, grouping, and view options.
  - Updated components (display menus, quick actions, doc list items, group headers) to consume and update state directly via context observables, removing prop drilling.
  - Simplified component signatures by removing props related to preferences and state handlers.
  - Refactored header and page components to use reactive subscriptions instead of local state and callbacks.

- **Style**
  - Adjusted user avatar display: margin applied only when user names are shown; removed vertical transform for better alignment.

- **New Features**
  - User avatar elements now include a data attribute indicating if the user name is displayed.

- **Bug Fixes**
  - Improved handling of document tags to prevent errors when tags are not in the expected format.

- **Documentation**
  - Added missing group header property for updated date fields in workspace property types.

- **Chores**
  - Clarified and renamed internal types for quick actions to improve code clarity.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/packages/frontend/core/src/components/explorer/context.ts b/packages/frontend/core/src/components/explorer/context.ts
index 1555a64b3d5e5..c3ccede0d691d 100644
--- a/packages/frontend/core/src/components/explorer/context.ts
+++ b/packages/frontend/core/src/components/explorer/context.ts
@@ -1,31 +1,50 @@
-import { createContext, type Dispatch, type SetStateAction } from 'react';
+import { LiveData } from '@toeverything/infra';
+import { createContext } from 'react';
 
 import type { DocListItemView } from './docs-view/doc-list-item';
 import type { ExplorerPreference } from './types';
 
-export type DocExplorerContextType = ExplorerPreference & {
-  view: DocListItemView;
-  setView: Dispatch<SetStateAction<DocListItemView>>;
-  groups: Array<{ key: string; items: string[] }>;
-  collapsed: string[];
-  selectMode?: boolean;
-  selectedDocIds: string[];
-  prevCheckAnchorId?: string | null;
-  onToggleCollapse: (groupId: string) => void;
-  onToggleSelect: (docId: string) => void;
-  onSelect: Dispatch<SetStateAction<string[]>>;
-  setPrevCheckAnchorId: Dispatch<SetStateAction<string | null>>;
+export type DocExplorerContextType = {
+  view$: LiveData<DocListItemView>;
+  groups$: LiveData<Array<{ key: string; items: string[] }>>;
+  collapsedGroups$: LiveData<string[]>;
+  selectMode$?: LiveData<boolean>;
+  selectedDocIds$: LiveData<string[]>;
+  prevCheckAnchorId$?: LiveData<string | null>;
+} & {
+  [K in keyof ExplorerPreference as `${K}$`]: LiveData<ExplorerPreference[K]>;
 };
 
-export const DocExplorerContext = createContext<DocExplorerContextType>({
-  view: 'list',
-  setView: () => {},
-  groups: [],
-  collapsed: [],
-  selectedDocIds: [],
-  prevCheckAnchorId: null,
-  onToggleSelect: () => {},
-  onToggleCollapse: () => {},
-  onSelect: () => {},
-  setPrevCheckAnchorId: () => {},
-});
+export const DocExplorerContext = createContext<DocExplorerContextType>(
+  {} as any
+);
+
+export const createDocExplorerContext = () =>
+  ({
+    view$: new LiveData<DocListItemView>('list'),
+    groups$: new LiveData<Array<{ key: string; items: string[] }>>([]),
+    collapsedGroups$: new LiveData<string[]>([]),
+    selectMode$: new LiveData<boolean>(false),
+    selectedDocIds$: new LiveData<string[]>([]),
+    prevCheckAnchorId$: new LiveData<string | null>(null),
+    filters$: new LiveData<ExplorerPreference['filters']>([
+      {
+        type: 'system',
+        key: 'trash',
+        value: 'false',
+        method: 'is',
+      },
+    ]),
+    groupBy$: new LiveData<ExplorerPreference['groupBy']>(undefined),
+    orderBy$: new LiveData<ExplorerPreference['orderBy']>(undefined),
+    displayProperties$: new LiveData<ExplorerPreference['displayProperties']>(
+      []
+    ),
+    showDocIcon$: new LiveData<ExplorerPreference['showDocIcon']>(true),
+    showDocPreview$: new LiveData<ExplorerPreference['showDocPreview']>(true),
+    quickFavorite$: new LiveData<ExplorerPreference['quickFavorite']>(false),
+    quickSelect$: new LiveData<ExplorerPreference['quickSelect']>(false),
+    quickSplit$: new LiveData<ExplorerPreference['quickSplit']>(false),
+    quickTrash$: new LiveData<ExplorerPreference['quickTrash']>(false),
+    quickTab$: new LiveData<ExplorerPreference['quickTab']>(false),
+  }) satisfies DocExplorerContextType;
diff --git a/packages/frontend/core/src/components/explorer/display-menu/index.tsx b/packages/frontend/core/src/components/explorer/display-menu/index.tsx
index ab97f36d019ac..fc2b0f1d4e685 100644
--- a/packages/frontend/core/src/components/explorer/display-menu/index.tsx
+++ b/packages/frontend/core/src/components/explorer/display-menu/index.tsx
@@ -11,85 +11,63 @@ import type {
 } from '@affine/core/modules/collection-rules/types';
 import { useI18n } from '@affine/i18n';
 import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
+import { useLiveData } from '@toeverything/infra';
 import type React from 'react';
-import { useCallback } from 'react';
+import { useCallback, useContext } from 'react';
 
-import type { ExplorerPreference } from '../types';
+import { DocExplorerContext } from '../context';
 import { GroupByList, GroupByName } from './group';
 import { OrderByList, OrderByName } from './order';
 import { DisplayProperties } from './properties';
 import { QuickActionsConfig } from './quick-actions';
 import * as styles from './styles.css';
 
-const ExplorerDisplayMenu = ({
-  preference,
-  onChange,
-}: {
-  preference: ExplorerPreference;
-  onChange?: (preference: ExplorerPreference) => void;
-}) => {
+const ExplorerDisplayMenu = () => {
   const t = useI18n();
+  const explorerContextValue = useContext(DocExplorerContext);
+  const groupBy = useLiveData(explorerContextValue.groupBy$);
+  const orderBy = useLiveData(explorerContextValue.orderBy$);
 
   const handleGroupByChange = useCallback(
     (groupBy: GroupByParams) => {
-      onChange?.({
-        ...preference,
-        groupBy,
-      });
+      explorerContextValue.groupBy$?.next(groupBy);
     },
-    [onChange, preference]
+    [explorerContextValue.groupBy$]
   );
 
   const handleOrderByChange = useCallback(
     (orderBy: OrderByParams) => {
-      onChange?.({
-        ...preference,
-        orderBy,
-      });
+      explorerContextValue.orderBy$?.next(orderBy);
     },
-    [onChange, preference]
+    [explorerContextValue.orderBy$]
   );
 
   return (
     <div className={styles.displayMenuContainer}>
       <MenuSub
-        items={
-          <GroupByList
-            groupBy={preference.groupBy}
-            onChange={handleGroupByChange}
-          />
-        }
+        items={<GroupByList groupBy={groupBy} onChange={handleGroupByChange} />}
       >
         <div className={styles.subMenuSelectorContainer}>
           <span>{t['com.affine.explorer.display-menu.grouping']()}</span>
           <span className={styles.subMenuSelectorSelected}>
-            {preference.groupBy ? (
-              <GroupByName groupBy={preference.groupBy} />
-            ) : null}
+            {groupBy ? <GroupByName groupBy={groupBy} /> : null}
           </span>
         </div>
       </MenuSub>
       <MenuSub
-        items={
-          <OrderByList
-            orderBy={preference.orderBy}
-            onChange={handleOrderByChange}
-          />
-        }
+        items={<OrderByList orderBy={orderBy} onChange={handleOrderByChange} />}
       >
         <div className={styles.subMenuSelectorContainer}>
           <span>{t['com.affine.explorer.display-menu.ordering']()}</span>
           <span className={styles.subMenuSelectorSelected}>
-            {preference.orderBy ? (
-              <OrderByName orderBy={preference.orderBy} />
-            ) : null}
+            {orderBy ? <OrderByName orderBy={orderBy} /> : null}
           </span>
         </div>
       </MenuSub>
       <Divider size="thinner" />
-      <DisplayProperties preference={preference} onChange={onChange} />
+      <DisplayProperties />
       <Divider size="thinner" />
-      <QuickActionsConfig preference={preference} onChange={onChange} />
+      <QuickActionsConfig />
     </div>
   );
 };
@@ -97,24 +75,15 @@ const ExplorerDisplayMenu = ({
 export const ExplorerDisplayMenuButton = ({
   style,
   className,
-  preference,
   menuProps,
-  onChange,
 }: {
   style?: React.CSSProperties;
   className?: string;
-  preference: ExplorerPreference;
-  onChange?: (preference: ExplorerPreference) => void;
   menuProps?: Omit<MenuProps, 'items' | 'children'>;
 }) => {
   const t = useI18n();
   return (
-    <Menu
-      items={
-        <ExplorerDisplayMenu preference={preference} onChange={onChange} />
-      }
-      {...menuProps}
-    >
+    <Menu items={<ExplorerDisplayMenu />} {...menuProps}>
       <Button
         className={className}
         style={style}
diff --git a/packages/frontend/core/src/components/explorer/display-menu/properties.tsx b/packages/frontend/core/src/components/explorer/display-menu/properties.tsx
index b73e67a3c4a20..15c4a28f516bb 100644
--- a/packages/frontend/core/src/components/explorer/display-menu/properties.tsx
+++ b/packages/frontend/core/src/components/explorer/display-menu/properties.tsx
@@ -5,11 +5,11 @@ import {
 } from '@affine/core/modules/workspace-property';
 import { useI18n } from '@affine/i18n';
 import { useLiveData, useService } from '@toeverything/infra';
-import { useCallback, useMemo } from 'react';
+import { useCallback, useContext, useMemo } from 'react';
 
 import { WorkspacePropertyName } from '../../properties';
 import { WorkspacePropertyTypes } from '../../workspace-property-types';
-import type { ExplorerPreference } from '../types';
+import { DocExplorerContext } from '../context';
 import * as styles from './properties.css';
 
 export const filterDisplayProperties = <
@@ -29,20 +29,17 @@ export const filterDisplayProperties = <
     }));
 };
 
-export const DisplayProperties = ({
-  preference,
-  onChange,
-}: {
-  preference: ExplorerPreference;
-  onChange?: (preference: ExplorerPreference) => void;
-}) => {
+export const DisplayProperties = () => {
   const t = useI18n();
+  const explorerContextValue = useContext(DocExplorerContext);
   const workspacePropertyService = useService(WorkspacePropertyService);
   const propertyList = useLiveData(workspacePropertyService.properties$);
 
-  const displayProperties = preference.displayProperties;
-  const showIcon = preference.showDocIcon;
-  const showBody = preference.showDocPreview;
+  const displayProperties = useLiveData(
+    explorerContextValue.displayProperties$
+  );
+  const showIcon = useLiveData(explorerContextValue.showDocIcon$);
+  const showBody = useLiveData(explorerContextValue.showDocPreview$);
 
   const propertiesGroups = useMemo(
     () => [
@@ -60,12 +57,9 @@ export const DisplayProperties = ({
 
   const handleDisplayPropertiesChange = useCallback(
     (displayProperties: string[]) => {
-      onChange?.({
-        ...preference,
-        displayProperties,
-      });
+      explorerContextValue.displayProperties$?.next(displayProperties);
     },
-    [onChange, preference]
+    [explorerContextValue.displayProperties$]
   );
 
   const handlePropertyClick = useCallback(
@@ -80,18 +74,12 @@ export const DisplayProperties = ({
   );
 
   const toggleIcon = useCallback(() => {
-    onChange?.({
-      ...preference,
-      showDocIcon: !showIcon,
-    });
-  }, [onChange, preference, showIcon]);
+    explorerContextValue.showDocIcon$?.next(!showIcon);
+  }, [explorerContextValue.showDocIcon$, showIcon]);
 
   const toggleBody = useCallback(() => {
-    onChange?.({
-      ...preference,
-      showDocPreview: !showBody,
-    });
-  }, [onChange, preference, showBody]);
+    explorerContextValue.showDocPreview$?.next(!showBody);
+  }, [explorerContextValue.showDocPreview$, showBody]);
 
   return (
     <div className={styles.root}>
diff --git a/packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx b/packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx
index 428e9e4975d69..54d409e794a47 100644
--- a/packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx
+++ b/packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx
@@ -1,50 +1,46 @@
 import { Checkbox, MenuItem, MenuSub } from '@affine/component';
 import { useI18n } from '@affine/i18n';
-import { useCallback } from 'react';
-
-import { type QuickActionKey, quickActions } from '../quick-actions.constants';
-import type { ExplorerPreference } from '../types';
-
-export const QuickActionsConfig = ({
-  preference,
-  onChange,
-}: {
-  preference: ExplorerPreference;
-  onChange?: (preference: ExplorerPreference) => void;
-}) => {
-  const t = useI18n();
+import { useLiveData } from '@toeverything/infra';
+import { useCallback, useContext } from 'react';
 
-  const toggleAction = useCallback(
-    (key: QuickActionKey) => {
-      onChange?.({
-        ...preference,
-        [key]: !preference[key],
-      });
-    },
-    [preference, onChange]
-  );
+import { DocExplorerContext } from '../context';
+import { type QuickAction, quickActions } from '../quick-actions.constants';
+
+export const QuickActionsConfig = () => {
+  const t = useI18n();
 
   return (
     <MenuSub
       items={quickActions.map(action => {
         if (action.disabled) return null;
 
-        return (
-          <MenuItem
-            key={action.key}
-            onClick={e => {
-              // do not close sub menu
-              e.preventDefault();
-              toggleAction(action.key);
-            }}
-            prefixIcon={<Checkbox checked={!!preference[action.key]} />}
-          >
-            {t.t(action.name)}
-          </MenuItem>
-        );
+        return <QuickActionItem key={action.key} action={action} />;
       })}
     >
       {t['com.affine.all-docs.quick-actions']()}
     </MenuSub>
   );
 };
+
+const QuickActionItem = ({ action }: { action: QuickAction }) => {
+  const t = useI18n();
+  const explorerContextValue = useContext(DocExplorerContext);
+
+  const value = useLiveData(explorerContextValue[`${action.key}$`]);
+
+  const handleClick = useCallback(
+    (e: React.MouseEvent) => {
+      e.stopPropagation();
+      e.preventDefault();
+      const value = explorerContextValue[`${action.key}$`]?.value;
+      explorerContextValue[`${action.key}$`]?.next(!value);
+    },
+    [action.key, explorerContextValue]
+  );
+
+  return (
+    <MenuItem prefixIcon={<Checkbox checked={!!value} />} onClick={handleClick}>
+      {t.t(action.name)}
+    </MenuItem>
+  );
+};
diff --git a/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx b/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx
index 0efdf7956c30c..66df4c852f149 100644
--- a/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx
+++ b/packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx
@@ -66,52 +66,49 @@ class MixId {
   }
 }
 export const DocListItem = ({ ...props }: DocListItemProps) => {
-  const {
-    view,
-    groups,
-    selectMode,
-    selectedDocIds,
-    prevCheckAnchorId,
-    onSelect,
-    onToggleSelect,
-    setPrevCheckAnchorId,
-  } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const view = useLiveData(contextValue.view$);
+  const groups = useLiveData(contextValue.groups$);
+  const selectMode = useLiveData(contextValue.selectMode$);
+  const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
+  const prevCheckAnchorId = useLiveData(contextValue.prevCheckAnchorId$);
 
   const handleMultiSelect = useCallback(
     (prevCursor: string, currCursor: string) => {
       const flattenList = groups.flatMap(group =>
         group.items.map(docId => MixId.create(group.key, docId))
       );
+
+      const prev = contextValue.selectedDocIds$?.value ?? [];
       const prevIndex = flattenList.indexOf(prevCursor);
       const currIndex = flattenList.indexOf(currCursor);
 
-      onSelect(prev => {
-        const lowerIndex = Math.min(prevIndex, currIndex);
-        const upperIndex = Math.max(prevIndex, currIndex);
+      const lowerIndex = Math.min(prevIndex, currIndex);
+      const upperIndex = Math.max(prevIndex, currIndex);
 
-        const resSet = new Set(prev);
-        const handledSet = new Set<string>();
-        for (let i = lowerIndex; i <= upperIndex; i++) {
-          const mixId = flattenList[i];
-          const { groupId, docId } = MixId.parse(mixId);
-          if (groupId === null || docId === null) {
-            continue;
-          }
-          if (handledSet.has(docId) || mixId === prevCursor) {
-            continue;
-          }
-          if (resSet.has(docId)) {
-            resSet.delete(docId);
-          } else {
-            resSet.add(docId);
-          }
-          handledSet.add(docId);
+      const resSet = new Set(prev);
+      const handledSet = new Set<string>();
+      for (let i = lowerIndex; i <= upperIndex; i++) {
+        const mixId = flattenList[i];
+        const { groupId, docId } = MixId.parse(mixId);
+        if (groupId === null || docId === null) {
+          continue;
+        }
+        if (handledSet.has(docId) || mixId === prevCursor) {
+          continue;
+        }
+        if (resSet.has(docId)) {
+          resSet.delete(docId);
+        } else {
+          resSet.add(docId);
         }
-        return [...resSet];
-      });
-      setPrevCheckAnchorId(currCursor);
+        handledSet.add(docId);
+      }
+
+      contextValue.selectedDocIds$?.next(Array.from(resSet));
+      contextValue.prevCheckAnchorId$?.next(currCursor);
     },
-    [groups, onSelect, setPrevCheckAnchorId]
+    [contextValue, groups]
   );
 
   const handleClick = useCallback(
@@ -127,13 +124,13 @@ export const DocListItem = ({ ...props }: DocListItemProps) => {
           // do multi select
           handleMultiSelect(prevCheckAnchorId, currCursor);
         } else {
-          onToggleSelect(docId);
-          setPrevCheckAnchorId(currCursor);
+          contextValue.selectedDocIds$?.next([docId]);
+          contextValue.prevCheckAnchorId$?.next(currCursor);
         }
       } else {
         if (e.shiftKey) {
-          onToggleSelect(docId);
-          setPrevCheckAnchorId(currCursor);
+          contextValue.selectedDocIds$?.next([docId]);
+          contextValue.prevCheckAnchorId$?.next(currCursor);
           return;
         } else {
           // as link
@@ -141,14 +138,7 @@ export const DocListItem = ({ ...props }: DocListItemProps) => {
         }
       }
     },
-    [
-      handleMultiSelect,
-      onToggleSelect,
-      prevCheckAnchorId,
-      props,
-      selectMode,
-      setPrevCheckAnchorId,
-    ]
+    [contextValue, handleMultiSelect, prevCheckAnchorId, props, selectMode]
   );
 
   return (
@@ -196,7 +186,8 @@ const DragHandle = memo(function DragHandle({
   preview,
   ...props
 }: HTMLProps<HTMLDivElement> & { preview?: ReactNode }) {
-  const { selectMode } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const selectMode = useLiveData(contextValue.selectMode$);
 
   const { dragRef, CustomDragPreview } = useDraggable<AffineDNDData>(
     () => ({
@@ -238,12 +229,13 @@ const Select = memo(function Select({
   id,
   ...props
 }: HTMLProps<HTMLDivElement>) {
-  const { selectMode, selectedDocIds, onToggleSelect } =
-    useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const selectMode = useLiveData(contextValue.selectMode$);
+  const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
 
   const handleSelectChange = useCallback(() => {
-    id && onToggleSelect(id);
-  }, [id, onToggleSelect]);
+    id && contextValue.selectedDocIds$?.next([id]);
+  }, [id, contextValue]);
 
   if (!id) {
     return null;
@@ -263,7 +255,8 @@ const DocIcon = memo(function DocIcon({
   id,
   ...props
 }: HTMLProps<HTMLDivElement>) {
-  const { showDocIcon } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const showDocIcon = useLiveData(contextValue.showDocIcon$);
   if (!showDocIcon) {
     return null;
   }
@@ -289,7 +282,8 @@ const DocPreview = memo(function DocPreview({
   loading,
   ...props
 }: HTMLProps<HTMLDivElement> & { loading?: ReactNode }) {
-  const { showDocPreview } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const showDocPreview = useLiveData(contextValue.showDocPreview$);
 
   if (!id || !showDocPreview) return null;
 
@@ -356,7 +350,8 @@ const randomPreviewSkeleton = () => {
   }));
 };
 export const CardViewDoc = ({ docId }: DocListItemProps) => {
-  const { selectMode } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const selectMode = useLiveData(contextValue.selectMode$);
   const docsService = useService(DocsService);
   const doc = useLiveData(docsService.list.doc$(docId));
   const [previewSkeleton] = useState(randomPreviewSkeleton);
diff --git a/packages/frontend/core/src/components/explorer/docs-view/group-header.tsx b/packages/frontend/core/src/components/explorer/docs-view/group-header.tsx
index 5a7a7591a33ab..77962003ebf06 100644
--- a/packages/frontend/core/src/components/explorer/docs-view/group-header.tsx
+++ b/packages/frontend/core/src/components/explorer/docs-view/group-header.tsx
@@ -1,6 +1,7 @@
 import { Button, IconButton } from '@affine/component';
 import { useI18n } from '@affine/i18n';
 import { ToggleRightIcon } from '@blocksuite/icons/rc';
+import { useLiveData } from '@toeverything/infra';
 import clsx from 'clsx';
 import {
   type HTMLAttributes,
@@ -20,14 +21,12 @@ export const DocGroupHeader = ({
   groupId: string;
 }) => {
   const t = useI18n();
-  const {
-    selectMode,
-    collapsed,
-    groups,
-    selectedDocIds,
-    onToggleCollapse,
-    onSelect,
-  } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+
+  const groups = useLiveData(contextValue.groups$);
+  const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
+  const collapsedGroups = useLiveData(contextValue.collapsedGroups$);
+  const selectMode = useLiveData(contextValue.selectMode$);
 
   const group = groups.find(g => g.key === groupId);
   const isGroupAllSelected = group?.items.every(id =>
@@ -35,24 +34,30 @@ export const DocGroupHeader = ({
   );
 
   const handleToggleCollapse = useCallback(() => {
-    onToggleCollapse(groupId);
-  }, [groupId, onToggleCollapse]);
+    const prev = contextValue.collapsedGroups$.value;
+    contextValue.collapsedGroups$.next(
+      prev.includes(groupId)
+        ? prev.filter(id => id !== groupId)
+        : [...prev, groupId]
+    );
+  }, [groupId, contextValue]);
 
   const handleSelectAll = useCallback(() => {
+    const prev = contextValue.selectedDocIds$.value;
     if (isGroupAllSelected) {
-      onSelect(prev => prev.filter(id => !group?.items.includes(id)));
+      contextValue.selectedDocIds$.next(
+        prev.filter(id => !group?.items.includes(id))
+      );
     } else {
-      onSelect(prev => {
-        const newSelected = [...prev];
-        group?.items.forEach(id => {
-          if (!newSelected.includes(id)) {
-            newSelected.push(id);
-          }
-        });
-        return newSelected;
+      const newSelected = [...prev];
+      group?.items.forEach(id => {
+        if (!newSelected.includes(id)) {
+          newSelected.push(id);
+        }
       });
+      contextValue.selectedDocIds$.next(newSelected);
     }
-  }, [group?.items, isGroupAllSelected, onSelect]);
+  }, [contextValue, group?.items, isGroupAllSelected]);
 
   const selectedCount = group?.items.filter(id =>
     selectedDocIds.includes(id)
@@ -61,7 +66,7 @@ export const DocGroupHeader = ({
   return (
     <div
       className={styles.groupHeader}
-      data-collapsed={collapsed.includes(groupId)}
+      data-collapsed={collapsedGroups.includes(groupId)}
     >
       <div className={clsx(styles.content, className)} {...props} />
       {selectMode ? (
diff --git a/packages/frontend/core/src/components/explorer/docs-view/properties.tsx b/packages/frontend/core/src/components/explorer/docs-view/properties.tsx
index 7cbaac9859d74..6193b527a8072 100644
--- a/packages/frontend/core/src/components/explorer/docs-view/properties.tsx
+++ b/packages/frontend/core/src/components/explorer/docs-view/properties.tsx
@@ -28,7 +28,8 @@ const cardInlinePropertyOrder: WorkspacePropertyType[] = [
 ];
 
 const useProperties = (docId: string, view: 'list' | 'card') => {
-  const { displayProperties } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const displayProperties = useLiveData(contextValue.displayProperties$);
   const docsService = useService(DocsService);
   const workspacePropertyService = useService(WorkspacePropertyService);
 
diff --git a/packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx b/packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx
index 918c741a71b3f..c76ef78352ec1 100644
--- a/packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx
+++ b/packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx
@@ -24,7 +24,8 @@ export const QuickFavorite = memo(function QuickFavorite({
   doc,
   ...iconButtonProps
 }: QuickActionProps) {
-  const { quickFavorite } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const quickFavorite = useLiveData(contextValue.quickFavorite$);
 
   const favAdapter = useService(CompatibleFavoriteItemsAdapter);
   const favourite = useLiveData(favAdapter.isFavorite$(doc.id, 'doc'));
@@ -55,7 +56,8 @@ export const QuickTab = memo(function QuickTab({
   doc,
   ...iconButtonProps
 }: QuickActionProps) {
-  const { quickTab } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const quickTab = useLiveData(contextValue.quickTab$);
   const workbench = useService(WorkbenchService).workbench;
   const onOpenInNewTab = useCallback(
     (e: React.MouseEvent) => {
@@ -83,7 +85,8 @@ export const QuickSplit = memo(function QuickSplit({
   doc,
   ...iconButtonProps
 }: QuickActionProps) {
-  const { quickSplit } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const quickSplit = useLiveData(contextValue.quickSplit$);
   const workbench = useService(WorkbenchService).workbench;
 
   const onOpenInSplitView = useCallback(
@@ -115,7 +118,8 @@ export const QuickDelete = memo(function QuickDelete({
 }: QuickActionProps) {
   const t = useI18n();
   const { openConfirmModal } = useConfirmModal();
-  const { quickTrash } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const quickTrash = useLiveData(contextValue.quickTrash$);
 
   const onMoveToTrash = useCallback(
     (e: React.MouseEvent) => {
@@ -162,8 +166,9 @@ export const QuickSelect = memo(function QuickSelect({
   doc,
   ...iconButtonProps
 }: QuickActionProps) {
-  const { quickSelect, selectedDocIds, onToggleSelect } =
-    useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const quickSelect = useLiveData(contextValue.quickSelect$);
+  const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
 
   const selected = selectedDocIds.includes(doc.id);
 
@@ -171,9 +176,13 @@ export const QuickSelect = memo(function QuickSelect({
     (e: React.MouseEvent) => {
       e.stopPropagation();
       e.preventDefault();
-      onToggleSelect(doc.id);
+      contextValue.selectedDocIds$?.next(
+        selected
+          ? selectedDocIds.filter(id => id !== doc.id)
+          : [...selectedDocIds, doc.id]
+      );
     },
-    [doc.id, onToggleSelect]
+    [contextValue, doc.id, selected, selectedDocIds]
   );
 
   if (!quickSelect) {
diff --git a/packages/frontend/core/src/components/explorer/quick-actions.constants.tsx b/packages/frontend/core/src/components/explorer/quick-actions.constants.tsx
index 7c5beb07c2193..ed77016e82de6 100644
--- a/packages/frontend/core/src/components/explorer/quick-actions.constants.tsx
+++ b/packages/frontend/core/src/components/explorer/quick-actions.constants.tsx
@@ -10,7 +10,7 @@ import {
 } from './docs-view/quick-actions';
 import type { ExplorerPreference } from './types';
 
-export interface QuickAction {
+interface QuickActionItem {
   name: I18nString;
   Component: React.FC<QuickActionProps>;
   disabled?: boolean;
@@ -22,7 +22,7 @@ type ExtractPrefixKeys<Obj extends object, Prefix extends string> = {
 
 export type QuickActionKey = ExtractPrefixKeys<ExplorerPreference, 'quick'>;
 
-const QUICK_ACTION_MAP: Record<QuickActionKey, QuickAction> = {
+const QUICK_ACTION_MAP: Record<QuickActionKey, QuickActionItem> = {
   quickFavorite: {
     name: 'com.affine.all-docs.quick-action.favorite',
     Component: QuickFavorite,
@@ -50,3 +50,5 @@ export const quickActions = Object.entries(QUICK_ACTION_MAP).map(
     return { key: key as QuickActionKey, ...config };
   }
 );
+
+export type QuickAction = (typeof quickActions)[number];
diff --git a/packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx b/packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx
index a4d4c752679cb..8794796d6be6f 100644
--- a/packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx
+++ b/packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx
@@ -144,6 +144,7 @@ export const CreatedByDocListInlineProperty = ({
       type="CreatedBy"
       size={20}
       emptyFallback={null}
+      showName={false}
     />
   );
 };
@@ -171,7 +172,7 @@ export const ModifiedByGroupHeader = ({
   return (
     <PlainTextDocGroupHeader groupId={groupId} docCount={docCount}>
       <div className={styles.userLabelContainer}>
-        <PublicUserLabel id={userId} size={20} />
+        <PublicUserLabel id={userId} size={20} showName={false} />
       </div>
     </PlainTextDocGroupHeader>
   );
diff --git a/packages/frontend/core/src/components/workspace-property-types/index.ts b/packages/frontend/core/src/components/workspace-property-types/index.ts
index ce634c30bf32d..c94c9ec996a57 100644
--- a/packages/frontend/core/src/components/workspace-property-types/index.ts
+++ b/packages/frontend/core/src/components/workspace-property-types/index.ts
@@ -223,6 +223,7 @@ export const WorkspacePropertyTypes = {
     defaultFilter: { method: 'this-week' },
     showInDocList: 'inline',
     docListProperty: UpdatedDateDocListProperty,
+    groupHeader: CreatedGroupHeader,
   },
   createdAt: {
     icon: HistoryIcon,
diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx
index 4eb1d7ad94254..6ccde0f9d637e 100644
--- a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx
+++ b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx
@@ -1,10 +1,13 @@
 import { type MenuProps, RadioGroup, type RadioItem } from '@affine/component';
 import { DocExplorerContext } from '@affine/core/components/explorer/context';
 import { ExplorerDisplayMenuButton } from '@affine/core/components/explorer/display-menu';
-import { DocListViewIcon } from '@affine/core/components/explorer/docs-view/doc-list-item';
+import {
+  type DocListItemView,
+  DocListViewIcon,
+} from '@affine/core/components/explorer/docs-view/doc-list-item';
 import { ExplorerNavigation } from '@affine/core/components/explorer/header/navigation';
-import type { ExplorerPreference } from '@affine/core/components/explorer/types';
-import { type Dispatch, type SetStateAction, useContext } from 'react';
+import { useLiveData } from '@toeverything/infra';
+import { useCallback, useContext } from 'react';
 
 import * as styles from './all-page-header.css';
 
@@ -27,7 +30,17 @@ const views = [
 ] satisfies RadioItem[];
 
 const ViewToggle = () => {
-  const { view, setView } = useContext(DocExplorerContext);
+  const explorerContextValue = useContext(DocExplorerContext);
+
+  const view = useLiveData(explorerContextValue.view$);
+
+  const handleViewChange = useCallback(
+    (view: DocListItemView) => {
+      explorerContextValue.view$?.next(view);
+    },
+    [explorerContextValue.view$]
+  );
+
   return (
     <RadioGroup
       itemHeight={24}
@@ -35,7 +48,7 @@ const ViewToggle = () => {
       padding={0}
       items={views}
       value={view}
-      onChange={setView}
+      onChange={handleViewChange}
       className={styles.viewToggle}
       borderRadius={4}
       indicatorClassName={styles.viewToggleIndicator}
@@ -51,24 +64,14 @@ const menuProps: Partial<MenuProps> = {
     sideOffset: 8,
   },
 };
-export const AllDocsHeader = ({
-  explorerPreference,
-  setExplorerPreference,
-}: {
-  explorerPreference: ExplorerPreference;
-  setExplorerPreference: Dispatch<SetStateAction<ExplorerPreference>>;
-}) => {
+export const AllDocsHeader = () => {
   return (
     <div className={styles.header}>
       <ExplorerNavigation active="docs" />
 
       <div className={styles.actions}>
         <ViewToggle />
-        <ExplorerDisplayMenuButton
-          preference={explorerPreference}
-          onChange={setExplorerPreference}
-          menuProps={menuProps}
-        />
+        <ExplorerDisplayMenuButton menuProps={menuProps} />
       </div>
     </div>
   );
diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx
index c7240ec947b15..8dce932103cbf 100644
--- a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx
+++ b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx
@@ -1,13 +1,9 @@
 import { Masonry, type MasonryGroup, useConfirmModal } from '@affine/component';
 import {
+  createDocExplorerContext,
   DocExplorerContext,
-  type DocExplorerContextType,
 } from '@affine/core/components/explorer/context';
-import {
-  DocListItem,
-  type DocListItemView,
-} from '@affine/core/components/explorer/docs-view/doc-list-item';
-import type { ExplorerPreference } from '@affine/core/components/explorer/types';
+import { DocListItem } from '@affine/core/components/explorer/docs-view/doc-list-item';
 import { Filters } from '@affine/core/components/filter';
 import { ListFloatingToolbar } from '@affine/core/components/page-list/components/list-floating-toolbar';
 import { WorkspacePropertyTypes } from '@affine/core/components/workspace-property-types';
@@ -49,9 +45,10 @@ const GroupHeader = memo(function GroupHeader({
   collapsed?: boolean;
   itemCount: number;
 }) {
-  const { groupBy } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
   const propertyService = useService(WorkspacePropertyService);
   const allProperties = useLiveData(propertyService.sortedProperties$);
+  const groupBy = useLiveData(contextValue.groupBy$);
 
   const groupType = groupBy?.type;
   const groupKey = groupBy?.key;
@@ -103,30 +100,17 @@ const DocListItemComponent = memo(function DocListItemComponent({
 export const AllPage = () => {
   const t = useI18n();
   const docsService = useService(DocsService);
-  const [view, setView] = useState<DocListItemView>('masonry');
-  const [collapsedGroups, setCollapsedGroups] = useState<string[]>([]);
-  const [selectMode, setSelectMode] = useState(false);
-  const [selectedDocIds, setSelectedDocIds] = useState<string[]>([]);
-  const [prevCheckAnchorId, setPrevCheckAnchorId] = useState<string | null>(
-    null
-  );
 
-  const [explorerPreference, setExplorerPreference] =
-    useState<ExplorerPreference>({
-      filters: [
-        {
-          type: 'system',
-          key: 'trash',
-          value: 'false',
-          method: 'is',
-        },
-      ],
-      displayProperties: [],
-      showDocIcon: true,
-      showDocPreview: true,
-    });
+  const [explorerContextValue] = useState(createDocExplorerContext);
 
-  const [groups, setGroups] = useState<any>([]);
+  const view = useLiveData(explorerContextValue.view$);
+  const filters = useLiveData(explorerContextValue.filters$);
+  const groupBy = useLiveData(explorerContextValue.groupBy$);
+  const orderBy = useLiveData(explorerContextValue.orderBy$);
+  const groups = useLiveData(explorerContextValue.groups$);
+  const selectedDocIds = useLiveData(explorerContextValue.selectedDocIds$);
+  const collapsedGroups = useLiveData(explorerContextValue.collapsedGroups$);
+  const selectMode = useLiveData(explorerContextValue.selectMode$);
 
   const { openConfirmModal } = useConfirmModal();
 
@@ -159,14 +143,10 @@ export const AllPage = () => {
   const collectionRulesService = useService(CollectionRulesService);
   useEffect(() => {
     const subscription = collectionRulesService
-      .watch(
-        explorerPreference.filters ?? [],
-        explorerPreference.groupBy,
-        explorerPreference.orderBy
-      )
+      .watch(filters ?? [], groupBy, orderBy)
       .subscribe({
         next: result => {
-          setGroups(result.groups);
+          explorerContextValue.groups$.next(result.groups);
         },
         error: error => {
           console.error(error);
@@ -177,58 +157,38 @@ export const AllPage = () => {
     };
   }, [
     collectionRulesService,
-    explorerPreference.filters,
-    explorerPreference.groupBy,
-    explorerPreference.orderBy,
+    explorerContextValue.groups$,
+    filters,
+    groupBy,
+    orderBy,
   ]);
 
   useEffect(() => {
     const onKeyDown = (e: KeyboardEvent) => {
       if (e.key === 'Escape') {
-        setSelectMode(false);
-        setSelectedDocIds([]);
-        setPrevCheckAnchorId(null);
+        explorerContextValue.selectMode$.next(false);
+        explorerContextValue.selectedDocIds$.next([]);
+        explorerContextValue.prevCheckAnchorId$.next(null);
       }
     };
     document.addEventListener('keydown', onKeyDown);
     return () => {
       document.removeEventListener('keydown', onKeyDown);
     };
-  }, []);
+  }, [explorerContextValue]);
 
-  const handleFilterChange = useCallback((filters: FilterParams[]) => {
-    setExplorerPreference(prev => ({
-      ...prev,
-      filters,
-    }));
-  }, []);
-
-  const toggleGroupCollapse = useCallback((groupId: string) => {
-    setCollapsedGroups(prev => {
-      return prev.includes(groupId)
-        ? prev.filter(id => id !== groupId)
-        : [...prev, groupId];
-    });
-  }, []);
-  const toggleDocSelect = useCallback((docId: string) => {
-    setSelectMode(true);
-    setSelectedDocIds(prev => {
-      return prev.includes(docId)
-        ? prev.filter(id => id !== docId)
-        : [...prev, docId];
-    });
-  }, []);
-  const onSelect = useCallback(
-    (...args: Parameters<typeof setSelectedDocIds>) => {
-      setSelectMode(true);
-      setSelectedDocIds(...args);
+  const handleFilterChange = useCallback(
+    (filters: FilterParams[]) => {
+      explorerContextValue.filters$.next(filters);
     },
-    []
+    [explorerContextValue]
   );
+
   const handleCloseFloatingToolbar = useCallback(() => {
-    setSelectMode(false);
-    setSelectedDocIds([]);
-  }, []);
+    explorerContextValue.selectMode$.next(false);
+    explorerContextValue.selectedDocIds$.next([]);
+  }, [explorerContextValue]);
+
   const handleMultiDelete = useCallback(() => {
     if (selectedDocIds.length === 0) {
       return;
@@ -257,54 +217,18 @@ export const AllPage = () => {
     });
   }, [docsService.list, openConfirmModal, selectedDocIds, t]);
 
-  const explorerContextValue = useMemo(
-    () =>
-      ({
-        ...explorerPreference,
-        view,
-        setView,
-        groups,
-        collapsed: collapsedGroups,
-        selectedDocIds,
-        selectMode,
-        prevCheckAnchorId,
-        setPrevCheckAnchorId,
-        onToggleCollapse: toggleGroupCollapse,
-        onToggleSelect: toggleDocSelect,
-        onSelect,
-      }) satisfies DocExplorerContextType,
-    [
-      collapsedGroups,
-      explorerPreference,
-      groups,
-      onSelect,
-      prevCheckAnchorId,
-      selectMode,
-      selectedDocIds,
-      toggleDocSelect,
-      toggleGroupCollapse,
-      view,
-    ]
-  );
-
   return (
     <DocExplorerContext.Provider value={explorerContextValue}>
       <ViewTitle title={t['All pages']()} />
       <ViewIcon icon="allDocs" />
       <ViewHeader>
-        <AllDocsHeader
-          explorerPreference={explorerPreference}
-          setExplorerPreference={setExplorerPreference}
-        />
+        <AllDocsHeader />
       </ViewHeader>
       <ViewBody>
         <div className={styles.body}>
           <div className={styles.filterArea}>
             <MigrationAllDocsDataNotification />
-            <Filters
-              filters={explorerPreference.filters ?? []}
-              onChange={handleFilterChange}
-            />
+            <Filters filters={filters ?? []} onChange={handleFilterChange} />
           </div>
           <div className={styles.scrollArea}>
             <Masonry
diff --git a/packages/frontend/core/src/modules/cloud/views/public-user.css.ts b/packages/frontend/core/src/modules/cloud/views/public-user.css.ts
index aab215e6b70d9..5316bf9684407 100644
--- a/packages/frontend/core/src/modules/cloud/views/public-user.css.ts
+++ b/packages/frontend/core/src/modules/cloud/views/public-user.css.ts
@@ -22,6 +22,9 @@ export const publicUserLabelRemoved = style([
 ]);
 
 export const publicUserLabelAvatar = style({
-  marginRight: '0.5em',
-  transform: 'translateY(4px)',
+  selectors: {
+    '&[data-show-name="true"]': {
+      marginRight: '0.5em',
+    },
+  },
 });
diff --git a/packages/frontend/core/src/modules/cloud/views/public-user.tsx b/packages/frontend/core/src/modules/cloud/views/public-user.tsx
index bc676cb58944c..180ed00c0def3 100644
--- a/packages/frontend/core/src/modules/cloud/views/public-user.tsx
+++ b/packages/frontend/core/src/modules/cloud/views/public-user.tsx
@@ -56,6 +56,7 @@ export const PublicUserLabel = ({
         name={user?.name ?? ''}
         size={size}
         className={styles.publicUserLabelAvatar}
+        data-show-name={showName}
       />
       {showName && user?.name}
     </span>
diff --git a/packages/frontend/core/src/modules/doc/stores/docs.ts b/packages/frontend/core/src/modules/doc/stores/docs.ts
index 933a1c1215291..6ecf6a5326da1 100644
--- a/packages/frontend/core/src/modules/doc/stores/docs.ts
+++ b/packages/frontend/core/src/modules/doc/stores/docs.ts
@@ -108,7 +108,13 @@ export class DocsStore extends Store {
         if (pages instanceof YArray) {
           return pages.map(v => ({
             id: v.get('id') as string,
-            tags: (v.get('tags')?.toJSON() ?? []) as string[],
+            tags: (() => {
+              const tags = v.get('tags');
+              if (tags instanceof YArray) {
+                return tags.toJSON() as string[];
+              }
+              return (tags ?? []) as string[];
+            })(),
           }));
         } else {
           return [];


Reviews:
user: login: EYHN
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-08T08:43:06Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T12:12:00Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 5**

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

<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/properties.tsx (1)</summary><blockquote>

`58-63`: **Dependency array captures a mutable observable**

`handleDisplayPropertiesChange` depends on `explorerContextValue.displayProperties$`, but the observable reference is stable only if the provider memoises it.  
If it is recreated on every render you will get an unnecessary new callback each render.

```diff
const handleDisplayPropertiesChange = useCallback(
  (ids: string[]) => explorerContextValue.displayProperties$?.next(ids),
-  [explorerContextValue.displayProperties$]
+  [explorerContextValue] // or leave the array empty if the ref is guaranteed stable
);
```

Confirm the stability of the observable reference to avoid subtle re-renders.

</blockquote></details>
<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx (2)</summary><blockquote>

`29-39`: **Dynamic key access bypasses type safety**

Accessing observables through `explorerContextValue[\`\${action.key}$\`]` circumvents TypeScript’s exhaustiveness checking. A typo in `action.key` or a missing observable will only fail at runtime.

Consider adding a type-safe lookup helper:

```ts
function getQuickAction$(
  ctx: DocExplorerContextType,
  key: QuickActionKey
): LiveData<boolean> {
  return (ctx as any)[`${key}$`];
}
```

Then use:

```ts
const action$ = getQuickAction$(explorerContextValue, action.key);
const value = useLiveData(action$);
```

This enables compile-time verification and makes refactors safer.

---

`31-38`: **Event handler recreates on every render**

`handleClick` closes over `explorerContextValue` which is likely stable, but it also closes over `action.key`. Because `QuickActionItem` is keyed by `action.key`, you can safely inline the handler to eliminate an extra `useCallback`, reducing code size and avoiding unnecessary memoisation.

</blockquote></details>
<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/index.tsx (1)</summary><blockquote>

`31-43`: **Handle potential undefined observables**

The handlers use optional chaining (`?.`) when calling `next()` on observables, which could silently fail if the observable is undefined.

Consider adding a guard clause or assertion to handle cases where the observable might be undefined:

```diff
  const handleGroupByChange = useCallback(
    (groupBy: GroupByParams) => {
-      explorerContextValue.groupBy$?.next(groupBy);
+      if (explorerContextValue.groupBy$) {
+        explorerContextValue.groupBy$.next(groupBy);
+      }
    },
    [explorerContextValue.groupBy$]
  );

  const handleOrderByChange = useCallback(
    (orderBy: OrderByParams) => {
-      explorerContextValue.orderBy$?.next(orderBy);
+      if (explorerContextValue.orderBy$) {
+        explorerContextValue.orderBy$.next(orderBy);
+      }
    },
    [explorerContextValue.orderBy$]
  );
```

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

`69-75`: **Avoid repeated context look-ups – lift `contextValue` & memoize selectors**

Every sub-component (`DocListItem`, `DragHandle`, `Select`, `DocIcon`, `DocPreview`, …) calls `useContext(DocExplorerContext)` individually, creating many unrelated subscriptions.  Although cheap, each call still walks the context tree and, more importantly, defeats tree-shaking if you later split the file.

A tiny helper can keep the code DRY and guarantees stable references:

```diff
-const contextValue = useContext(DocExplorerContext);
-const view = useLiveData(contextValue.view$);
-const groups = useLiveData(contextValue.groups$);
+const explorer = useContext(DocExplorerContext);           // one lookup
+
+// small selector helpers keep the call-sites clean
+const useExplorer = <T,>(sel: (c: typeof explorer) => LiveData<T>) =>
+  useLiveData(sel(explorer));
+
+const view            = useExplorer(c => c.view$);
+const groups          = useExplorer(c => c.groups$);
```

You may keep one “public” hook (`useExplorer`) close to the context definition to reuse everywhere.

---

`122-140`: **Click handler ignores ⌘/Ctrl multi-select**

Single click inside `selectMode` overwrites the whole `selectedDocIds` array with `[docId]`, so users cannot build an arbitrary selection set via ⌘/Ctrl+Click. If that interaction is required, change to:

```diff
-if (selectMode) {
-  …
-  contextValue.selectedDocIds$.next([docId]);
+if (selectMode) {
+  …
+  const prev = contextValue.selectedDocIds$.value;
+  const next = prev.includes(docId)
+    ? prev.filter(id => id !== docId)
+    : [...prev, docId];
+  contextValue.selectedDocIds$.next(next);
```

---

`233-238`: **Checkbox change handler overwrites whole selection**

`Select` currently reduces the selection to a single id. This makes the checkbox behave differently from a shift-click on the row. Re-use the toggle logic suggested above so both entry points are consistent.

</blockquote></details>
<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (1)</summary><blockquote>

`1-50`: **ESLint “Finnish notation” errors – decide & silence intentionally**

Static analysis flags every `$` suffixed property. Since `$` is idiomatic for `LiveData`/observable values in this codebase, either:

1. Disable the `rxjs/finnish` rule in the project, or
2. Locally suppress it once at the top of this file:

```ts
/* eslint-disable rxjs/finnish */
```

Without this, CI may fail.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 8-8: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 9-9: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 10-10: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 11-11: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 12-12: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 13-13: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 24-24: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 25-25: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 26-26: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 27-27: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 28-28: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 29-29: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 30-30: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 38-38: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 39-39: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 40-40: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 43-43: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 44-44: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 45-45: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 46-46: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 47-47: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 48-48: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 49-49: Finnish notation should not be used here.

(rxjs/finnish)

</details>

</details>

</blockquote></details>
<details>
<summary>packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx (3)</summary><blockquote>

`104-114`: **`useLiveData` avalanche – memo selectors to avoid extra renders**

Each `useLiveData(explorerContextValue.xxx$)` creates an independent subscription, causing the entire `AllPage` component to re-render when **any** tracked field changes (view, filters, selection, …).  
That’s acceptable for small UI but can become expensive.

Consider aggregating the streams into one derived state (e.g., `combineLatest`) inside the context layer or via a custom hook (`useExplorerSelectors`). That way only truly relevant parts update.

---

`145-154`: **Dependency array: passing `explorerContextValue.groups$` is redundant**

The `LiveData` instance of `groups$` is stable; including it in `useEffect`’s dependency array forces linter suppressions and mental overhead without any practical change detection.

```diff
-  }, [
-    collectionRulesService,
-    explorerContextValue.groups$,
-    filters,
-    groupBy,
-    orderBy,
-  ]);
+  }, [collectionRulesService, filters, groupBy, orderBy]);
```

This keeps the effect semantics unchanged and avoids accidental re-subscriptions if the context implementation ever recreates `groups$`.

---

`166-179`: **Event listener cleanup – use `passive: true` or throttling**

`keydown` listeners fire globally; on heavy editors multiple listeners might stack. Passing `{ passive: true }` (where supported) or moving the handler to `useEvent` (from `react-use`) prevents layout thrash. Minor, but worth noting in performance-sensitive PR.

</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 7013f9b8049c5a665f09e1ed5cbe678c53b1e103 and 13e3ed06867186cc2f69001959a5db413b465330.

</details>

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

* `packages/frontend/core/src/components/explorer/context.ts` (1 hunks)
* `packages/frontend/core/src/components/explorer/display-menu/index.tsx` (1 hunks)
* `packages/frontend/core/src/components/explorer/display-menu/properties.tsx` (4 hunks)
* `packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx` (1 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx` (7 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/group-header.tsx` (3 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/properties.tsx` (1 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx` (5 hunks)
* `packages/frontend/core/src/components/explorer/quick-actions.constants.tsx` (3 hunks)
* `packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx` (2 hunks)
* `packages/frontend/core/src/components/workspace-property-types/index.ts` (1 hunks)
* `packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx` (3 hunks)
* `packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx` (6 hunks)
* `packages/frontend/core/src/modules/cloud/views/public-user.css.ts` (1 hunks)
* `packages/frontend/core/src/modules/cloud/views/public-user.tsx` (1 hunks)
* `packages/frontend/core/src/modules/doc/stores/docs.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (6)</summary>

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

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (1)</summary>

* `DocExplorerContext` (18-20)

</details>

</blockquote></details>
<details>
<summary>packages/frontend/core/src/components/workspace-property-types/index.ts (1)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/components/workspace-property-types/date.tsx (1)</summary>

* `CreatedGroupHeader` (256-258)

</details>

</blockquote></details>
<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/properties.tsx (1)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (1)</summary>

* `DocExplorerContext` (18-20)

</details>

</blockquote></details>
<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx (2)</summary><blockquote>

<details>
<summary>packages/frontend/core/src/components/explorer/quick-actions.constants.tsx (2)</summary>

* `quickActions` (48-52)
* `QuickAction` (54-54)

</details>
<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (1)</summary>

* `DocExplorerContext` (18-20)

</details>

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

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (1)</summary>

* `DocExplorerContext` (18-20)

</details>

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

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (1)</summary>

* `DocExplorerContext` (18-20)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts</summary>

[error] 8-8: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 9-9: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 10-10: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 11-11: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 12-12: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 13-13: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 24-24: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 25-25: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 26-26: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 27-27: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 28-28: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 29-29: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 30-30: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 38-38: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 39-39: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 40-40: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 43-43: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 44-44: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 45-45: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 46-46: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 47-47: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 48-48: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 49-49: Finnish notation should not be used here.

(rxjs/finnish)

</details>

</details>

</details>

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

<details>
<summary>packages/frontend/core/src/components/explorer/quick-actions.constants.tsx (3)</summary>

`13-17`: **Improved type naming for better clarity**

Renaming the interface from `QuickAction` to `QuickActionItem` provides better clarity by distinguishing between the interface shape (internal implementation details) and the exported type.

---

`25-25`: **Updated type reference for consistency**

The type annotation for `QUICK_ACTION_MAP` now correctly uses `QuickActionItem` instead of the old interface name, maintaining type consistency throughout the file.

---

`54-54`: **Good addition of an exported type**

Adding a new exported type `QuickAction` as the union type of the elements of the `quickActions` array is a good practice that exposes a proper public API while keeping implementation details encapsulated.

</details>
<details>
<summary>packages/frontend/core/src/modules/cloud/views/public-user.tsx (1)</summary>

`59-59`: **Enhanced styling control with data attribute**

Adding the `data-show-name` attribute to the Avatar component enables conditional CSS styling based on whether the name is shown, which works well with the corresponding CSS changes in `public-user.css.ts`.

</details>
<details>
<summary>packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx (2)</summary>

`147-147`: **Consistent user interface behavior**

Adding `showName={false}` to `CreatedByUpdatedByAvatar` in the `CreatedByDocListInlineProperty` component ensures consistent behavior with other similar components where only the avatar is displayed.

---

`175-175`: **Consistent user interface behavior**

Setting `showName={false}` for the `PublicUserLabel` in `ModifiedByGroupHeader` aligns with the updated styling approach of controlling name visibility and ensures consistent display behavior across the application.

</details>
<details>
<summary>packages/frontend/core/src/components/workspace-property-types/index.ts (1)</summary>

`226-226`: **Added missing group header configuration**

Adding the `groupHeader: CreatedGroupHeader` property to the `updatedAt` property ensures consistent behavior with other date-related properties like `createdAt`, which already include this configuration. This completes the property type definition.

</details>
<details>
<summary>packages/frontend/core/src/modules/cloud/views/public-user.css.ts (1)</summary>

`25-29`: **Improved conditional styling with data attribute**

The CSS has been improved to conditionally apply margin only when the name is shown (`data-show-name="true"`), and the vertical transformation has been removed. This creates a cleaner layout that adapts to whether the user name is being displayed.

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

`31-33`: **Guard against missing context provider**

`useLiveData(contextValue.displayProperties$)` will throw if `displayProperties$` is `undefined` (which is what the default `{ } as any` gives you when the component is rendered outside of the expected provider).  
Consider supplying a defensive fallback so that the hook does not crash in unit tests or story-book environments where the provider is sometimes omitted.

```diff
- const displayProperties = useLiveData(contextValue.displayProperties$);
+ const displayProperties = useLiveData(
+   contextValue.displayProperties$ ?? /* default */ { value: [] } as any
+ );
```

An even better improvement is to make the absence of the provider a *type error* by removing the `as any` cast in the context definition and providing a meaningful default instead.

</details>
<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/properties.tsx (1)</summary>

`32-43`: **`useLiveData` will crash if any observable is undefined**

All three calls rely on the context always providing the corresponding `LiveData`.  
If the menu is rendered before the context is fully initialised (e.g. during skeleton loading) the component will throw.

```diff
-const displayProperties = useLiveData(explorerContextValue.displayProperties$);
+const displayProperties = useLiveData(
+  explorerContextValue.displayProperties$ ?? ({ value: [] } as any)
+);
```

Apply the same fallback to `showDocIcon$` and `showDocPreview$`, or – preferably – guarantee in `DocExplorerContext` that these observables are non-nullable.

</details>
<details>
<summary>packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx (2)</summary>

`32-57`: **Well-executed reactive state management refactoring!**

The `ViewToggle` component now correctly obtains its state from the `DocExplorerContext` using the `useLiveData` hook instead of props, which eliminates prop drilling. The `handleViewChange` callback is properly memoized to prevent unnecessary re-renders.

---

`67-78`: **Good simplification of component interface**

The `AllDocsHeader` component signature has been simplified by removing the `explorerPreference` and `setExplorerPreference` props, making the component more maintainable. The state is now managed entirely through the reactive context.

</details>
<details>
<summary>packages/frontend/core/src/components/explorer/docs-view/group-header.tsx (4)</summary>

`24-29`: **Excellent reactive state subscription implementation**

The component now subscribes to multiple context observables using `useLiveData`, which will improve rendering performance by preventing unnecessary re-renders when unrelated state changes.

---

`36-43`: **Clean implementation of collapsible group toggle**

The collapse handler properly accesses the current value with `.value` and updates it using `.next()`, following the reactive pattern consistently. This approach is more efficient than the previous prop drilling method.

---

`45-60`: **Effective implementation of bulk selection logic**

The select all handler now directly updates the `selectedDocIds$` observable, maintaining the same functionality while improving the state management approach. The logic for selecting/deselecting all items in a group is handled correctly.

---

`69-69`: **Clean reactive UI state handling**

The `data-collapsed` attribute is now directly derived from the reactive state, which will update efficiently when the collapsed groups change.

</details>
<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/index.tsx (3)</summary>

`25-30`: **Good centralization of state management**

The component now properly accesses state through context observables using `useLiveData` instead of receiving it through props, which aligns well with the overall refactoring pattern.

---

`68-70`: **Nice removal of prop drilling for child components**

The `DisplayProperties` and `QuickActionsConfig` components are now rendered without props, assuming they've been refactored to use context directly, which is consistent with the overall architecture changes.

---

`86-86`: **Clean simplification of component interface**

The `ExplorerDisplayMenuButton` component now correctly passes the `ExplorerDisplayMenu` without unnecessary props, streamlining the component hierarchy.

</details>
<details>
<summary>packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx (3)</summary>

`27-28`: **Excellent consistent context usage across quick action components**

All quick action components (`QuickFavorite`, `QuickTab`, `QuickSplit`, `QuickDelete`, `QuickSelect`) now consistently use the context observables with `useLiveData` to access their respective feature flags and state. This provides better performance and maintainability.



Also applies to: 59-60, 88-89, 121-122, 169-171

---

`179-184`: **Direct state update with reactive pattern**

The `QuickSelect` component now directly updates the `selectedDocIds$` observable instead of using a callback, which is consistent with the reactive pattern used throughout the codebase.

---

`185-185`: **Well-maintained dependency array**

The dependency array for the `onChange` callback correctly includes all used values (`contextValue`, `doc.id`, `selected`, `selectedDocIds`), ensuring the callback is properly memoized.

</details>

</blockquote></details>

</details>

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

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

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (2)</summary><blockquote>

`7-16`: _🛠️ Refactor suggestion_

**Optional fields mismatch with implementation.**

`selectMode$` and `prevCheckAnchorId$` are marked optional (`?`) in the type but always provided by `createDocExplorerContext`, and consumers don't check for their existence.

```diff
-  selectMode$?: LiveData<boolean>;
-  prevCheckAnchorId$?: LiveData<string | null>;
+  selectMode$: LiveData<boolean>;
+  prevCheckAnchorId$: LiveData<string | null>;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 8-8: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 9-9: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 10-10: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 11-11: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 12-12: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 13-13: Finnish notation should not be used here.

(rxjs/finnish)

</details>

</details>

---

`18-20`: _🛠️ Refactor suggestion_

**Empty default context may cause runtime errors.**

The current implementation creates a context with an empty object that lacks all the expected fields, which could lead to runtime errors if a component uses the context outside of a provider.

```diff
-export const DocExplorerContext = createContext<DocExplorerContextType>(
-  {} as any
-);
+export const DocExplorerContext = createContext<DocExplorerContextType | null>(
+  null
+);
+
+export const useDocExplorerContext = () => {
+  const ctx = useContext(DocExplorerContext);
+  if (!ctx) {
+    throw new Error('DocExplorerContext not found – did you forget the provider?');
+  }
+  return ctx;
+};
```

</blockquote></details>

</blockquote></details>

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

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

`117-141`: **Consider extracting hardcoded values to constants.**

The component contains hardcoded style values and calculations that could be extracted to constants or theme variables for better maintainability.

For example, consider extracting the height calculations:

```diff
+const VIEW_HEIGHTS = {
+  list: 42,
+  grid: 280,
+  masonry: (docId: string) => calcCardHeightById(docId)
+};

// Then in the code:
-height:
-  view === 'list'
-    ? 42
-    : view === 'grid'
-      ? 280
-      : calcCardHeightById(docId),
+height: view === 'masonry' 
+  ? VIEW_HEIGHTS.masonry(docId)
+  : VIEW_HEIGHTS[view],
```

</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 13e3ed06867186cc2f69001959a5db413b465330 and 66500669c862c3aaa30de70d0e7ddb9955b60cb9.

</details>

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

* `packages/frontend/core/src/components/explorer/context.ts` (1 hunks)
* `packages/frontend/core/src/components/explorer/display-menu/index.tsx` (1 hunks)
* `packages/frontend/core/src/components/explorer/display-menu/properties.tsx` (4 hunks)
* `packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx` (1 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx` (7 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/group-header.tsx` (3 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/properties.tsx` (1 hunks)
* `packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx` (5 hunks)
* `packages/frontend/core/src/components/explorer/quick-actions.constants.tsx` (3 hunks)
* `packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx` (2 hunks)
* `packages/frontend/core/src/components/workspace-property-types/index.ts` (1 hunks)
* `packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx` (3 hunks)
* `packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx` (6 hunks)
* `packages/frontend/core/src/modules/cloud/views/public-user.css.ts` (1 hunks)
* `packages/frontend/core/src/modules/cloud/views/public-user.tsx` (1 hunks)
* `packages/frontend/core/src/modules/doc/stores/docs.ts` (1 hunks)

</details>

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

* packages/frontend/core/src/modules/cloud/views/public-user.tsx
* packages/frontend/core/src/modules/cloud/views/public-user.css.ts
* packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx
* packages/frontend/core/src/components/workspace-property-types/index.ts
* packages/frontend/core/src/components/explorer/display-menu/properties.tsx
* packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx
* packages/frontend/core/src/components/explorer/docs-view/properties.tsx
* packages/frontend/core/src/modules/doc/stores/docs.ts
* packages/frontend/core/src/components/explorer/quick-actions.constants.tsx
* packages/frontend/core/src/components/explorer/docs-view/group-header.tsx
* packages/frontend/core/src/components/explorer/docs-view/quick-actions.tsx
* packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx
* packages/frontend/core/src/components/explorer/display-menu/quick-actions.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (2)</summary><blockquote>

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

* `DocListItemView` (35-35)

</details>
<details>
<summary>packages/frontend/core/src/components/explorer/types.ts (1)</summary>

* `ExplorerPreference` (9-21)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>packages/frontend/core/src/components/explorer/context.ts</summary>

[error] 8-8: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 9-9: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 10-10: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 11-11: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 12-12: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 13-13: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 24-24: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 25-25: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 26-26: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 27-27: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 28-28: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 29-29: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 30-30: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 38-38: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 39-39: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 40-40: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 43-43: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 44-44: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 45-45: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 46-46: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 47-47: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 48-48: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 49-49: Finnish notation should not be used here.

(rxjs/finnish)

</details>

</details>

</details>

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

* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* 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: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* 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: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* 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: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* 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: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* 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: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop bundle check (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Desktop bundle check (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop bundle check (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false)
* GitHub Check: Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true)
* GitHub Check: Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true)
* GitHub Check: Desktop Test (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: Server Test (2, 8)
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* GitHub Check: Unit Test (1)
* GitHub Check: test-build-mobile-app / build-android-web
* GitHub Check: test-build-mobile-app / build-ios-web
* GitHub Check: y-octo binding test on aarch64-pc-windows-msvc
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: loom thread test
* GitHub Check: fuzzing
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: Run native tests
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Test (8)
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Test (7)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: Typecheck
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint

</details>

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

<details>
<summary>packages/frontend/core/src/components/explorer/display-menu/index.tsx (6)</summary>

`14-16`: **LGTM! Well-structured import changes to support reactive context pattern.**

The imports have been properly updated to include the necessary hooks and context for the new reactive state management approach.



Also applies to: 18-18

---

`25-29`: **Great refactor to internalize state management!**

The component signature has been simplified by removing the `preference` and `onChange` props in favor of directly accessing state from context. This reduces prop drilling and improves maintainability.

---

`31-43`: **LGTM! Clean implementation of state update handlers.**

The update handlers now directly modify the context's LiveData observables instead of calling callback props. The dependencies in useCallback have been correctly updated to reference the corresponding observables.

---

`48-64`: **LGTM! Properly passing state to child components.**

The component now correctly uses local state from context to pass to child components that still require props.

---

`68-70`: **Cleaned up child component integration.**

`DisplayProperties` and `QuickActionsConfig` no longer receive props, indicating they've been similarly refactored to use context directly. This completes the pattern consistently across components.

---

`86-86`: **LGTM! Updated ExplorerDisplayMenuButton to match new pattern.**

The button component no longer needs to pass preference and onChange props to ExplorerDisplayMenu, aligning with the context-based state management approach.

</details>
<details>
<summary>packages/frontend/core/src/components/explorer/context.ts (1)</summary>

`22-50`: **LGTM! Well-structured factory function for context creation.**

The `createDocExplorerContext` factory provides a clean way to initialize all LiveData observables with appropriate default values. The use of the `satisfies` operator ensures type compatibility.

Note: The static analysis flags regarding Finnish notation ($) can be ignored as this is a standard convention for observable streams.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 24-24: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 25-25: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 26-26: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 27-27: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 28-28: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 29-29: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 30-30: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 38-38: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 39-39: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 40-40: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 43-43: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 44-44: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 45-45: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 46-46: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 47-47: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 48-48: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 49-49: Finnish notation should not be used here.

(rxjs/finnish)

</details>

</details>

</details>
<details>
<summary>packages/frontend/core/src/desktop/pages/workspace/all-page/all-page.tsx (8)</summary>

`104-104`: **LGTM! Simplified context initialization.**

Creating the context value once using useState is an efficient approach that ensures stable context references across renders.

---

`106-114`: **Great refactor to reactive state management!**

The component now uses LiveData observables from context instead of multiple useState hooks. This centralizes state management and reduces unnecessary re-renders.

---

`143-164`: **LGTM! Properly updated effect dependencies.**

The effect dependencies have been correctly updated to reference the new reactive state sources, ensuring the effect runs when relevant state changes.

---

`166-178`: **LGTM! Simplified keyboard event handling.**

The keyboard event handler now directly updates context observables, and the dependencies have been correctly updated to include only the explorerContextValue.

---

`180-185`: **LGTM! Clean implementation of filter change handler.**

The handler has been updated to modify the filters$ observable directly and has the correct dependency.

---

`187-190`: **LGTM! Properly updated toolbar handler.**

The handler now correctly updates context observables directly.

---

`225-225`: **LGTM! Simplified AllDocsHeader usage.**

AllDocsHeader no longer needs props as it now accesses state directly from context.

---

`231-231`: **LGTM! Updated Filters component integration.**

The Filters component still receives props but now gets its values from context-derived state, which is consistent with the overall pattern.

</details>

</blockquote></details>

</details>

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

Review comments:
diff_hunk: @@ -47,9 +43,10 @@ const GroupHeader = memo(function GroupHeader({
   collapsed?: boolean;
   itemCount: number;
 }) {
-  const { groupBy } = useContext(DocExplorerContext);
+  const { groupBy$ } = useContext(DocExplorerContext);
path: packages/frontend/core/src/desktop/pages/workspace/all-page-new/all-page.tsx
user: login: graphite-app[bot]
body: This line is using the old context structure with direct 'groupBy' property. It should be updated to use the new LiveData structure: const { groupBy$ } = useContext(DocExplorerContext); const groupBy = useLiveData(groupBy$);

*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-08T12:11:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -108,7 +108,13 @@ export class DocsStore extends Store {
         if (pages instanceof YArray) {
           return pages.map(v => ({
             id: v.get('id') as string,
-            tags: (v.get('tags')?.toJSON() ?? []) as string[],
+            tags: (() => {
+              const tags = v.get('tags');
+              if (tags instanceof YArray) {
+                return tags.toJSON() as string[];
+              }
+              return (tags ?? []) as string[];
+            })(),
           }));
path: packages/frontend/core/src/modules/doc/stores/docs.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Type safety & data-shape clarification for tags extraction**

Good catch on the runtime check – this protects against `.toJSON()` on non-YArray values.  
Two additional observations:

1. `tags.toJSON()` returns `unknown[]`, but you immediately assert it to `string[]`. If the collaboration payload ever stores objects (e.g. `{ id, name }`) the cast silently lies and downstream code may explode.
2. When `tags` is *already* a plain JS array you return it unchanged; this keeps the original reference, which might be mutated elsewhere.

```diff
- if (tags instanceof YArray) {
-   return tags.toJSON() as string[];
- }
- return (tags ?? []) as string[];
+if (tags instanceof YArray) {
+  return tags.toJSON().filter((t): t is string => typeof t === 'string');
+}
+return Array.isArray(tags) ? [...tags] : [];
```

This keeps the return type honest and prevents accidental shared-mutation.  
  


<!-- 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
            tags: (() => {
              const tags = v.get('tags');
              if (tags instanceof YArray) {
                return tags.toJSON().filter((t): t is string => typeof t === 'string');
              }
              return Array.isArray(tags) ? [...tags] : [];
            })(),
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:57:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,52 +66,49 @@ class MixId {
   }
 }
 export const DocListItem = ({ ...props }: DocListItemProps) => {
-  const {
-    view,
-    groups,
-    selectMode,
-    selectedDocIds,
-    prevCheckAnchorId,
-    onSelect,
-    onToggleSelect,
-    setPrevCheckAnchorId,
-  } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const view = useLiveData(contextValue.view$);
+  const groups = useLiveData(contextValue.groups$);
+  const selectMode = useLiveData(contextValue.selectMode$);
+  const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
+  const prevCheckAnchorId = useLiveData(contextValue.prevCheckAnchorId$);
 
   const handleMultiSelect = useCallback(
     (prevCursor: string, currCursor: string) => {
       const flattenList = groups.flatMap(group =>
         group.items.map(docId => MixId.create(group.key, docId))
       );
+
+      const prev = contextValue.selectedDocIds$?.value ?? [];
       const prevIndex = flattenList.indexOf(prevCursor);
       const currIndex = flattenList.indexOf(currCursor);
 
-      onSelect(prev => {
-        const lowerIndex = Math.min(prevIndex, currIndex);
-        const upperIndex = Math.max(prevIndex, currIndex);
+      const lowerIndex = Math.min(prevIndex, currIndex);
+      const upperIndex = Math.max(prevIndex, currIndex);
 
-        const resSet = new Set(prev);
-        const handledSet = new Set<string>();
-        for (let i = lowerIndex; i <= upperIndex; i++) {
-          const mixId = flattenList[i];
-          const { groupId, docId } = MixId.parse(mixId);
-          if (groupId === null || docId === null) {
-            continue;
-          }
-          if (handledSet.has(docId) || mixId === prevCursor) {
-            continue;
-          }
-          if (resSet.has(docId)) {
-            resSet.delete(docId);
-          } else {
-            resSet.add(docId);
-          }
-          handledSet.add(docId);
+      const resSet = new Set(prev);
+      const handledSet = new Set<string>();
+      for (let i = lowerIndex; i <= upperIndex; i++) {
+        const mixId = flattenList[i];
+        const { groupId, docId } = MixId.parse(mixId);
+        if (groupId === null || docId === null) {
+          continue;
+        }
+        if (handledSet.has(docId) || mixId === prevCursor) {
+          continue;
+        }
+        if (resSet.has(docId)) {
+          resSet.delete(docId);
+        } else {
+          resSet.add(docId);
         }
-        return [...resSet];
-      });
-      setPrevCheckAnchorId(currCursor);
+        handledSet.add(docId);
+      }
+
path: packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Range-select should add, not toggle, by default**

Current behaviour toggles each item in the interval: items already selected get un-selected. Most UX guidelines (macOS / Windows Explorer, VS Code, etc.) treat shift-click as _additive_ selection. If this is intentional, ignore; otherwise replacing the toggle logic with a union will match user expectations and simplify the loop:

```diff
-        if (resSet.has(docId)) {
-          resSet.delete(docId);
-        } else {
-          resSet.add(docId);
-        }
+        resSet.add(docId);   // always add
```

---



</details>

**Make range-select additive, not toggling**

The current loop toggles each item’s selection state—removing already-selected items—which conflicts with standard UX (macOS Finder, Windows Explorer, VS Code) where shift-click range-select should only add items. To align behavior and simplify the code, replace the toggle with a straight `add`:

• File: `packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx`  
• Around lines 89–107, change:

```diff
-        if (resSet.has(docId)) {
-          resSet.delete(docId);
-        } else {
-          resSet.add(docId);
-        }
+        // Always add items within the shift-click range
+        resSet.add(docId);
```

This ensures range selection only expands the existing set. 

<!-- 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 resSet = new Set(prev);
      const handledSet = new Set<string>();
      for (let i = lowerIndex; i <= upperIndex; i++) {
        const mixId = flattenList[i];
        const { groupId, docId } = MixId.parse(mixId);
        if (groupId === null || docId === null) {
          continue;
        }
        if (handledSet.has(docId) || mixId === prevCursor) {
          continue;
        }
        // Always add items within the shift-click range
        resSet.add(docId);
        handledSet.add(docId);
      }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:57:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,52 +66,49 @@
   }
 }
 export const DocListItem = ({ ...props }: DocListItemProps) => {
-  const {
-    view,
-    groups,
-    selectMode,
-    selectedDocIds,
-    prevCheckAnchorId,
-    onSelect,
-    onToggleSelect,
-    setPrevCheckAnchorId,
-  } = useContext(DocExplorerContext);
+  const contextValue = useContext(DocExplorerContext);
+  const view = useLiveData(contextValue.view$);
+  const groups = useLiveData(contextValue.groups$);
+  const selectMode = useLiveData(contextValue.selectMode$);
+  const selectedDocIds = useLiveData(contextValue.selectedDocIds$);
+  const prevCheckAnchorId = useLiveData(contextValue.prevCheckAnchorId$);
 
   const handleMultiSelect = useCallback(
     (prevCursor: string, currCursor: string) => {
       const flattenList = groups.flatMap(group =>
         group.items.map(docId => MixId.create(group.key, docId))
       );
+
+      const prev = contextValue.selectedDocIds$?.value ?? [];
       const prevIndex = flattenList.indexOf(prevCursor);
       const currIndex = flattenList.indexOf(currCursor);
 
-      onSelect(prev => {
-        const lowerIndex = Math.min(prevIndex, currIndex);
-        const upperIndex = Math.max(prevIndex, currIndex);
+      const lowerIndex = Math.min(prevIndex, currIndex);
+      const upperIndex = Math.max(prevIndex, currIndex);
 
-        const resSet = new Set(prev);
-        const handledSet = new Set<string>();
-        for (let i = lowerIndex; i <= upperIndex; i++) {
-          const mixId = flattenList[i];
-          const { groupId, docId } = MixId.parse(mixId);
-          if (groupId === null || docId === null) {
-            continue;
-          }
-          if (handledSet.has(docId) || mixId === prevCursor) {
-            continue;
-          }
-          if (resSet.has(docId)) {
-            resSet.delete(docId);
-          } else {
-            resSet.add(docId);
-          }
-          handledSet.add(docId);
+      const resSet = new Set(prev);
+      const handledSet = new Set<string>();
+      for (let i = lowerIndex; i <= upperIndex; i++) {
+        const mixId = flattenList[i];
+        const { groupId, docId } = MixId.parse(mixId);
+        if (groupId === null || docId === null) {
+          continue;
+        }
+        if (handledSet.has(docId) || mixId === prevCursor) {
+          continue;
+        }
+        if (resSet.has(docId)) {
+          resSet.delete(docId);
+        } else {
+          resSet.add(docId);
         }
-        return [...resSet];
-      });
-      setPrevCheckAnchorId(currCursor);
+        handledSet.add(docId);
+      }
+
+      contextValue.selectedDocIds$?.next(Array.from(resSet));
path: packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`handleMultiSelect` can break for unknown cursors**

When either `prevCursor` or `currCursor` is _not_ found in `flattenList`, `indexOf` returns `-1`, producing an invalid range (`lowerIndex = -1`). The subsequent `for` loop then iterates from `-1`, which yields `flattenList[-1] === undefined`, and ultimately throws.

Guard early:

```diff
-const prevIndex = flattenList.indexOf(prevCursor);
-const currIndex = flattenList.indexOf(currCursor);
+const prevIndex = flattenList.indexOf(prevCursor);
+const currIndex = flattenList.indexOf(currCursor);
+
+if (prevIndex === -1 || currIndex === -1) {
+  // cursors are out of current list scope – bail out gracefully
+  return;
+}
```

This prevents runtime errors when the underlying list changes between two clicks.  



<!-- 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 prev = contextValue.selectedDocIds$?.value ?? [];
      const prevIndex = flattenList.indexOf(prevCursor);
      const currIndex = flattenList.indexOf(currCursor);

      if (prevIndex === -1 || currIndex === -1) {
        // cursors are out of current list scope – bail out gracefully
        return;
      }

      const lowerIndex = Math.min(prevIndex, currIndex);
      const upperIndex = Math.max(prevIndex, currIndex);

      const resSet = new Set(prev);
      const handledSet = new Set<string>();
      for (let i = lowerIndex; i <= upperIndex; i++) {
        const mixId = flattenList[i];
        const { groupId, docId } = MixId.parse(mixId);
        if (groupId === null || docId === null) {
          continue;
        }
        if (handledSet.has(docId) || mixId === prevCursor) {
          continue;
        }
        if (resSet.has(docId)) {
          resSet.delete(docId);
        } else {
          resSet.add(docId);
        }
        handledSet.add(docId);
      }

      contextValue.selectedDocIds$?.next(Array.from(resSet));
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:57:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,31 +1,50 @@
-import { createContext, type Dispatch, type SetStateAction } from 'react';
+import { LiveData } from '@toeverything/infra';
+import { createContext } from 'react';
 
 import type { DocListItemView } from './docs-view/doc-list-item';
 import type { ExplorerPreference } from './types';
 
-export type DocExplorerContextType = ExplorerPreference & {
-  view: DocListItemView;
-  setView: Dispatch<SetStateAction<DocListItemView>>;
-  groups: Array<{ key: string; items: string[] }>;
-  collapsed: string[];
-  selectMode?: boolean;
-  selectedDocIds: string[];
-  prevCheckAnchorId?: string | null;
-  onToggleCollapse: (groupId: string) => void;
-  onToggleSelect: (docId: string) => void;
-  onSelect: Dispatch<SetStateAction<string[]>>;
-  setPrevCheckAnchorId: Dispatch<SetStateAction<string | null>>;
+export type DocExplorerContextType = {
+  view$: LiveData<DocListItemView>;
+  groups$: LiveData<Array<{ key: string; items: string[] }>>;
+  collapsedGroups$: LiveData<string[]>;
+  selectMode$?: LiveData<boolean>;
+  selectedDocIds$: LiveData<string[]>;
+  prevCheckAnchorId$?: LiveData<string | null>;
+} & {
+  [K in keyof ExplorerPreference as `${K}$`]: LiveData<ExplorerPreference[K]>;
 };
path: packages/frontend/core/src/components/explorer/context.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Optional fields mismatch – simplify and strengthen the type**

`selectMode$` and `prevCheckAnchorId$` are marked optional (`?`) in the type but _always_ provided by `createDocExplorerContext`, and consumers never null-check their existence.  
Removing `?` avoids unnecessary undefined checks and tightens the contract:

```diff
-  selectMode$?: LiveData<boolean>;
-  prevCheckAnchorId$?: LiveData<string | null>;
+  selectMode$: LiveData<boolean>;
+  prevCheckAnchorId$: LiveData<string | null>;
```



<!-- 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
export type DocExplorerContextType = {
  view$: LiveData<DocListItemView>;
  groups$: LiveData<Array<{ key: string; items: string[] }>>;
  collapsedGroups$: LiveData<string[]>;
  selectMode$: LiveData<boolean>;
  selectedDocIds$: LiveData<string[]>;
  prevCheckAnchorId$: LiveData<string | null>;
} & {
  [K in keyof ExplorerPreference as `${K}$`]: LiveData<ExplorerPreference[K]>;
};
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 8-8: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 9-9: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 10-10: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 11-11: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 12-12: Finnish notation should not be used here.

(rxjs/finnish)

---

[error] 13-13: Finnish notation should not be used here.

(rxjs/finnish)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:57:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,30 +1,50 @@
+import { LiveData } from '@toeverything/infra';
+import { createContext } from 'react';
 
 import type { DocListItemView } from './docs-view/doc-list-item';
 import type { ExplorerPreference } from './types';
 
-export type DocExplorerContextType = ExplorerPreference & {
-  view: DocListItemView;
-  setView: Dispatch<SetStateAction<DocListItemView>>;
-  groups: Array<{ key: string; items: string[] }>;
-  collapsed: string[];
-  selectMode?: boolean;
-  selectedDocIds: string[];
-  prevCheckAnchorId?: string | null;
-  onToggleCollapse: (groupId: string) => void;
-  onToggleSelect: (docId: string) => void;
-  onSelect: Dispatch<SetStateAction<string[]>>;
-  setPrevCheckAnchorId: Dispatch<SetStateAction<string | null>>;
+export type DocExplorerContextType = {
+  view$: LiveData<DocListItemView>;
+  groups$: LiveData<Array<{ key: string; items: string[] }>>;
+  collapsedGroups$: LiveData<string[]>;
+  selectMode$?: LiveData<boolean>;
+  selectedDocIds$: LiveData<string[]>;
+  prevCheckAnchorId$?: LiveData<string | null>;
+} & {
+  [K in keyof ExplorerPreference as `${K}$`]: LiveData<ExplorerPreference[K]>;
 };
 
-export const DocExplorerContext = createContext<DocExplorerContextType>({
-  view: 'list',
-  setView: () => {},
-  groups: [],
-  collapsed: [],
-  selectedDocIds: [],
-  prevCheckAnchorId: null,
-  onToggleSelect: () => {},
-  onToggleCollapse: () => {},
-  onSelect: () => {},
-  setPrevCheckAnchorId: () => {},
-});
+export const DocExplorerContext = createContext<DocExplorerContextType>(
+  {} as any
+);
path: packages/frontend/core/src/components/explorer/context.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Empty default context may lead to runtime `undefined` access**

`createContext({} as any)` provides an object that lacks every `$` field.  
If a component is ever mounted outside the provider (tests, storybook, future reuse), it will crash when accessing `.value`. A safer pattern is to expose `null` and throw a descriptive error:

```diff
-export const DocExplorerContext = createContext<DocExplorerContextType>(
-  {} as any
-);
+export const DocExplorerContext = createContext<DocExplorerContextType | null>(
+  null
+);
+
+export const useDocExplorerContext = () => {
+  const ctx = useContext(DocExplorerContext);
+  if (!ctx) {
+    throw new Error('DocExplorerContext not found – did you forget the provider?');
+  }
+  return ctx;
+};
```

Then replace `useContext(DocExplorerContext)` with `useDocExplorerContext()` everywhere.  



<!-- 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
export const DocExplorerContext = createContext<DocExplorerContextType | null>(
  null
);

export const useDocExplorerContext = () => {
  const ctx = useContext(DocExplorerContext);
  if (!ctx) {
    throw new Error('DocExplorerContext not found – did you forget the provider?');
  }
  return ctx;
};
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T09:57:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-08T05:38:04Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change refactors the document explorer's state management from callback-based React state and prop drilling to a centralized, reactive context using `LiveData` observables. Components now subscribe to context observables for state and update them directly, removing the need for prop-based state updates and local state. Minor UI and style adjustments are also included.

## Changes

| Files/Groups                                                                                                 | Change Summary |
|-------------------------------------------------------------------------------------------------------------|---------------|
| `packages/frontend/core/src/components/explorer/context.ts`                                                 | Refactored `DocExplorerContextType` to use `LiveData` for all properties; removed setters; added `createDocExplorerContext` factory. |
| `packages/frontend/core/src/components/explorer/display-menu/index.tsx`,<br>`display-menu/properties.tsx`,<br>`display-menu/quick-actions.tsx` | Refactored menu and properties components to consume state from context observables via `useLiveData`; removed prop drilling for preferences and callbacks. |
| `packages/frontend/core/src/components/explorer/docs-view/doc-list-item.tsx`,<br>`docs-view/group-header.tsx`,<br>`docs-view/properties.tsx`,<br>`docs-view/quick-actions.tsx` | Shifted state management from direct context values and callbacks to `LiveData` observables; updated event handlers to push to observables. |
| `packages/frontend/core/src/components/explorer/quick-actions.constants.tsx`                                | Renamed internal interface; clarified exported types for quick actions. |
| `packages/frontend/core/src/components/workspace-property-types/created-updated-by.tsx`,<br>`workspace-property-types/index.ts` | UI: Added `showName={false}` to user labels; added `groupHeader` to `updatedAt` property type. |
| `packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx`,<br>`all-page.tsx`        | Refactored header and page to use context observables for all state; removed local state and prop-based preference management. |
| `packages/frontend/core/src/modules/cloud/views/public-user.css.ts`,<br>`public-user.tsx`                   | CSS: Scoped avatar margin to `data-show-name="true"`; added corresponding attribute in component. |
| `packages/frontend/core/src/modules/doc/stores/docs.ts`                                                     | Improved safety in extracting `tags` from Yjs arrays, handling multiple possible types. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
  participant AllPage
  participant DocExplorerContext
  participant DisplayMenu
  participant DocListItem

  AllPage->>DocExplorerContext: createDocExplorerContext()
  AllPage->>DocExplorerContext: Provide context to children
  DisplayMenu->>DocExplorerContext: useContext + useLiveData (subscribe to preferences)
  DisplayMenu->>DocExplorerContext: Update preference via observable.next()
  DocListItem->>DocExplorerContext: useContext + useLiveData (subscribe to selection state)
  DocListItem->>DocExplorerContext: Update selection via observable.next()
```

## Assessment against linked issues

| Objective (Issue)                                | Addressed | Explanation |
|--------------------------------------------------|-----------|-------------|
| Optimize performance to reduce filter re-render lag (AF-2605) | ✅        |             |

## Suggested labels

`mod:component`

## Suggested reviewers

- CatsJuice

## Poem

> In the land of code, a rabbit hops,  
> Refactoring state, prop drilling stops.  
> Context now streams, with data alive,  
> Components subscribe, and swiftly derive.  
> No more lag when filters change hue,  
> Reactive and clean—cheers to the crew! 🐇✨

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


<!-- 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 -->

<!-- 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=12188):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `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 generate unit testing code for this file.`
  -	`@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 generate unit testing code.`
  - `@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-08T05:38:11Z
body: * **#12188** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12188?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/12188?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#12182** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12182?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12102** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12102?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#11958** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/11958?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=12188).

<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-08T05:43:52Z
body: ## [Codecov](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12188?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) Report
Attention: Patch coverage is `0%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 54.13%. Comparing base [(`76b4da5`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/76b4da54b764cde5e251298755ac785f574afdf5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) to head [(`6650066`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/66500669c862c3aaa30de70d0e7ddb9955b60cb9?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.

| [Files with missing lines](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12188?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | Patch % | Lines |
|---|---|---|
| [...kages/frontend/core/src/modules/doc/stores/docs.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12188?src=pr&el=tree&filepath=packages%2Ffrontend%2Fcore%2Fsrc%2Fmodules%2Fdoc%2Fstores%2Fdocs.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-cGFja2FnZXMvZnJvbnRlbmQvY29yZS9zcmMvbW9kdWxlcy9kb2Mvc3RvcmVzL2RvY3MudHM=) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12188?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |

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


```diff
@@            Coverage Diff             @@
##           canary   #12188      +/-   ##
==========================================
+ Coverage   53.82%   54.13%   +0.30%     
==========================================
  Files        2667     2667              
  Lines      119390   119393       +3     
  Branches    19309    19389      +80     
==========================================
+ Hits        64261    64631     +370     
+ Misses      53569    52678     -891     
- Partials     1560     2084     +524     
```

| [Flag](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12188/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/12188/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `78.37% <ø> (+0.81%)` | :arrow_up: |
| [unittest](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12188/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `30.92% <0.00%> (-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/12188?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-10T07:00:55Z
body: ### Merge activity

* **May 10, 3:00 AM EDT**: `CatsJuice` added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).
* **May 10, 3:15 AM EDT**: Merged by the [Graphite merge queue](https://app.graphite.dev/merges?org=toeverything&repo=AFFiNE).

author_association: CONTRIBUTOR