Repository: toeverything/AFFiNE
Pull request #12180: feat(editor): improve status display in attachment embed view
Author: login: fundon
Labels: name: mod:component
name: test
description: Related to test cases
name: app:core
Created: 2025-05-08T00:04:15Z | Merged: 2025-05-10T08:47:41Z

Closes: [BS-3438](https://linear.app/affine-design/issue/BS-3438/attachment-embed-view-中的-status-组件)
Closes: [BS-3447](https://linear.app/affine-design/issue/BS-3447/触发-litportal-re-render)

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

- **New Features**
  - Introduced a visual status indicator for embedded attachments with reload capability.
  - Added a new resource status component to display error messages and reload actions.
- **Improvements**
  - Enhanced attachment rendering flow with reactive state and unified embed handling.
  - Simplified resource state and blob URL lifecycle management.
  - Added status visibility flags for PDF and video embeds.
- **Bug Fixes**
  - Improved error handling and refresh support for embedded content including PDFs, videos, and audio.
- **Style**
  - Added styles for the attachment embed status indicator positioning.
- **Refactor**
  - Streamlined attachment and resource controller implementations for better maintainability.
- **Tests**
  - Added end-to-end test verifying PDF viewer reload and re-rendering in embed mode.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/blocksuite/affine/blocks/attachment/src/attachment-block.ts b/blocksuite/affine/blocks/attachment/src/attachment-block.ts
index 8b2bde40ee1b7..382a87daa8e32 100644
--- a/blocksuite/affine/blocks/attachment/src/attachment-block.ts
+++ b/blocksuite/affine/blocks/attachment/src/attachment-block.ts
@@ -28,11 +28,12 @@ import {
   WarningIcon,
 } from '@blocksuite/icons/lit';
 import { BlockSelection } from '@blocksuite/std';
-import { Slice } from '@blocksuite/store';
-import { computed } from '@preact/signals-core';
+import { nanoid, Slice } from '@blocksuite/store';
+import { computed, signal } from '@preact/signals-core';
 import { html, type TemplateResult } from 'lit';
 import { choose } from 'lit/directives/choose.js';
 import { type ClassInfo, classMap } from 'lit/directives/class-map.js';
+import { guard } from 'lit/directives/guard.js';
 import { styleMap } from 'lit/directives/style-map.js';
 import { when } from 'lit/directives/when.js';
 
@@ -40,6 +41,10 @@ import { AttachmentEmbedProvider } from './embed';
 import { styles } from './styles';
 import { downloadAttachmentBlob, refreshData } from './utils';
 
+type AttachmentResolvedStateInfo = ResolvedStateInfo & {
+  kind?: TemplateResult;
+};
+
 @Peekable({
   enableOn: ({ model }: AttachmentBlockComponent) => {
     return !model.store.readonly && model.props.type.endsWith('pdf');
@@ -103,15 +108,22 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
     window.open(blobUrl, '_blank');
   };
 
+  // Refreshes data.
   refreshData = () => {
     refreshData(this).catch(console.error);
   };
 
-  protected get embedView() {
-    return this.std
-      .get(AttachmentEmbedProvider)
-      .render(this.model, this.blobUrl ?? undefined, this._maxFileSize);
-  }
+  private readonly _refreshKey$ = signal<string | null>(null);
+
+  // Refreshes the embed component.
+  reload = () => {
+    if (this.model.props.embed) {
+      this._refreshKey$.value = nanoid();
+      return;
+    }
+
+    this.refreshData();
+  };
 
   private _selectBlock() {
     const selectionManager = this.host.selection;
@@ -195,68 +207,70 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
 
   protected renderWithHorizontal(
     classInfo: ClassInfo,
-    { icon, title, description, state }: ResolvedStateInfo,
-    kind: TemplateResult
+    { icon, title, description, kind, state }: AttachmentResolvedStateInfo
   ) {
-    return html`<div class=${classMap(classInfo)}>
-      <div class="affine-attachment-content">
-        <div class="affine-attachment-content-title">
-          <div class="affine-attachment-content-title-icon">${icon}</div>
-          <div class="affine-attachment-content-title-text truncate">
-            ${title}
+    return html`
+      <div class=${classMap(classInfo)}>
+        <div class="affine-attachment-content">
+          <div class="affine-attachment-content-title">
+            <div class="affine-attachment-content-title-icon">${icon}</div>
+            <div class="affine-attachment-content-title-text truncate">
+              ${title}
+            </div>
           </div>
-        </div>
 
-        <div class="affine-attachment-content-description">
-          <div class="affine-attachment-content-info truncate">
-            ${description}
+          <div class="affine-attachment-content-description">
+            <div class="affine-attachment-content-info truncate">
+              ${description}
+            </div>
+            ${choose(state, [
+              ['error', this.renderReloadButton],
+              ['error:oversize', this.renderUpgradeButton],
+            ])}
           </div>
-          ${choose(state, [
-            ['error', this.renderReloadButton],
-            ['error:oversize', this.renderUpgradeButton],
-          ])}
         </div>
-      </div>
 
-      <div class="affine-attachment-banner">${kind}</div>
-    </div>`;
+        <div class="affine-attachment-banner">${kind}</div>
+      </div>
+    `;
   }
 
   protected renderWithVertical(
     classInfo: ClassInfo,
-    { icon, title, description, state }: ResolvedStateInfo,
-    kind: TemplateResult
+    { icon, title, description, kind, state }: AttachmentResolvedStateInfo
   ) {
-    return html`<div class=${classMap(classInfo)}>
-      <div class="affine-attachment-content">
-        <div class="affine-attachment-content-title">
-          <div class="affine-attachment-content-title-icon">${icon}</div>
-          <div class="affine-attachment-content-title-text truncate">
-            ${title}
+    return html`
+      <div class=${classMap(classInfo)}>
+        <div class="affine-attachment-content">
+          <div class="affine-attachment-content-title">
+            <div class="affine-attachment-content-title-icon">${icon}</div>
+            <div class="affine-attachment-content-title-text truncate">
+              ${title}
+            </div>
           </div>
-        </div>
 
-        <div class="affine-attachment-content-info truncate">
-          ${description}
+          <div class="affine-attachment-content-info truncate">
+            ${description}
+          </div>
         </div>
-      </div>
 
-      <div class="affine-attachment-banner">
-        ${kind}
-        ${choose(state, [
-          ['error', this.renderReloadButton],
-          ['error:oversize', this.renderUpgradeButton],
-        ])}
+        <div class="affine-attachment-banner">
+          ${kind}
+          ${choose(state, [
+            ['error', this.renderReloadButton],
+            ['error:oversize', this.renderUpgradeButton],
+          ])}
+        </div>
       </div>
-    </div>`;
+    `;
   }
 
-  protected renderCard = () => {
+  protected resolvedState$ = computed<AttachmentResolvedStateInfo>(() => {
     const theme = this.std.get(ThemeProvider).theme$.value;
     const loadingIcon = getLoadingIconWith(theme);
 
-    const { name, size, style } = this.model.props;
-    const cardStyle = style ?? AttachmentBlockStyles[1];
+    const size = this.model.props.size;
+    const name = this.model.props.name$.value;
     const kind = getAttachmentFileIcon(name.split('.').pop() ?? '');
 
     const resolvedState = this.resourceController.resolveStateWith({
@@ -267,6 +281,13 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
       description: humanFileSize(size),
     });
 
+    return { ...resolvedState, kind };
+  });
+
+  protected renderCardView = () => {
+    const resolvedState = this.resolvedState$.value;
+    const cardStyle = this.model.props.style$.value ?? AttachmentBlockStyles[1];
+
     const classInfo = {
       'affine-attachment-card': true,
       [cardStyle]: true,
@@ -276,11 +297,45 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
 
     return when(
       cardStyle === 'cubeThick',
-      () => this.renderWithVertical(classInfo, resolvedState, kind),
-      () => this.renderWithHorizontal(classInfo, resolvedState, kind)
+      () => this.renderWithVertical(classInfo, resolvedState),
+      () => this.renderWithHorizontal(classInfo, resolvedState)
     );
   };
 
+  protected renderEmbedView = () => {
+    const { model, blobUrl } = this;
+    if (!model.props.embed || !blobUrl) return null;
+
+    const { std, _maxFileSize } = this;
+    const provider = std.get(AttachmentEmbedProvider);
+
+    const render = provider.getRender(model, _maxFileSize);
+    if (!render) return null;
+
+    const enabled = provider.shouldShowStatus(model);
+
+    return html`
+      <div class="affine-attachment-embed-container">
+        ${guard([this._refreshKey$.value], () => render(model, blobUrl))}
+      </div>
+      ${when(enabled, () => {
+        const resolvedState = this.resolvedState$.value;
+        if (resolvedState.state !== 'error') return null;
+        // It should be an error messge.
+        const message = resolvedState.description;
+        if (!message) return null;
+
+        return html`
+          <affine-resource-status
+            class="affine-attachment-embed-status"
+            .message=${message}
+            .reload=${() => this.reload()}
+          ></affine-resource-status>
+        `;
+      })}
+    `;
+  };
+
   private readonly _renderCitation = () => {
     const { name, footnoteIdentifier } = this.model.props;
     const fileType = name.split('.').pop() ?? '';
@@ -305,15 +360,7 @@ export class AttachmentBlockComponent extends CaptionedBlockComponent<Attachment
         ${when(
           this.isCitation,
           () => this._renderCitation(),
-          () =>
-            when(
-              this.embedView,
-              () =>
-                html`<div class="affine-attachment-embed-container">
-                  ${this.embedView}
-                </div>`,
-              this.renderCard
-            )
+          () => this.renderEmbedView() ?? this.renderCardView()
         )}
       </div>
     `;
diff --git a/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts b/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
index 254d880cd5b01..58f5d48fd4a44 100644
--- a/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
+++ b/blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
@@ -77,13 +77,19 @@ export const attachmentViewDropdownMenu = {
         const model = ctx.getCurrentModelByType(AttachmentBlockModel);
         if (!model) return;
 
-        if (!ctx.hasSelectedSurfaceModels) {
+        const provider = ctx.std.get(AttachmentEmbedProvider);
+
+        // TODO(@fundon): should auto focus image block.
+        if (
+          provider.shouldBeConverted(model) &&
+          !ctx.hasSelectedSurfaceModels
+        ) {
           // Clears
           ctx.reset();
           ctx.select('note');
         }
 
-        ctx.std.get(AttachmentEmbedProvider).convertTo(model);
+        provider.convertTo(model);
 
         ctx.track('SelectedView', {
           ...trackBaseProps,
@@ -257,7 +263,7 @@ const builtinToolbarConfig = {
       icon: ResetIcon(),
       run(ctx) {
         const block = ctx.getCurrentBlockByType(AttachmentBlockComponent);
-        block?.refreshData();
+        block?.reload();
       },
     },
     {
diff --git a/blocksuite/affine/blocks/attachment/src/embed.ts b/blocksuite/affine/blocks/attachment/src/embed.ts
index f2f91fcecbaa6..fbe44c1c1f692 100644
--- a/blocksuite/affine/blocks/attachment/src/embed.ts
+++ b/blocksuite/affine/blocks/attachment/src/embed.ts
@@ -39,9 +39,22 @@ export type AttachmentEmbedConfig = {
     std: BlockStdScope
   ) => Promise<void> | void;
   /**
-   * The template will be used to render the embed view.
+   * Renders the embed view.
    */
-  template?: (model: AttachmentBlockModel, blobUrl: string) => TemplateResult;
+  render?: (
+    model: AttachmentBlockModel,
+    blobUrl: string
+  ) => TemplateResult | null;
+
+  /**
+   * Should show status when turned on.
+   */
+  shouldShowStatus?: boolean;
+
+  /**
+   * Should block type conversion be required.
+   */
+  shouldBeConverted?: boolean;
 };
 
 // Single embed config.
@@ -115,26 +128,38 @@ export class AttachmentEmbedService extends Extension {
     return this.values.some(config => config.check(model, maxFileSize));
   }
 
-  render(
+  getRender(model: AttachmentBlockModel, maxFileSize = this._maxFileSize) {
+    return (
+      this.values.find(config => config.check(model, maxFileSize))?.render ??
+      null
+    );
+  }
+
+  shouldShowStatus(
     model: AttachmentBlockModel,
-    blobUrl?: string,
     maxFileSize = this._maxFileSize
   ) {
-    if (!model.props.embed || !blobUrl) return;
-
-    const config = this.values.find(config => config.check(model, maxFileSize));
-    if (!config || !config.template) {
-      console.error('No embed view template found!', model, model.props.type);
-      return;
-    }
+    return (
+      this.values.find(config => config.check(model, maxFileSize))
+        ?.shouldShowStatus ?? false
+    );
+  }
 
-    return config.template(model, blobUrl);
+  shouldBeConverted(
+    model: AttachmentBlockModel,
+    maxFileSize = this._maxFileSize
+  ) {
+    return (
+      this.values.find(config => config.check(model, maxFileSize))
+        ?.shouldBeConverted ?? false
+    );
   }
 }
 
 const embedConfig: AttachmentEmbedConfig[] = [
   {
     name: 'image',
+    shouldBeConverted: true,
     check: model =>
       model.store.schema.flavourSchemaMap.has('affine:image') &&
       model.props.type.startsWith('image/'),
@@ -147,6 +172,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
   },
   {
     name: 'pdf',
+    shouldShowStatus: true,
     check: (model, maxFileSize) =>
       model.props.type === 'application/pdf' && model.props.size <= maxFileSize,
     action: model => {
@@ -159,7 +185,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
         xywh: bound.serialize(),
       });
     },
-    template: (_, blobUrl) => {
+    render: (_, blobUrl) => {
       // More options: https://tinytip.co/tips/html-pdf-params/
       // https://chromium.googlesource.com/chromium/src/+/refs/tags/121.0.6153.1/chrome/browser/resources/pdf/open_pdf_params_parser.ts
       const parameters = '#toolbar=0';
@@ -185,6 +211,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
   },
   {
     name: 'video',
+    shouldShowStatus: true,
     check: (model, maxFileSize) =>
       model.props.type.startsWith('video/') && model.props.size <= maxFileSize,
     action: model => {
@@ -197,7 +224,7 @@ const embedConfig: AttachmentEmbedConfig[] = [
         xywh: bound.serialize(),
       });
     },
-    template: (_, blobUrl) =>
+    render: (_, blobUrl) =>
       html`<video
         style=${styleMap({
           display: 'flex',
@@ -216,8 +243,12 @@ const embedConfig: AttachmentEmbedConfig[] = [
     name: 'audio',
     check: (model, maxFileSize) =>
       model.props.type.startsWith('audio/') && model.props.size <= maxFileSize,
-    template: (_, blobUrl) =>
-      html`<audio controls src=${blobUrl} style="margin: 4px;"></audio>`,
+    render: (_, blobUrl) =>
+      html`<audio
+        style=${styleMap({ margin: '4px' })}
+        src=${blobUrl}
+        controls
+      ></audio>`,
   },
 ];
 
diff --git a/blocksuite/affine/blocks/attachment/src/styles.ts b/blocksuite/affine/blocks/attachment/src/styles.ts
index 37bbe7e71cdde..7d30bb6f61309 100644
--- a/blocksuite/affine/blocks/attachment/src/styles.ts
+++ b/blocksuite/affine/blocks/attachment/src/styles.ts
@@ -143,6 +143,12 @@ export const styles = css`
     height: 100%;
   }
 
+  .affine-attachment-embed-status {
+    position: absolute;
+    left: 14px;
+    bottom: 64px;
+  }
+
   .affine-attachment-embed-event-mask {
     position: absolute;
     inset: 0;
diff --git a/blocksuite/affine/components/src/resource/index.ts b/blocksuite/affine/components/src/resource/index.ts
index 4c9c41406ebce..2ae6fbaa5edba 100644
--- a/blocksuite/affine/components/src/resource/index.ts
+++ b/blocksuite/affine/components/src/resource/index.ts
@@ -1,193 +1,8 @@
-import type { Disposable } from '@blocksuite/global/disposable';
-import type { BlobEngine, BlobState } from '@blocksuite/sync';
-import {
-  computed,
-  effect,
-  type ReadonlySignal,
-  signal,
-} from '@preact/signals-core';
-import type { TemplateResult } from 'lit-html';
+import { ResourceStatus } from './status';
 
-export type ResourceKind = 'Blob' | 'File' | 'Image';
+export * from './resource';
+export * from './status';
 
-export type StateKind =
-  | 'loading'
-  | 'uploading'
-  | 'error'
-  | 'error:oversize'
-  | 'none';
-
-export type StateInfo = {
-  icon: TemplateResult;
-  title?: string;
-  description?: string;
-};
-
-export type ResolvedStateInfoPart = {
-  loading: boolean;
-  error: boolean;
-  state: StateKind;
-};
-
-export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
-
-export class ResourceController implements Disposable {
-  readonly blobUrl$ = signal<string | null>(null);
-
-  readonly state$ = signal<Partial<BlobState>>({});
-
-  readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
-    const {
-      uploading = false,
-      downloading = false,
-      overSize = false,
-      errorMessage,
-    } = this.state$.value;
-    const hasExceeded = overSize;
-    const hasError = hasExceeded || Boolean(errorMessage);
-    const state = this.determineState(
-      hasExceeded,
-      hasError,
-      uploading,
-      downloading
-    );
-
-    const loading = state === 'uploading' || state === 'loading';
-
-    return { error: hasError, loading, state };
-  });
-
-  private engine?: BlobEngine;
-
-  constructor(
-    readonly blobId$: ReadonlySignal<string | undefined>,
-    readonly kind: ResourceKind = 'File'
-  ) {}
-
-  // This is a tradeoff, initializing `Blob Sync Engine`.
-  setEngine(engine: BlobEngine) {
-    this.engine = engine;
-    return this;
-  }
-
-  determineState(
-    hasExceeded: boolean,
-    hasError: boolean,
-    uploading: boolean,
-    downloading: boolean
-  ): StateKind {
-    if (hasExceeded) return 'error:oversize';
-    if (hasError) return 'error';
-    if (uploading) return 'uploading';
-    if (downloading) return 'loading';
-    return 'none';
-  }
-
-  resolveStateWith(
-    info: {
-      loadingIcon: TemplateResult;
-      errorIcon?: TemplateResult;
-    } & StateInfo
-  ): ResolvedStateInfo {
-    const { error, loading, state } = this.resolvedState$.value;
-
-    const { icon, title, description, loadingIcon, errorIcon } = info;
-
-    const result = {
-      error,
-      loading,
-      state,
-      icon,
-      title,
-      description,
-    };
-
-    if (loading) {
-      result.icon = loadingIcon ?? icon;
-      result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
-    } else if (error) {
-      result.icon = errorIcon ?? icon;
-      result.description = this.state$.value.errorMessage ?? description;
-    }
-
-    return result;
-  }
-
-  updateState(state: Partial<BlobState>) {
-    this.state$.value = { ...this.state$.value, ...state };
-  }
-
-  subscribe() {
-    return effect(() => {
-      const blobId = this.blobId$.value;
-      if (!blobId) return;
-
-      const blobState$ = this.engine?.blobState$(blobId);
-      if (!blobState$) return;
-
-      const subscription = blobState$.subscribe(state => {
-        let { uploading, downloading } = state;
-        if (state.overSize || state.errorMessage) {
-          uploading = false;
-          downloading = false;
-        }
-
-        this.updateState({ ...state, uploading, downloading });
-      });
-
-      return () => subscription.unsubscribe();
-    });
-  }
-
-  async blob() {
-    const blobId = this.blobId$.peek();
-    if (!blobId) return null;
-
-    let blob: Blob | null = null;
-    let errorMessage: string | null = null;
-
-    try {
-      blob = (await this.engine?.get(blobId)) ?? null;
-
-      if (!blob) errorMessage = `${this.kind} not found`;
-    } catch (err) {
-      console.error(err);
-      errorMessage = `Failed to retrieve ${this.kind}`;
-    }
-
-    if (errorMessage) this.updateState({ errorMessage });
-
-    return blob;
-  }
-
-  async createUrlWith(type?: string) {
-    let blob = await this.blob();
-    if (!blob) return null;
-
-    if (type) blob = new Blob([blob], { type });
-
-    return URL.createObjectURL(blob);
-  }
-
-  async refreshUrlWith(type?: string) {
-    const url = await this.createUrlWith(type);
-    if (!url) return;
-
-    const prevUrl = this.blobUrl$.peek();
-
-    this.blobUrl$.value = url;
-
-    if (!prevUrl) return;
-
-    // Releases the previous url.
-    URL.revokeObjectURL(prevUrl);
-  }
-
-  dispose() {
-    const url = this.blobUrl$.peek();
-    if (!url) return;
-
-    // Releases the current url.
-    URL.revokeObjectURL(url);
-  }
+export function effects() {
+  customElements.define('affine-resource-status', ResourceStatus);
 }
diff --git a/blocksuite/affine/components/src/resource/resource.ts b/blocksuite/affine/components/src/resource/resource.ts
new file mode 100644
index 0000000000000..463e9ed1293c4
--- /dev/null
+++ b/blocksuite/affine/components/src/resource/resource.ts
@@ -0,0 +1,200 @@
+import type { Disposable } from '@blocksuite/global/disposable';
+import type { BlobEngine, BlobState } from '@blocksuite/sync';
+import {
+  computed,
+  effect,
+  type ReadonlySignal,
+  signal,
+} from '@preact/signals-core';
+import type { TemplateResult } from 'lit-html';
+
+export type ResourceKind = 'Blob' | 'File' | 'Image';
+
+export type StateKind =
+  | 'loading'
+  | 'uploading'
+  | 'error'
+  | 'error:oversize'
+  | 'none';
+
+export type StateInfo = {
+  icon: TemplateResult;
+  title?: string;
+  description?: string;
+};
+
+export type ResolvedStateInfoPart = {
+  loading: boolean;
+  error: boolean;
+  state: StateKind;
+  url: string | null;
+};
+
+export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
+
+export class ResourceController implements Disposable {
+  readonly blobUrl$ = signal<string | null>(null);
+
+  readonly state$ = signal<Partial<BlobState>>({});
+
+  readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
+    const url = this.blobUrl$.value;
+    const {
+      uploading = false,
+      downloading = false,
+      overSize = false,
+      errorMessage,
+    } = this.state$.value;
+    const hasExceeded = overSize;
+    const hasError = hasExceeded || Boolean(errorMessage);
+    const state = this.determineState(
+      hasExceeded,
+      hasError,
+      uploading,
+      downloading
+    );
+
+    const loading = state === 'uploading' || state === 'loading';
+
+    return { error: hasError, loading, state, url };
+  });
+
+  private engine?: BlobEngine;
+
+  constructor(
+    readonly blobId$: ReadonlySignal<string | undefined>,
+    readonly kind: ResourceKind = 'File'
+  ) {}
+
+  // This is a tradeoff, initializing `Blob Sync Engine`.
+  setEngine(engine: BlobEngine) {
+    this.engine = engine;
+    return this;
+  }
+
+  determineState(
+    hasExceeded: boolean,
+    hasError: boolean,
+    uploading: boolean,
+    downloading: boolean
+  ): StateKind {
+    if (hasExceeded) return 'error:oversize';
+    if (hasError) return 'error';
+    if (uploading) return 'uploading';
+    if (downloading) return 'loading';
+    return 'none';
+  }
+
+  resolveStateWith(
+    info: {
+      loadingIcon: TemplateResult;
+      errorIcon?: TemplateResult;
+    } & StateInfo
+  ): ResolvedStateInfo {
+    const { error, loading, state, url } = this.resolvedState$.value;
+
+    const { icon, title, description, loadingIcon, errorIcon } = info;
+
+    const result = {
+      error,
+      loading,
+      state,
+      icon,
+      title,
+      description,
+      url,
+    };
+
+    if (loading) {
+      result.icon = loadingIcon ?? icon;
+      result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
+    } else if (error) {
+      result.icon = errorIcon ?? icon;
+      result.description = this.state$.value.errorMessage ?? description;
+    }
+
+    return result;
+  }
+
+  updateState(state: Partial<BlobState>) {
+    this.state$.value = { ...this.state$.value, ...state };
+  }
+
+  subscribe() {
+    return effect(() => {
+      const blobId = this.blobId$.value;
+      if (!blobId) return;
+
+      const blobState$ = this.engine?.blobState$(blobId);
+      if (!blobState$) return;
+
+      const subscription = blobState$.subscribe(state => {
+        let { uploading, downloading } = state;
+        if (state.overSize || state.errorMessage) {
+          uploading = false;
+          downloading = false;
+        }
+
+        this.updateState({ ...state, uploading, downloading });
+      });
+
+      return () => subscription.unsubscribe();
+    });
+  }
+
+  async blob() {
+    const blobId = this.blobId$.peek();
+    if (!blobId) return null;
+
+    let blob: Blob | null = null;
+    let errorMessage: string | null = null;
+
+    try {
+      if (!this.engine) {
+        throw new Error('Blob engine is not initialized');
+      }
+
+      blob = (await this.engine.get(blobId)) ?? null;
+
+      if (!blob) errorMessage = `${this.kind} not found`;
+    } catch (err) {
+      console.error(err);
+      errorMessage = `Failed to retrieve ${this.kind}`;
+    }
+
+    if (errorMessage) this.updateState({ errorMessage });
+
+    return blob;
+  }
+
+  async createUrlWith(type?: string) {
+    let blob = await this.blob();
+    if (!blob) return null;
+
+    if (type) blob = new Blob([blob], { type });
+
+    return URL.createObjectURL(blob);
+  }
+
+  async refreshUrlWith(type?: string) {
+    const url = await this.createUrlWith(type);
+    if (!url) return;
+
+    const prevUrl = this.blobUrl$.peek();
+
+    this.blobUrl$.value = url;
+
+    if (!prevUrl) return;
+
+    // Releases the previous url.
+    URL.revokeObjectURL(prevUrl);
+  }
+
+  dispose() {
+    const url = this.blobUrl$.peek();
+    if (!url) return;
+
+    // Releases the current url.
+    URL.revokeObjectURL(url);
+  }
+}
diff --git a/blocksuite/affine/components/src/resource/status.ts b/blocksuite/affine/components/src/resource/status.ts
new file mode 100644
index 0000000000000..e6a239d637072
--- /dev/null
+++ b/blocksuite/affine/components/src/resource/status.ts
@@ -0,0 +1,141 @@
+import {
+  fontBaseStyle,
+  panelBaseColorsStyle,
+} from '@blocksuite/affine-shared/styles';
+import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
+import {
+  createButtonPopper,
+  stopPropagation,
+} from '@blocksuite/affine-shared/utils';
+import { WithDisposable } from '@blocksuite/global/lit';
+import { InformationIcon } from '@blocksuite/icons/lit';
+import { PropTypes, requiredProperties } from '@blocksuite/std';
+import { css, html, LitElement } from 'lit';
+import { property, query } from 'lit/decorators.js';
+
+@requiredProperties({
+  message: PropTypes.string,
+  reload: PropTypes.instanceOf(Function),
+})
+export class ResourceStatus extends WithDisposable(LitElement) {
+  static override styles = css`
+    button.status {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 24px;
+      height: 24px;
+      border-radius: 50%;
+      font-size: 18px;
+      outline: none;
+      border: none;
+      cursor: pointer;
+      color: ${unsafeCSSVarV2('button/pureWhiteText')};
+      background: ${unsafeCSSVarV2('status/error')};
+      box-shadow: var(--affine-overlay-shadow);
+    }
+
+    ${panelBaseColorsStyle('.popper')}
+    ${fontBaseStyle('.popper')}
+    .popper {
+      display: none;
+      outline: none;
+      padding: 8px;
+      border-radius: 8px;
+      width: 260px;
+      font-size: var(--affine-font-sm);
+      font-style: normal;
+      font-weight: 400;
+      line-height: 22px;
+
+      &[data-show] {
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+      }
+    }
+
+    .content {
+      color: ${unsafeCSSVarV2('text/primary')};
+    }
+
+    .footer {
+      display: flex;
+      justify-content: flex-end;
+    }
+
+    button.reload {
+      display: flex;
+      align-items: center;
+      padding: 2px 12px;
+      border-radius: 8px;
+      border: none;
+      background: none;
+      cursor: pointer;
+      outline: none;
+      color: ${unsafeCSSVarV2('button/primary')};
+    }
+  `;
+
+  private _popper: ReturnType<typeof createButtonPopper> | null = null;
+
+  private _updatePopper() {
+    this._popper?.dispose();
+    this._popper = createButtonPopper({
+      reference: this._trigger,
+      popperElement: this._content,
+      mainAxis: 8,
+      allowedPlacements: ['top-start', 'bottom-start'],
+    });
+  }
+
+  override firstUpdated() {
+    this._updatePopper();
+    this.disposables.addFromEvent(this, 'click', stopPropagation);
+    this.disposables.addFromEvent(this, 'keydown', (e: KeyboardEvent) => {
+      e.stopPropagation();
+      if (e.key === 'Escape') {
+        this._popper?.hide();
+      }
+    });
+    this.disposables.addFromEvent(this._trigger, 'click', (_: MouseEvent) => {
+      this._popper?.toggle();
+    });
+    this.disposables.addFromEvent(
+      this._reloadButton,
+      'click',
+      (_: MouseEvent) => {
+        this._popper?.hide();
+        this.reload();
+      }
+    );
+    this.disposables.add(() => this._popper?.dispose());
+  }
+
+  override render() {
+    return html`
+      <button class="status">${InformationIcon()}</button>
+      <div class="popper">
+        <div class="content">${this.message}</div>
+        <div class="footer">
+          <button class="reload">Reload</button>
+        </div>
+      </div>
+    `;
+  }
+
+  @query('div.popper')
+  private accessor _content!: HTMLDivElement;
+
+  @query('button.status')
+  private accessor _trigger!: HTMLButtonElement;
+
+  @query('button.reload')
+  private accessor _reloadButton!: HTMLButtonElement;
+
+  @property({ attribute: false })
+  accessor message!: string;
+
+  @property({ attribute: false })
+  accessor reload!: () => void;
+}
diff --git a/blocksuite/affine/foundation/src/effects.ts b/blocksuite/affine/foundation/src/effects.ts
index 5d5b266d71298..7f59fc42b5c03 100644
--- a/blocksuite/affine/foundation/src/effects.ts
+++ b/blocksuite/affine/foundation/src/effects.ts
@@ -18,6 +18,7 @@ import { effects as componentLinkPreviewEffects } from '@blocksuite/affine-compo
 import { effects as componentLinkedDocTitleEffects } from '@blocksuite/affine-components/linked-doc-title';
 import { effects as componentOpenDocDropdownMenuEffects } from '@blocksuite/affine-components/open-doc-dropdown-menu';
 import { effects as componentPortalEffects } from '@blocksuite/affine-components/portal';
+import { effects as componentResourceEffects } from '@blocksuite/affine-components/resource';
 import { effects as componentSizeDropdownMenuEffects } from '@blocksuite/affine-components/size-dropdown-menu';
 import { SmoothCorner } from '@blocksuite/affine-components/smooth-corner';
 import { effects as componentToggleButtonEffects } from '@blocksuite/affine-components/toggle-button';
@@ -56,6 +57,7 @@ export function effects() {
   componentEdgelessLineStylesEffects();
   componentEdgelessShapeColorPickerEffects();
   componentOpenDocDropdownMenuEffects();
+  componentResourceEffects();
 
   customElements.define('icon-button', IconButton);
   customElements.define('smooth-corner', SmoothCorner);
diff --git a/packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx b/packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
index fd525a080f43f..e0302602ece6e 100644
--- a/packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
+++ b/packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
@@ -74,7 +74,7 @@ declare global {
   }
 }
 
-export type ElementOrFactory = React.ReactElement | (() => React.ReactElement);
+export type ElementOrFactory = React.ReactNode | (() => React.ReactNode);
 
 type LitPortal = {
   id: string;
@@ -139,18 +139,16 @@ export const useLitPortalFactory = () => {
 };
 
 // render a react element to a lit template
-export const useLitPortal = (
-  elementOrFactory: React.ReactElement | (() => React.ReactElement)
-) => {
+export const useLitPortal = (elementOrFactory: ElementOrFactory) => {
   const [anchor, setAnchor] = useState<HTMLElement>();
   const template = useMemo(
     () =>
       createLitPortalAnchor(event => {
+        let anchor: HTMLElement | undefined;
         if (event.name !== 'disconnectedCallback') {
-          setAnchor(event.target as HTMLElement);
-        } else {
-          setAnchor(undefined);
+          anchor = event.target as HTMLElement;
         }
+        setAnchor(anchor);
       }),
     []
   );
diff --git a/packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx b/packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
index 2a1a1eb16ef6f..896877c86693d 100644
--- a/packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
+++ b/packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
@@ -13,6 +13,7 @@ export function patchForPDFEmbedView(reactToLit: ReactToLit): ExtensionType {
     setup: di => {
       di.override(AttachmentEmbedConfigIdentifier('pdf'), () => ({
         name: 'pdf',
+        shouldShowStatus: true,
         check: (model, maxFileSize) =>
           model.props.type === 'application/pdf' &&
           model.props.size <= maxFileSize,
@@ -26,8 +27,8 @@ export function patchForPDFEmbedView(reactToLit: ReactToLit): ExtensionType {
             xywh: bound.serialize(),
           });
         },
-        template: (model, _blobUrl) =>
-          reactToLit(<AttachmentEmbedPreview model={model} />, false),
+        render: (model, _) =>
+          reactToLit(<AttachmentEmbedPreview model={model} />, true),
       }));
     },
   };
@@ -41,7 +42,7 @@ export function patchForAudioEmbedView(reactToLit: ReactToLit): ExtensionType {
         check: (model, maxFileSize) =>
           model.props.type.startsWith('audio/') &&
           model.props.size <= maxFileSize,
-        template: (model, _blobUrl) =>
+        render: (model, _) =>
           reactToLit(<AttachmentEmbedPreview model={model} />, false),
       }));
     },
diff --git a/tests/affine-local/e2e/attachment-preview.spec.ts b/tests/affine-local/e2e/attachment-preview.spec.ts
index e68409e54d0f9..ccf529dbf521a 100644
--- a/tests/affine-local/e2e/attachment-preview.spec.ts
+++ b/tests/affine-local/e2e/attachment-preview.spec.ts
@@ -347,3 +347,42 @@ test('should enable pointer event in pdf viewer', async ({ page }) => {
 
   await expect(attachmentSelection).toBeHidden();
 });
+
+test('should re-render pdf viewer', async ({ page }) => {
+  await openHomePage(page);
+  await clickNewPageButton(page);
+  await waitForEmptyEditor(page);
+
+  const title = getBlockSuiteEditorTitle(page);
+  await title.click();
+  await page.keyboard.type('PDF preview');
+
+  await page.keyboard.press('Enter');
+
+  await importAttachment(page, 'lorem-ipsum.pdf');
+
+  const attachment = page.locator('affine-attachment');
+  await attachment.click();
+
+  const toolbar = locateToolbar(page);
+
+  // Switches to embed view
+  await toolbar.getByLabel('Switch view').click();
+  await toolbar.getByLabel('Embed view').click();
+
+  const portal = attachment.locator('lit-react-portal');
+  await expect(portal).toBeVisible();
+
+  const portalId = await portal.getAttribute('portalid');
+  expect(portalId).not.toBeNull();
+
+  const moreMenu = toolbar.getByLabel('More menu');
+  await moreMenu.click();
+
+  await toolbar.getByRole('button', { name: 'Reload' }).click();
+
+  const newPortalId = await portal.getAttribute('portalid');
+  expect(newPortalId).not.toBeNull();
+
+  expect(portalId).not.toEqual(newPortalId);
+});


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>blocksuite/affine/blocks/attachment/src/embed.ts (1)</summary><blockquote>

`172-193`: _🛠️ Refactor suggestion_

**Use modern, valid attributes and strengthen iframe security**

`frameborder` is deprecated and ignored by modern browsers; `allowfullscreen` should be lower-case boolean; providing a `title` is important for accessibility.  
Consider also adding a restrictive `sandbox` to reduce XSS risk from embedded PDFs.

```diff
-        <iframe
+        <iframe
           style=${styleMap({
             width: '100%',
             minHeight: '480px',
             colorScheme: 'auto',
           })}
           src=${blobUrl + parameters}
           loading="lazy"
-          scrolling="no"
-          frameborder="no"
-          allowTransparency
-          allowfullscreen
+          scrolling="no"
+          sandbox="allow-same-origin allow-scripts"
+          allowfullscreen
+          title="Embedded PDF preview"
           type="application/pdf"
           credentialless
         ></iframe>
```


Let me know if you’d like a quick audit of other embed types for similar issues.

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/components/src/resource/status.ts (1)</summary><blockquote>

`21-36`: **Add an accessible name to the status button**

Screen-reader users have no clue what the round red button does.  
A short `aria-label` (or `title`) fixes this at almost zero cost.

```diff
-<button class="status">${InformationIcon()}</button>
+<button
+  class="status"
+  aria-label="Show resource status"
+  title="Show resource status"
+>
+  ${InformationIcon()}
+</button>
```



Also applies to: 115-117

</blockquote></details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/embed.ts (2)</summary><blockquote>

`211-225`: **Minor media-embed improvements**

1. Add `preload="metadata"` to avoid downloading large files before user interaction.  
2. Add `playsinline` for better mobile UX.  
3. Provide a `title` for accessibility.

```diff
-      html`<video
+      html`<video
         style=${styleMap({
           display: 'flex',
           objectFit: 'cover',
           backgroundSize: 'cover',
           width: '100%',
           height: '100%',
         })}
         src=${blobUrl}
         width="100%"
         height="100%"
+        preload="metadata"
+        playsinline
+        title="Embedded video"
         controls
       ></video>`
```

---

`230-235`: **Lightweight optimisation for audio embeds**

Likewise, consider lazy-loading audio:

```diff
-      html`<audio
+      html`<audio
         style=${styleMap({ margin: '4px' })}
         src=${blobUrl}
+        preload="metadata"
         controls
       ></audio>`
```

</blockquote></details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (1)</summary><blockquote>

`251-268`: **Unify resolved state computation**

`resolvedState$` reconstructs the file-icon on every reactive update.  
A tiny memoisation (or caching the extension outside the signal) would save string splits & icon look-ups, especially when numerous attachments are present.  Not urgent, just a heads-up for future perf passes.

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (3)</summary><blockquote>

`189-195`: **`dispose()` currently only revokes the blob URL**  

Even after fixing the previous issue, `dispose()` should also reset `blobUrl$` to `null` to reflect the revoked state and avoid accidental reuse.

```diff
   dispose() {
     const url = this.blobUrl$.peek();
-    if (!url) return;
+    if (!url) return;
 
     // Releases the current url.
     URL.revokeObjectURL(url);
+
+    /* reflect that the URL is no longer valid */
+    this.blobUrl$.value = null;
   }
```

---

`88-117`: **Avoid mutating an object declared with `const`**  

`resolveStateWith` creates `const result` and mutates its fields afterwards.  
While allowed, it obscures intent and can mislead readers into believing the object is immutable.

```diff
-    const result = {
+    const base = {
       error,
       loading,
       state,
       icon,
       title,
       description,
       url,
     };
 
-    if (loading) {
-      result.icon = loadingIcon ?? icon;
-      result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
-    } else if (error) {
-      result.icon = errorIcon ?? icon;
-      result.description = this.state$.value.errorMessage ?? description;
-    }
-
-    return result;
+    if (loading) {
+      return {
+        ...base,
+        icon: loadingIcon ?? icon,
+        title: state === 'uploading' ? 'Uploading...' : 'Loading...',
+      };
+    }
+    if (error) {
+      return {
+        ...base,
+        icon: errorIcon ?? icon,
+        description: this.state$.value.errorMessage ?? description,
+      };
+    }
+    return base;
   }
```

---

`75-86`: **Minor readability nit: clarify `hasError` semantics**  

`hasError` currently includes the oversize case, yet `determineState` already handles oversize explicitly.  
Consider computing `hasError` as `Boolean(errorMessage)` to avoid double-counting and make intent clearer.

[nitpick]

</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 97aa3fc67293736a9e24d79e07a462603ff02e0e and 853b34df50f3b5332a300fec477e3b3c3f16b9ca.

</details>

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

* `blocksuite/affine/all/src/effects.ts` (2 hunks)
* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (1 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)

</details>

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

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

<details>
<summary>packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx (1)</summary><blockquote>

<details>
<summary>packages/frontend/component/src/lit-react/lit-portal/index.ts (2)</summary>

* `useLitPortal` (4-4)
* `ElementOrFactory` (2-2)

</details>

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

<details>
<summary>blocksuite/affine/foundation/src/effects.ts (1)</summary>

* `effects` (33-68)

</details>

</blockquote></details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/embed.ts (2)</summary><blockquote>

<details>
<summary>blocksuite/affine/model/src/blocks/attachment/attachment-model.ts (1)</summary>

* `AttachmentBlockModel` (106-108)

</details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (1)</summary>

* `blobUrl` (62-64)

</details>

</blockquote></details>

</details>

</details>

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

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

</details>

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

<details>
<summary>packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx (2)</summary>

`142-142`: **Good use of type alias for improved maintainability!**

Replacing the inline union type with the `ElementOrFactory` type alias enhances code readability and maintainability while keeping the exact same functionality.

---

`147-151`: **Clean refactoring of state update logic.**

This refactoring improves code quality by:
1. Consolidating the state update into a single `setAnchor` call
2. Making the conditional logic more explicit and easier to follow
3. Potentially reducing rerenders by ensuring `setAnchor` is only called once per event

The functionality remains unchanged - the anchor is still set to the event target for all events except 'disconnectedCallback'.

</details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/styles.ts (1)</summary>

`146-150`: **LGTM: Clean addition of status indicator styles**

The CSS for positioning the status component within the attachment embed container looks good. The absolute positioning with specific left and bottom offsets ensures consistent placement.

</details>
<details>
<summary>blocksuite/affine/foundation/src/effects.ts (2)</summary>

`21-21`: **LGTM: Proper import of resource effects**

Good addition of the resource component effects import.

---

`60-60`: **LGTM: Resource effects initialization**

Correctly initialized the resource component effects alongside other component effects.

</details>
<details>
<summary>blocksuite/affine/all/src/effects.ts (2)</summary>

`12-12`: **LGTM: Clean type import**

Good addition of the type import for resource component effects.

---

`35-36`: **LGTM: Type definition extension**

Properly extended the global effects type union to include resource component effects.

</details>
<details>
<summary>packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx (3)</summary>

`16-16`: **LGTM: Status visibility flag for PDF embed view**

Good addition of the `shouldShowStatus` flag for PDF embeds, enabling status display for this type of attachment.

---

`30-31`: **LGTM: Updated render method with status support**

Good refactoring to rename `template` to `render` and passing `true` as the second parameter to `reactToLit` for status rendering support.

---

`45-46`: <details>
<summary>❓ Verification inconclusive</summary>

**Consider adding status support for audio embeds**

The audio embed view has been updated to use `render` instead of `template`, but unlike the PDF embed, it doesn't have `shouldShowStatus: true` and still passes `false` to `reactToLit`. Consider whether audio embeds would also benefit from status display.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if audio embeds might need status display based on error handling patterns

# Look for error handling in audio-related code
rg "audio.*error|error.*audio" --type=ts --type=tsx
```

Length of output: 81

---

To verify whether the audio embed view includes status support, let’s first locate all “*-embed-view.tsx” components in our extensions folder and identify the audio one:

---

🏁 Script executed:

```shell
#!/bin/bash
# List all embed-view.tsx files in the repo
fd -e tsx -t f | grep embed-view.tsx
```

Length of output: 113

---

Let’s inspect the full `AttachmentEmbedPreview` implementation to see how PDF vs. audio branches handle status:

---

🏁 Script executed:

```shell
#!/bin/bash
# Dump the attachment-embed-view.tsx to review PDF vs. audio handling
cat packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
```

Length of output: 1820

---



</details>

**Consider enabling status support for audio embeds**

The audio embed view currently omits status flags—unlike the PDF patch, it neither sets `shouldShowStatus: true` nor passes `true` to `reactToLit`, so it won’t show loading spinners or error messages. Decide if audio previews should also surface load/error states. For example:

- File: `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx`
  ```diff
   export function patchForAudioEmbedView(reactToLit: ReactToLit): ExtensionType {
     return {
       setup: di => {
         di.override(AttachmentEmbedConfigIdentifier('audio'), () => ({
           name: 'audio',
           check: (model, maxFileSize) =>
             model.props.type.startsWith('audio/') &&
             model.props.size <= maxFileSize,
-          render: (model, _) =>
-            reactToLit(<AttachmentEmbedPreview model={model} />, false),
+          shouldShowStatus: true,
+          render: (model, _) =>
+            reactToLit(<AttachmentEmbedPreview model={model} />, true),
         }));
       },
     };
   }
  ```

Please review the audio embed UX requirements and verify if status indicators are desired here.

</details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (1)</summary>

`296-316`: **Double-check that the error message surfaced to users is meaningful**

`resolvedState.description` is normally used for file size, but here it becomes the error message shown inside `<affine-resource-status>`.  
If `ResourceController.resolveStateWith` does not overwrite `description` for the `'error'` state, users will see a file-size string instead of a helpful message.

Please verify that `description` carries the actual error reason or introduce a dedicated `errorMessage` field.

```ts
const message = resolvedState.description; // <- may contain "1.2 MB"
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T07:16:18Z
user: login: Saul-Mirone
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T14:51:19Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T05:43:52Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

`6-8`: _⚠️ Potential issue_

**Guard against duplicate custom-element registration**

The `effects()` function doesn't protect against duplicate registration, which could cause a `DOMException` if this function is called multiple times (e.g., when multiple bundles call global `effects()`).



Apply this fix:

```diff
 export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
 }
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`123-143`: _⚠️ Potential issue_

**Memory-leak risk: reactive effect is not cleaned up in `dispose()`**

The `subscribe()` method creates an effect and returns the disposer, but the class never retains or calls this disposer in its `dispose()` method. If the caller doesn't store and use the returned disposer, this can lead to memory leaks as the effect continues running even after the controller is disposed.


Apply this fix to prevent memory leaks:

```diff
 export class ResourceController implements Disposable {
+  /** Disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;

   // ...

   subscribe() {
-    return effect(() => {
+    // Clean up existing subscription before creating a new one
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = effect(() => {
       const blobId = this.blobId$.value;
       if (!blobId) return;

       const blobState$ = this.engine?.blobState$(blobId);
       if (!blobState$) return;

       const subscription = blobState$.subscribe(state => {
         // ...
       });

       return () => subscription.unsubscribe();
     });
+    
+    // Return disposer for completeness
+    return this.unsubscribeEffect;
   }

   // ...

   dispose() {
+    // Stop the reactive effect first
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = undefined;
     
     const url = this.blobUrl$.peek();
     if (!url) return;

     // Releases the current url.
     URL.revokeObjectURL(url);
   }
```

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (1)</summary><blockquote>

`119-126`: **Add null check for model in reload method**

While the reload implementation is good, it's accessing model properties without first checking if the model exists.


Consider adding a null check:

```diff
 reload = () => {
+  if (!this.model) return;
+
   if (this.model.props.embed) {
     this._refreshKey$.value = nanoid();
     return;
   }

   this.refreshData();
 };
```

</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 cc743c404fbf2af79accda11e920339c03b36802 and 9b24755217e1d070bd8d0588757a034642ec012a.

</details>

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

* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (6 hunks)
* `blocksuite/affine/blocks/attachment/src/configs/toolbar.ts` (1 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (1 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* blocksuite/affine/foundation/src/effects.ts

</details>

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

* blocksuite/affine/blocks/attachment/src/styles.ts
* blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
* packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
* blocksuite/affine/components/src/resource/status.ts
* blocksuite/affine/blocks/attachment/src/embed.ts
* packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx

</details>

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

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

<details>
<summary>blocksuite/affine/foundation/src/effects.ts (1)</summary>

* `effects` (33-68)

</details>

</blockquote></details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (5)</summary><blockquote>

<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary>

* `ResolvedStateInfo` (33-33)

</details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/utils.ts (1)</summary>

* `refreshData` (71-76)

</details>
<details>
<summary>blocksuite/affine/shared/src/services/theme-service.ts (1)</summary>

* `ThemeProvider` (28-30)

</details>
<details>
<summary>blocksuite/affine/components/src/icons/loading.ts (1)</summary>

* `getLoadingIconWith` (38-39)

</details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/embed.ts (2)</summary>

* `AttachmentEmbedProvider` (78-80)
* `_maxFileSize` (83-85)

</details>

</blockquote></details>

</details>

</details>

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

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

</details>

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

<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (2)</summary>

`35-67`: **ResourceController implementation looks well-structured**

The new controller effectively manages resource states using reactive signals and provides clear methods for state determination and resolution. The use of computed signals for derived state helps maintain reactivity throughout the component lifecycle.

---

`145-168`: **Proper handling when BlobEngine is absent**

Good implementation of explicit error handling when the blob engine is not initialized. This ensures transparent error messaging rather than silently treating missing engines as "not found".

</details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (3)</summary>

`268-285`: **Well-designed reactive state management**

The computed signal elegantly extends the resource state with UI-specific details, providing a single source of truth for the component's rendering logic. This promotes cleaner code by centralizing state management.

---

`305-337`: **Improved embed view with proper error handling**

The new `renderEmbedView` method effectively handles embedded content rendering with:
1. Proper conditional rendering based on embed flag and blob URL
2. Optimized rendering using the `guard` directive keyed by the refresh signal
3. Appropriate error handling with a status component for error states
4. Clean reload functionality

This significantly improves the user experience when dealing with attachment embeds.

---

`44-46`: **Type extension follows good practices**

The `AttachmentResolvedStateInfo` type properly extends the base `ResolvedStateInfo` type with an additional `kind` property, following TypeScript's type extension best practices.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T05:46:17Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

`6-8`: **Guard against duplicate custom-element registration**

`customElements.define` throws a `DOMException` if the element has already been registered.  
`effects()` can be executed more than once (e.g., when multiple bundles call global `effects()`), which would currently crash the app.


```diff
export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
}
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`123-143`: _⚠️ Potential issue_

**Memory-leak risk: reactive `effect` is never torn down by `dispose()`**

The `subscribe()` method creates an effect and returns the disposer, but the class never retains or calls this disposer inside `dispose()`. If the caller forgets to keep the return value, the effect keeps running even after the controller is disposed.


```diff
+  /** disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;

   subscribe() {
+    // Clean up an existing subscription before creating a new one
+    this.unsubscribeEffect?.();
-    return effect(() => {
+    this.unsubscribeEffect = effect(() => {
       const blobId = this.blobId$.value;
       if (!blobId) return;

       const blobState$ = this.engine?.blobState$(blobId);
       if (!blobState$) return;

       const subscription = blobState$.subscribe(state => {
         let { uploading, downloading } = state;
         if (state.overSize || state.errorMessage) {
           uploading = false;
           downloading = false;
         }

         this.updateState({ ...state, uploading, downloading });
       });

       return () => subscription.unsubscribe();
     });
+    
+    // Return disposer for completeness
+    return this.unsubscribeEffect;
   }
```

Then update the `dispose()` method to clean up the effect:

```diff
   dispose() {
+    // Stop the reactive effect first
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = undefined;
+
     const url = this.blobUrl$.peek();
     if (!url) return;

     // Releases the current url.
     URL.revokeObjectURL(url);
   }
```

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`145-168`: **Graceful handling when `BlobEngine` is absent**

The `blob()` method correctly throws an error when the engine is not initialized, but the pattern isn't consistent with how other error cases are handled.


Consider refactoring to handle all error cases consistently:

```diff
  async blob() {
    const blobId = this.blobId$.peek();
    if (!blobId) return null;

    let blob: Blob | null = null;
    let errorMessage: string | null = null;

    try {
      if (!this.engine) {
-        throw new Error('Blob engine is not initialized');
+        errorMessage = 'Blob engine is not initialized';
+        return null;
      }

      blob = (await this.engine.get(blobId)) ?? null;

      if (!blob) errorMessage = `${this.kind} not found`;
    } catch (err) {
      console.error(err);
      errorMessage = `Failed to retrieve ${this.kind}`;
    }

    if (errorMessage) this.updateState({ errorMessage });

    return blob;
  }
```

This approach is more consistent by handling all error conditions in the same way - updating the error message and returning null.

</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 9b24755217e1d070bd8d0588757a034642ec012a and c040fec78ab6c0a8707163899a3d422d65f70fec.

</details>

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

* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (6 hunks)
* `blocksuite/affine/blocks/attachment/src/configs/toolbar.ts` (1 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (1 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)
* `tests/affine-local/e2e/attachment-preview.spec.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* blocksuite/affine/foundation/src/effects.ts

</details>

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

* blocksuite/affine/blocks/attachment/src/styles.ts
* blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
* packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
* blocksuite/affine/components/src/resource/status.ts
* packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
* blocksuite/affine/blocks/attachment/src/embed.ts

</details>

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

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

<details>
<summary>tests/affine-local/e2e/attachment-preview.spec.ts (2)</summary><blockquote>

<details>
<summary>tests/kit/src/utils/page-logic.ts (3)</summary>

* `clickNewPageButton` (39-48)
* `waitForEmptyEditor` (50-52)
* `getBlockSuiteEditorTitle` (54-56)

</details>
<details>
<summary>tests/kit/src/utils/editor.ts (1)</summary>

* `locateToolbar` (83-87)

</details>

</blockquote></details>

</details>

</details>

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

<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (4)</summary>

`119-126`: **LGTM! Well-designed refresh mechanism**

The implementation smartly differentiates between refreshing the embed component (using a refresh key) and refreshing the data, providing an efficient way to handle different refresh scenarios.

---

`268-285`: **Good reactive pattern for resource state management**

The computed signal effectively combines resource state with file-specific information, creating a unified state object that can be used consistently throughout the component.

---

`305-337`: **Well-implemented error handling with status display**

The embed view implementation correctly uses the guard directive for performance and includes conditional rendering of the status component based on error state. The reload callback is properly wired to the component's reload method.

---

`351-367`: **LGTM! Clean and logical rendering priority**

The render logic now correctly prioritizes the embed view when available and falls back to the card view, which is more maintainable and follows a clear visual hierarchy.

</details>

</blockquote></details>

</details>

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

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

`6-8`: **Guard against duplicate custom-element registration**

`customElements.define` throws a `DOMException` if the element has already been registered.  
`effects()` can be executed more than once (e.g., when multiple bundles call global `effects()`), which would currently crash the app.

```diff
export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
}
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`123-143`: _⚠️ Potential issue_

**Memory-leak risk: reactive `effect` is never torn down by `dispose()`**

The `subscribe()` method creates an effect and returns the disposer, but the class itself never retains or calls this disposer inside `dispose()`. If the caller forgets to keep the return value, the effect keeps running even after the controller is disposed, holding references to signals and the engine observable.

```diff
export class ResourceController implements Disposable {
+  /** disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;

  // Other properties...

  subscribe() {
+    /* clean up an existing subscription before creating a new one */
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = effect(() => {
-    return effect(() => {
      const blobId = this.blobId$.value;
      if (!blobId) return;

      const blobState$ = this.engine?.blobState$(blobId);
      if (!blobState$) return;

      const subscription = blobState$.subscribe(state => {
        let { uploading, downloading } = state;
        if (state.overSize || state.errorMessage) {
          uploading = false;
          downloading = false;
        }

        this.updateState({ ...state, uploading, downloading });
      });

      return () => subscription.unsubscribe();
    });
+
+    /* return disposer for completeness */
+    return this.unsubscribeEffect;
  }

  dispose() {
+    /* stop the reactive effect first */
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = undefined;

    const url = this.blobUrl$.peek();
    if (!url) return;

    // Releases the current url.
    URL.revokeObjectURL(url);
  }
}
```

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`170-177`: **Document URL lifecycle management for `createUrlWith` method**

The method creates an object URL but leaves it to the caller to handle revoking it, which could lead to memory leaks if not managed properly.

```diff
async createUrlWith(type?: string) {
  let blob = await this.blob();
  if (!blob) return null;

  if (type) blob = new Blob([blob], { type });

+  // Note: The caller is responsible for revoking this URL with URL.revokeObjectURL()
+  // when it's no longer needed to prevent memory leaks
  return URL.createObjectURL(blob);
}
```

</blockquote></details>
<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (2)</summary><blockquote>

`119-126`: **Document the difference between embed reload and data reload**

The method handles two different reload scenarios, but the distinction between them could be clearer for future maintainers.

```diff
// Refreshes the embed component.
reload = () => {
+  // For embed views, we just need to re-render the component with a new key
  if (this.model.props.embed) {
    this._refreshKey$.value = nanoid();
    return;
  }

+  // For non-embed views, we need to refresh the underlying data
  this.refreshData();
};
```

---

`268-285`: **Optimize theme-dependent icon calculation in resolvedState$**

Currently, the loading icon is recalculated on every computation of `resolvedState$`, even when only non-theme-related properties change. Consider memoizing theme-dependent calculations.

```diff
+// Memoize theme-dependent icons to avoid recalculation when only data changes
+private readonly _themeIcons = computed(() => {
+  const theme = this.std.get(ThemeProvider).theme$.value;
+  return {
+    loadingIcon: getLoadingIconWith(theme),
+    errorIcon: WarningIcon(),
+    defaultIcon: AttachmentIcon()
+  };
+});

protected resolvedState$ = computed<AttachmentResolvedStateInfo>(() => {
-  const theme = this.std.get(ThemeProvider).theme$.value;
-  const loadingIcon = getLoadingIconWith(theme);
+  const { loadingIcon, errorIcon, defaultIcon } = this._themeIcons.value;

  const size = this.model.props.size;
  const name = this.model.props.name$.value;
  const kind = getAttachmentFileIcon(name.split('.').pop() ?? '');

  const resolvedState = this.resourceController.resolveStateWith({
    loadingIcon,
-   errorIcon: WarningIcon(),
-   icon: AttachmentIcon(),
+   errorIcon,
+   icon: defaultIcon,
    title: name,
    description: humanFileSize(size),
  });

  return { ...resolvedState, kind };
});
```

</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 c040fec78ab6c0a8707163899a3d422d65f70fec and 9b53011e1d3307a2a178e715db0d5e75fe02186b.

</details>

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

* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (6 hunks)
* `blocksuite/affine/blocks/attachment/src/configs/toolbar.ts` (1 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (1 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)
* `tests/affine-local/e2e/attachment-preview.spec.ts` (1 hunks)

</details>

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

* blocksuite/affine/blocks/attachment/src/styles.ts
* blocksuite/affine/foundation/src/effects.ts
* blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
* tests/affine-local/e2e/attachment-preview.spec.ts
* packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
* blocksuite/affine/components/src/resource/status.ts
* packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
* blocksuite/affine/blocks/attachment/src/embed.ts

</details>

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

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

</details>

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

<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (1)</summary>

`305-337`: **LGTM! Great use of guards for optimized re-renders**

The `renderEmbedView` method effectively uses the `guard` directive to optimize rendering and conditionally displays the status component only when needed. This approach reduces unnecessary re-renders and improves performance.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T06:13:25Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T06:44:32Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

`6-8`: _⚠️ Potential issue_

**Guard against duplicate custom-element registration**

The `customElements.define()` call will throw a `DOMException` if the 'affine-resource-status' element has already been registered. Since the `effects()` function could be called multiple times in different contexts (e.g., when hot reloading in development or when multiple bundles call global `effects()`), this could crash the application.



Apply this fix to prevent registration errors:

```diff
export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
}
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`123-143`: _⚠️ Potential issue_

**Memory leak risk: reactive effect is never torn down by dispose()**

The `subscribe()` method creates an effect and returns the disposer, but the class itself never retains or calls this disposer inside `dispose()`. If the caller forgets to keep the return value, the effect keeps running even after the controller is disposed, holding references to signals and the engine observable.



```diff
export class ResourceController implements Disposable {
+  /** disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;

  // ... existing code ...

  subscribe() {
+    // Clean up existing subscription before creating a new one
+    this.unsubscribeEffect?.();
-    return effect(() => {
+    this.unsubscribeEffect = effect(() => {
      const blobId = this.blobId$.value;
      if (!blobId) return;

      const blobState$ = this.engine?.blobState$(blobId);
      if (!blobState$) return;

      const subscription = blobState$.subscribe(state => {
        let { uploading, downloading } = state;
        if (state.overSize || state.errorMessage) {
          uploading = false;
          downloading = false;
        }

        this.updateState({ ...state, uploading, downloading });
      });

      return () => subscription.unsubscribe();
    });
+    
+    // Return disposer for completeness
+    return this.unsubscribeEffect;
  }

  // ... existing code ...

  dispose() {
+    // Stop the reactive effect first
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = undefined;
    
    const url = this.blobUrl$.peek();
    if (!url) return;

    // Releases the current url.
    URL.revokeObjectURL(url);
  }
}
```

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (1)</summary><blockquote>

`116-126`: **Improve refresh implementation with clear documentation**

The `reload()` method has different logic paths depending on whether the attachment is embedded or not. While the implementation looks correct, it could benefit from clearer documentation to explain the rationale behind this distinction.


```diff
  private readonly _refreshKey$ = signal<string | null>(null);

  // Refreshes the embed component.
  reload = () => {
+   // For embedded content, we refresh by triggering a re-render
+   // using a new key rather than reloading the entire attachment data
    if (this.model.props.embed) {
      this._refreshKey$.value = nanoid();
      return;
    }

+   // For non-embedded content, fall back to refreshing the underlying data
    this.refreshData();
  };
```

</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 c85c08a5e52ad09eb67bdbd534ad99d6c65e3370 and f1d78504cd0de1dea54f010dc39ca4491c9fcba3.

</details>

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

* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (6 hunks)
* `blocksuite/affine/blocks/attachment/src/configs/toolbar.ts` (1 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (1 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)
* `tests/affine-local/e2e/attachment-preview.spec.ts` (1 hunks)

</details>

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

* blocksuite/affine/blocks/attachment/src/styles.ts
* blocksuite/affine/foundation/src/effects.ts
* blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
* packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
* packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
* tests/affine-local/e2e/attachment-preview.spec.ts
* blocksuite/affine/components/src/resource/status.ts
* blocksuite/affine/blocks/attachment/src/embed.ts

</details>

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

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

<details>
<summary>blocksuite/affine/foundation/src/effects.ts (1)</summary>

* `effects` (33-68)

</details>

</blockquote></details>

</details>

</details>

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

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

</details>

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

<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (3)</summary>

`305-337`: **Well-structured embed view rendering with effective status handling**

The new `renderEmbedView()` method is a great improvement, providing better organization of embed rendering logic and incorporating the new status component for error states. The use of the `guard` directive with the refresh key ensures efficient re-rendering.

---

`268-285`: **Good implementation of computed state with extended properties**

The `resolvedState$` computed signal effectively extends the ResourceController's state with an additional `kind` property, providing a clean way to manage both resource state and UI representation in a reactive manner.

---

`287-303`: **Improved card view rendering with consistent style handling**

The refactored `renderCardView()` method cleanly separates the rendering logic from state management, making the code more maintainable. Using the computed `resolvedState$` signal ensures UI consistency with the underlying resource state.

</details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (3)</summary>

`145-168`: **Enhanced error handling in blob retrieval**

The `blob()` method now properly handles the case when the engine is not initialized by throwing an error. This is a good improvement that prevents silent failures when the controller is misconfigured.

---

`88-117`: **Clear and comprehensive state resolution logic**

The `resolveStateWith` method effectively handles different resource states (loading, error) and provides appropriate UI information based on the current state. This creates a clean separation between the resource state and its visual representation.

---

`40-60`: **Well-structured computed state with proper state determination**

The `resolvedState$` computed signal elegantly combines multiple state factors into a single coherent state representation, making it easy to consume in UI components.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T06:57:31Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T07:10:22Z
user: login: graphite-app[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T08:08:03Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

`6-8`: **Guard against duplicate custom-element registration**

`customElements.define` throws a `DOMException` if the element has already been registered.  
`effects()` can be executed more than once (e.g., when multiple bundles call global `effects()`), which would currently crash the app.

```diff
 export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
 }
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`123-143`: **Memory-leak risk: reactive `effect` is never torn down by `dispose()`**

The `subscribe()` method creates an effect and returns the disposer, but the class never retains or calls this disposer inside `dispose()`. If the caller forgets to keep the return value, the effect keeps running even after the controller is disposed, holding references to signals and the engine observable.

```diff
 export class ResourceController implements Disposable {
+  /** disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;

   subscribe() {
+    /* clean up an existing subscription before creating a new one */
+    this.unsubscribeEffect?.();
-    return effect(() => {
+    this.unsubscribeEffect = effect(() => {
       const blobId = this.blobId$.value;
       if (!blobId) return;

       const blobState$ = this.engine?.blobState$(blobId);
       if (!blobState$) return;

       const subscription = blobState$.subscribe(state => {
         let { uploading, downloading } = state;
         if (state.overSize || state.errorMessage) {
           uploading = false;
           downloading = false;
         }

         this.updateState({ ...state, uploading, downloading });
       });

       return () => subscription.unsubscribe();
     });
+    
+    /* return disposer for completeness */
+    return this.unsubscribeEffect;
   }

   dispose() {
+    /* stop the reactive effect first */
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = undefined;
     const url = this.blobUrl$.peek();
     if (!url) return;

     // Releases the current url.
     URL.revokeObjectURL(url);
   }
```

This guarantees that all observable subscriptions are cleaned up together with the URL revocation.

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (2)</summary><blockquote>

`35-67`: **Consider adding a type parameter to ResourceController**

The controller manages resources of different kinds (Blob, File, Image). Consider making the class generic with a type parameter to provide better type safety when working with specific resource types.

```diff
-export class ResourceController implements Disposable {
+export class ResourceController<T extends ResourceKind = ResourceKind> implements Disposable {
   // ...
   constructor(
     readonly blobId$: ReadonlySignal<string | undefined>,
-    readonly kind: ResourceKind = 'File'
+    readonly kind: T = 'File' as T
   ) {}
   // ...
}
```

This would allow consumers to create controllers with specific resource kinds and get appropriate type checking.

---

`133-136`: **Consider adding a comment explaining the state reset logic**

The logic that resets `uploading` and `downloading` flags when there's an error or oversize condition is important but might not be immediately obvious to future maintainers. Consider adding a comment explaining why these flags need to be reset in error conditions.

```diff
 if (state.overSize || state.errorMessage) {
+  // Reset uploading/downloading flags when in error state to ensure proper UI state
   uploading = false;
   downloading = false;
 }
```

</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 f1d78504cd0de1dea54f010dc39ca4491c9fcba3 and 9256ce23bbac230670883aea724ab32e63ffd920.

</details>

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

* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (6 hunks)
* `blocksuite/affine/blocks/attachment/src/configs/toolbar.ts` (2 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (1 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)
* `tests/affine-local/e2e/attachment-preview.spec.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* blocksuite/affine/foundation/src/effects.ts

</details>

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

* blocksuite/affine/blocks/attachment/src/styles.ts
* blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
* packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
* tests/affine-local/e2e/attachment-preview.spec.ts
* packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
* blocksuite/affine/components/src/resource/status.ts
* blocksuite/affine/blocks/attachment/src/embed.ts
* blocksuite/affine/blocks/attachment/src/attachment-block.ts

</details>

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

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

<details>
<summary>blocksuite/affine/foundation/src/effects.ts (1)</summary>

* `effects` (33-68)

</details>

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (3)</summary><blockquote>

<details>
<summary>blocksuite/affine/data-view/src/view-presets/table/pc-virtual/virtual/virtual-scroll.ts (1)</summary>

* `Disposable` (11-13)

</details>
<details>
<summary>blocksuite/framework/sync/src/blob/source.ts (1)</summary>

* `BlobState` (3-8)

</details>
<details>
<summary>blocksuite/framework/sync/src/blob/engine.ts (1)</summary>

* `BlobEngine` (16-201)

</details>

</blockquote></details>

</details>

</details>

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

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

</details>

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

<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (3)</summary>

`152-155`: **Good implementation of engine check**

The code properly checks if the engine is initialized before attempting to use it, and throws a clear error message when it's not. This improves error handling and makes debugging easier.

---

`40-60`: **Well-structured reactive state resolution**

The computed signal `resolvedState$` efficiently derives the current state from multiple sources of truth (URL and state flags). The determination of loading/error states follows a clear priority order, making the logic easy to follow and maintain.

---

`179-191`: **Proper URL lifecycle management**

The `refreshUrlWith` method correctly revokes the previous object URL before assigning a new one, preventing memory leaks. This is a crucial detail when working with blob URLs, as they continue to consume memory until explicitly released.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T08:09:24Z
user: login: Saul-Mirone
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-10T08:12:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

`6-8`: _⚠️ Potential issue_

**Guard against duplicate custom-element registration**

`customElements.define` throws a `DOMException` if the element has already been registered.  
`effects()` can be executed more than once (e.g., when multiple bundles call global `effects()`), which would currently crash the app.

```diff
export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
}
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`123-143`: _⚠️ Potential issue_

**Memory-leak risk: reactive `effect` is never torn down by `dispose()`**

The `subscribe()` method creates an effect and returns the disposer, but the class never retains or calls this disposer inside `dispose()`. If the caller forgets to keep the return value, the effect keeps running even after the controller is disposed, holding references to signals and the engine observable.

```diff
@@ class ResourceController implements Disposable {
+  /** disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;

@@ subscribe() {
+  /* clean up an existing subscription before creating a new one */
+  this.unsubscribeEffect?.();
-  return effect(() => {
+  this.unsubscribeEffect = effect(() => {
     // ...existing code...
   });
+  
+  /* return disposer for completeness */
+  return this.unsubscribeEffect;
 }

@@ dispose() {
+  /* stop the reactive effect first */
+  this.unsubscribeEffect?.();
+  this.unsubscribeEffect = undefined;
   
   const url = this.blobUrl$.peek();
   // ...existing code...
```

This ensures that all observable subscriptions are cleaned up together with the URL revocation.

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (4)</summary><blockquote>

`119-121`: **Use an immutable pattern for state updates**

The current implementation is mutating the signal value directly with object spread. Consider using a callback pattern for state updates to ensure atomicity.

```diff
updateState(state: Partial<BlobState>) {
-  this.state$.value = { ...this.state$.value, ...state };
+  this.state$.value = (prevState) => ({ ...prevState, ...state });
}
```

This pattern is more robust when multiple updates might happen in quick succession.

---

`88-117`: **The state resolution could benefit from more descriptive error messages**

The current implementation uses generic error/loading messages. Consider enhancing the user experience by providing more specific messages based on the state kind.

```diff
if (loading) {
  result.icon = loadingIcon ?? icon;
-  result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
+  result.title = state === 'uploading' ? 'Uploading file...' : 'Loading file...';
} else if (error) {
  result.icon = errorIcon ?? icon;
-  result.description = this.state$.value.errorMessage ?? description;
+  result.description = this.state$.value.errorMessage ?? 
+    (state === 'error:oversize' ? 'File size exceeds the maximum limit' : 'Failed to load file');
}
```

---

`145-168`: **Ensure blob method has complete error handling**

The `blob()` method has good error handling for missing engines, but could benefit from a more specific approach for error categorization.

```diff
async blob() {
  const blobId = this.blobId$.peek();
  if (!blobId) return null;

  let blob: Blob | null = null;
  let errorMessage: string | null = null;

  try {
    if (!this.engine) {
-      throw new Error('Blob engine is not initialized');
+      this.updateState({ errorMessage: 'Blob engine is not initialized' });
+      return null;
    }

    blob = (await this.engine.get(blobId)) ?? null;

    if (!blob) errorMessage = `${this.kind} not found`;
  } catch (err) {
    console.error(err);
-    errorMessage = `Failed to retrieve ${this.kind}`;
+    errorMessage = err instanceof Error 
+      ? `Error: ${err.message}` 
+      : `Failed to retrieve ${this.kind}`;
  }

  if (errorMessage) this.updateState({ errorMessage });

  return blob;
}
```

This approach provides more context in error messages and avoids unnecessary exception handling.

---

`35-67`: **The controller would benefit from event emission**

For better integration with UI components, consider adding an event emitter to notify listeners of state changes directly rather than requiring them to subscribe to signals.

```diff
+ import { Emitter } from '@blocksuite/global/utils';

export class ResourceController implements Disposable {
+  private readonly _emitter = new Emitter<{
+    stateChanged: ResolvedStateInfo;
+  }>();
+  readonly onStateChanged = this._emitter.event;

  readonly blobUrl$ = signal<string | null>(null);
  
  readonly state$ = signal<Partial<BlobState>>({});
  
  readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
    // ...existing code...
  });
  
+  constructor(
    readonly blobId$: ReadonlySignal<string | undefined>,
    readonly kind: ResourceKind = 'File'
-  ) {}
+  ) {
+    effect(() => {
+      const state = this.resolvedState$.value;
+      // Create a complete state object to emit
+      const completeState = this.resolveStateWith({
+        icon: { ... }, // Default icon
+        loadingIcon: { ... }, // Default loading icon
+      });
+      this._emitter.fire('stateChanged', completeState);
+    });
+  }
}
```

This pattern would allow non-signal-based components to react to state changes.

</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 9256ce23bbac230670883aea724ab32e63ffd920 and 89b61b1be467b8f12bd59983ff9024a652dd8d73.

</details>

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

* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (6 hunks)
* `blocksuite/affine/blocks/attachment/src/configs/toolbar.ts` (2 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (2 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)
* `tests/affine-local/e2e/attachment-preview.spec.ts` (1 hunks)

</details>

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

* blocksuite/affine/foundation/src/effects.ts
* blocksuite/affine/blocks/attachment/src/styles.ts
* blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
* tests/affine-local/e2e/attachment-preview.spec.ts
* packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
* packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
* blocksuite/affine/components/src/resource/status.ts
* blocksuite/affine/blocks/attachment/src/embed.ts
* blocksuite/affine/blocks/attachment/src/attachment-block.ts

</details>

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

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

<details>
<summary>blocksuite/affine/foundation/src/effects.ts (1)</summary>

* `effects` (33-68)

</details>

</blockquote></details>

</details>

</details>

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

* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)
* GitHub Check: Cloud E2E Test 5/6
* GitHub Check: Cloud E2E Test 6/6
* GitHub Check: Cloud Desktop E2E Test
* GitHub Check: Cloud E2E Test 3/6
* GitHub Check: Cloud E2E Test 2/6
* GitHub Check: Cloud E2E Test 4/6
* GitHub Check: Server Test (4, 8)
* GitHub Check: Cloud E2E Test 1/6
* GitHub Check: Server Test (3, 8)
* GitHub Check: Server Test (7, 8)
* GitHub Check: Server Test (6, 8)
* GitHub Check: Server Test (5, 8)
* GitHub Check: Server Test (0, 8)
* GitHub Check: Server Test (1, 8)
* GitHub Check: Server Test (2, 8)
* GitHub Check: Frontend Copilot E2E Test (1, 8)
* GitHub Check: Frontend Copilot E2E Test (7, 8)
* GitHub Check: Frontend Copilot E2E Test (8, 8)
* GitHub Check: Frontend Copilot E2E Test (5, 8)
* GitHub Check: Frontend Copilot E2E Test (6, 8)
* GitHub Check: Frontend Copilot E2E Test (3, 8)
* GitHub Check: Frontend Copilot E2E Test (4, 8)
* GitHub Check: Frontend Copilot E2E Test (2, 8)
* GitHub Check: Server Copilot Api Test
* GitHub Check: Server E2E Test
* GitHub Check: Unit Test (5)
* GitHub Check: Unit Test (4)
* GitHub Check: Unit Test (1)
* GitHub Check: Unit Test (3)
* GitHub Check: Unit Test (2)
* 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: loom thread test
* GitHub Check: E2E Test (8)
* GitHub Check: y-octo binding test on x86_64-apple-darwin
* GitHub Check: fuzzing
* GitHub Check: E2E Test (10)
* GitHub Check: E2E Test (7)
* GitHub Check: Run native tests
* GitHub Check: E2E Test (9)
* GitHub Check: E2E Test (6)
* GitHub Check: E2E Test (5)
* GitHub Check: E2E Test (3)
* GitHub Check: E2E Test (4)
* GitHub Check: E2E Mobile Test (5)
* GitHub Check: E2E Test (2)
* GitHub Check: E2E Test (1)
* GitHub Check: E2E Mobile Test (4)
* GitHub Check: E2E Mobile Test (3)
* GitHub Check: E2E Mobile Test (2)
* GitHub Check: E2E Mobile Test (1)
* GitHub Check: E2E BlockSuite Test (10)
* GitHub Check: E2E BlockSuite Test (9)
* GitHub Check: E2E BlockSuite Test (8)
* GitHub Check: E2E BlockSuite Test (3)
* GitHub Check: E2E BlockSuite Test (6)
* GitHub Check: E2E BlockSuite Test (7)
* GitHub Check: Typecheck
* GitHub Check: E2E BlockSuite Test (1)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, firefox)
* GitHub Check: E2E BlockSuite Test (2)
* GitHub Check: E2E BlockSuite Test (5)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, chromium)
* GitHub Check: E2E BlockSuite Test (4)
* GitHub Check: E2E BlockSuite Cross Browser Test (2, webkit)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, chromium)
* GitHub Check: E2E BlockSuite Cross Browser Test (1, firefox)
* GitHub Check: Build @affine/electron renderer
* GitHub Check: Build AFFiNE native (x86_64-pc-windows-msvc)
* GitHub Check: Build AFFiNE native (aarch64-pc-windows-msvc)
* GitHub Check: Analyze (javascript, affine)
* GitHub Check: Analyze (typescript, affine)
* GitHub Check: Analyze (typescript, blocksuite)
* GitHub Check: Lint
* GitHub Check: Analyze (javascript, blocksuite)

</details>

</details>

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

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

`6-8`: **Guard against duplicate custom-element registration**

The `customElements.define` call will throw a `DOMException` if the element has already been registered. Since the `effects()` function might be called multiple times (e.g., when multiple bundles call global `effects()`), this could crash the application.


```diff
export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
}
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (1)</summary><blockquote>

`123-143`: _⚠️ Potential issue_

**Memory-leak risk: reactive `effect` is never torn down by `dispose()`**

The `subscribe()` method creates an effect and returns the disposer, but the class itself never retains or calls this disposer inside `dispose()`. If the caller forgets to keep the return value, the effect keeps running even after the controller is disposed, holding references to signals and the engine observable.



```diff
export class ResourceController implements Disposable {
+  /** disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;

  subscribe() {
+    /* clean up an existing subscription before creating a new one */
+    this.unsubscribeEffect?.();
-    return effect(() => {
+    this.unsubscribeEffect = effect(() => {
      // existing code
    });
+
+    /* return disposer for completeness */
+    return this.unsubscribeEffect;
  }

  dispose() {
+    /* stop the reactive effect first */
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = undefined;
    
    const url = this.blobUrl$.peek();
    if (!url) return;

    // Releases the current url.
    URL.revokeObjectURL(url);
  }
}
```

</blockquote></details>

</blockquote></details>

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

<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (3)</summary><blockquote>

`116-126`: **Improve the `reload` method documentation**

The method documentation describes this as refreshing the embed component, but it does more than that. It either refreshes the embed view by generating a new key or falls back to refreshing data. Consider enhancing the comment to clarify this dual behavior.


```diff
-  // Refreshes the embed component.
+  /**
+   * Refreshes the component's content:
+   * - For embedded content: Triggers re-rendering by updating the refresh key
+   * - For non-embedded content: Refreshes the underlying data
+   */
```

---

`319-320`: **Use a more descriptive variable name for the guard dependency**

The guard dependency array contains `[this._refreshKey$.value]`, but it's not immediately clear from reading the code what this value means. Consider using a more descriptive variable name.


```diff
-        ${guard([this._refreshKey$.value], () => render(model, blobUrl))}
+        ${guard([this._refreshKey$.value], () => {
+          // Re-render whenever the refresh key changes
+          return render(model, blobUrl);
+        })}
```

---

`324-327`: **Clarify error message extraction comment**

The comment "It should be an error message" is ambiguous. It's not clear what "it" refers to. Consider rewording to clarify that you're extracting the error message from the resolved state.


```diff
-        // It should be an error messge.
+        // Extract error message from the resolved state description
```

</blockquote></details>
<details>
<summary>blocksuite/affine/components/src/resource/resource.ts (2)</summary><blockquote>

`179-191`: **Check for URL creation failure in refreshUrlWith**

The `refreshUrlWith` method doesn't handle the case where URL creation fails. While there's an early return if `url` is null, the method should clean up the previous state in this case.


```diff
async refreshUrlWith(type?: string) {
  const url = await this.createUrlWith(type);
-  if (!url) return;
+  const prevUrl = this.blobUrl$.peek();
+  
+  if (!url) {
+    // Clear the current URL if creation failed
+    this.blobUrl$.value = null;
+    
+    // Still revoke the previous URL to prevent leaks
+    if (prevUrl) URL.revokeObjectURL(prevUrl);
+    return;
+  }

-  const prevUrl = this.blobUrl$.peek();

  this.blobUrl$.value = url;

  if (!prevUrl) return;

  // Releases the previous url.
  URL.revokeObjectURL(prevUrl);
}
```

---

`88-117`: **Add type and state information for oversized content**

The `resolveStateWith` method doesn't provide specific information for the 'error:oversize' state. Consider enhancing the method to include specific title and description for oversized content to improve the user experience.


```diff
resolveStateWith(
  info: {
    loadingIcon: TemplateResult;
    errorIcon?: TemplateResult;
  } & StateInfo
): ResolvedStateInfo {
  const { error, loading, state, url } = this.resolvedState$.value;

  const { icon, title, description, loadingIcon, errorIcon } = info;

  const result = {
    error,
    loading,
    state,
    icon,
    title,
    description,
    url,
  };

  if (loading) {
    result.icon = loadingIcon ?? icon;
    result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
  } else if (error) {
    result.icon = errorIcon ?? icon;
+   if (state === 'error:oversize') {
+     result.title = 'File too large';
+     result.description = this.state$.value.errorMessage ?? 'This file exceeds the maximum size limit';
+   } else {
    result.description = this.state$.value.errorMessage ?? description;
+   }
  }

  return result;
}
```

</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 89b61b1be467b8f12bd59983ff9024a652dd8d73 and b3f0f38b41fec3143185e0139c36af08f889cf6f.

</details>

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

* `blocksuite/affine/blocks/attachment/src/attachment-block.ts` (6 hunks)
* `blocksuite/affine/blocks/attachment/src/configs/toolbar.ts` (2 hunks)
* `blocksuite/affine/blocks/attachment/src/embed.ts` (7 hunks)
* `blocksuite/affine/blocks/attachment/src/styles.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/index.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/resource.ts` (1 hunks)
* `blocksuite/affine/components/src/resource/status.ts` (1 hunks)
* `blocksuite/affine/foundation/src/effects.ts` (2 hunks)
* `packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx` (2 hunks)
* `packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx` (3 hunks)
* `tests/affine-local/e2e/attachment-preview.spec.ts` (1 hunks)

</details>

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

* blocksuite/affine/blocks/attachment/src/styles.ts
* blocksuite/affine/foundation/src/effects.ts
* blocksuite/affine/blocks/attachment/src/configs/toolbar.ts
* tests/affine-local/e2e/attachment-preview.spec.ts
* packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx
* packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
* blocksuite/affine/components/src/resource/status.ts
* blocksuite/affine/blocks/attachment/src/embed.ts

</details>

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

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

<details>
<summary>blocksuite/affine/components/src/resource/index.ts (1)</summary><blockquote>

<details>
<summary>blocksuite/affine/foundation/src/effects.ts (1)</summary>

* `effects` (33-68)

</details>

</blockquote></details>

</details>

</details>

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

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

</details>

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

<details>
<summary>blocksuite/affine/blocks/attachment/src/attachment-block.ts (1)</summary>

`329-335`: **LGTM! Good implementation of the status component**

The implementation of the resource status component with reload capability is well-designed. It provides users with clear error information and a way to recover from errors, improving the overall user experience.

</details>

</blockquote></details>

</details>

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

Review comments:
diff_hunk: @@ -1,193 +1,8 @@
-import type { Disposable } from '@blocksuite/global/disposable';
-import type { BlobEngine, BlobState } from '@blocksuite/sync';
-import {
-  computed,
-  effect,
-  type ReadonlySignal,
-  signal,
-} from '@preact/signals-core';
-import type { TemplateResult } from 'lit-html';
+import { ResourceStatus } from './status';
 
-export type ResourceKind = 'Blob' | 'File' | 'Image';
+export * from './resource';
+export * from './status';
 
-export type StateKind =
-  | 'loading'
-  | 'uploading'
-  | 'error'
-  | 'error:oversize'
-  | 'none';
-
-export type StateInfo = {
-  icon: TemplateResult;
-  title?: string;
-  description?: string;
-};
-
-export type ResolvedStateInfoPart = {
-  loading: boolean;
-  error: boolean;
-  state: StateKind;
-};
-
-export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
-
-export class ResourceController implements Disposable {
-  readonly blobUrl$ = signal<string | null>(null);
-
-  readonly state$ = signal<Partial<BlobState>>({});
-
-  readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
-    const {
-      uploading = false,
-      downloading = false,
-      overSize = false,
-      errorMessage,
-    } = this.state$.value;
-    const hasExceeded = overSize;
-    const hasError = hasExceeded || Boolean(errorMessage);
-    const state = this.determineState(
-      hasExceeded,
-      hasError,
-      uploading,
-      downloading
-    );
-
-    const loading = state === 'uploading' || state === 'loading';
-
-    return { error: hasError, loading, state };
-  });
-
-  private engine?: BlobEngine;
-
-  constructor(
-    readonly blobId$: ReadonlySignal<string | undefined>,
-    readonly kind: ResourceKind = 'File'
-  ) {}
-
-  // This is a tradeoff, initializing `Blob Sync Engine`.
-  setEngine(engine: BlobEngine) {
-    this.engine = engine;
-    return this;
-  }
-
-  determineState(
-    hasExceeded: boolean,
-    hasError: boolean,
-    uploading: boolean,
-    downloading: boolean
-  ): StateKind {
-    if (hasExceeded) return 'error:oversize';
-    if (hasError) return 'error';
-    if (uploading) return 'uploading';
-    if (downloading) return 'loading';
-    return 'none';
-  }
-
-  resolveStateWith(
-    info: {
-      loadingIcon: TemplateResult;
-      errorIcon?: TemplateResult;
-    } & StateInfo
-  ): ResolvedStateInfo {
-    const { error, loading, state } = this.resolvedState$.value;
-
-    const { icon, title, description, loadingIcon, errorIcon } = info;
-
-    const result = {
-      error,
-      loading,
-      state,
-      icon,
-      title,
-      description,
-    };
-
-    if (loading) {
-      result.icon = loadingIcon ?? icon;
-      result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
-    } else if (error) {
-      result.icon = errorIcon ?? icon;
-      result.description = this.state$.value.errorMessage ?? description;
-    }
-
-    return result;
-  }
-
-  updateState(state: Partial<BlobState>) {
-    this.state$.value = { ...this.state$.value, ...state };
-  }
-
-  subscribe() {
-    return effect(() => {
-      const blobId = this.blobId$.value;
-      if (!blobId) return;
-
-      const blobState$ = this.engine?.blobState$(blobId);
-      if (!blobState$) return;
-
-      const subscription = blobState$.subscribe(state => {
-        let { uploading, downloading } = state;
-        if (state.overSize || state.errorMessage) {
-          uploading = false;
-          downloading = false;
-        }
-
-        this.updateState({ ...state, uploading, downloading });
-      });
-
-      return () => subscription.unsubscribe();
-    });
-  }
-
-  async blob() {
-    const blobId = this.blobId$.peek();
-    if (!blobId) return null;
-
-    let blob: Blob | null = null;
-    let errorMessage: string | null = null;
-
-    try {
-      blob = (await this.engine?.get(blobId)) ?? null;
-
-      if (!blob) errorMessage = `${this.kind} not found`;
-    } catch (err) {
-      console.error(err);
-      errorMessage = `Failed to retrieve ${this.kind}`;
-    }
-
-    if (errorMessage) this.updateState({ errorMessage });
-
-    return blob;
-  }
-
-  async createUrlWith(type?: string) {
-    let blob = await this.blob();
-    if (!blob) return null;
-
-    if (type) blob = new Blob([blob], { type });
-
-    return URL.createObjectURL(blob);
-  }
-
-  async refreshUrlWith(type?: string) {
-    const url = await this.createUrlWith(type);
-    if (!url) return;
-
-    const prevUrl = this.blobUrl$.peek();
-
-    this.blobUrl$.value = url;
-
-    if (!prevUrl) return;
-
-    // Releases the previous url.
-    URL.revokeObjectURL(prevUrl);
-  }
-
-  dispose() {
-    const url = this.blobUrl$.peek();
-    if (!url) return;
-
-    // Releases the current url.
-    URL.revokeObjectURL(url);
-  }
+export function effects() {
+  customElements.define('affine-resource-status', ResourceStatus);
 }
path: blocksuite/affine/components/src/resource/index.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Guard against duplicate custom-element registration**

`customElements.define` throws a `DOMException` if the element has already been registered.  
`effects()` can be executed more than once (e.g., when multiple bundles call global `effects()`), which would currently crash the app.

```diff
 export function effects() {
-  customElements.define('affine-resource-status', ResourceStatus);
+  if (!customElements.get('affine-resource-status')) {
+    customElements.define('affine-resource-status', ResourceStatus);
+  }
 }
```



<!-- 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 function effects() {
  if (!customElements.get('affine-resource-status')) {
    customElements.define('affine-resource-status', ResourceStatus);
  }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T07:16:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,140 @@
+import {
+  fontBaseStyle,
+  panelBaseColorsStyle,
+} from '@blocksuite/affine-shared/styles';
+import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
+import {
+  createButtonPopper,
+  stopPropagation,
+} from '@blocksuite/affine-shared/utils';
+import { WithDisposable } from '@blocksuite/global/lit';
+import { InformationIcon } from '@blocksuite/icons/lit';
+import { PropTypes, requiredProperties } from '@blocksuite/std';
+import { css, html, LitElement } from 'lit';
+import { property, query } from 'lit/decorators.js';
+
+@requiredProperties({
+  message: PropTypes.string,
+  reload: PropTypes.instanceOf(Function),
+})
+export class ResourceStatus extends WithDisposable(LitElement) {
+  static override styles = css`
+    button.status {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 24px;
+      height: 24px;
+      border-radius: 50%;
+      font-size: 18px;
+      outline: none;
+      border: none;
+      cursor: pointer;
+      color: ${unsafeCSSVarV2('button/pureWhiteText')};
+      background: ${unsafeCSSVarV2('status/error')};
+      box-shadow: var(--affine-overlay-shadow);
+    }
+
+    ${panelBaseColorsStyle('.popper')}
+    ${fontBaseStyle('.popper')}
+    .popper {
+      display: none;
+      outline: none;
+      padding: 8px;
+      border-radius: 8px;
+      width: 260px;
+      font-size: var(--affine-font-sm);
+      font-style: normal;
+      font-weight: 400;
+      line-height: 22px;
+
+      &[data-show] {
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+      }
+    }
+
+    .content {
+      color: ${unsafeCSSVarV2('text/primary')};
+    }
+
+    .footer {
+      display: flex;
+      justify-content: flex-end;
+    }
+
+    button.reload {
+      display: flex;
+      align-items: center;
+      padding: 2px 12px;
+      border-radius: 8px;
+      border: none;
+      background: none;
+      cursor: pointer;
+      outline: none;
+      color: ${unsafeCSSVarV2('button/primary')};
+    }
+  `;
+
+  private _popper: ReturnType<typeof createButtonPopper> | null = null;
+
+  private _updatePopper() {
+    this._popper?.dispose();
+    this._popper = createButtonPopper({
+      reference: this._trigger,
+      popperElement: this._content,
+      mainAxis: 8,
+      allowedPlacements: ['top-start', 'bottom-start'],
+    });
+  }
path: blocksuite/affine/components/src/resource/status.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Dispose the popper instance in `disconnectedCallback` to prevent leaks**

`_popper` is created once and never released when the element is removed from the DOM.  
On page navigation or re-render this can leave event listeners & mutation observers dangling.

```diff
   private _updatePopper() {
     this._popper?.dispose();
     this._popper = createButtonPopper({
       …
     });
   }

+  override disconnectedCallback() {
+    this._popper?.dispose();
+    super.disconnectedCallback();
+  }
```



<!-- 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
  private _popper: ReturnType<typeof createButtonPopper> | null = null;

  private _updatePopper() {
    this._popper?.dispose();
    this._popper = createButtonPopper({
      reference: this._trigger,
      popperElement: this._content,
      mainAxis: 8,
      allowedPlacements: ['top-start', 'bottom-start'],
    });
  }

  override disconnectedCallback() {
    this._popper?.dispose();
    super.disconnectedCallback();
  }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T07:16:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+import type { Disposable } from '@blocksuite/global/disposable';
+import type { BlobEngine, BlobState } from '@blocksuite/sync';
+import {
+  computed,
+  effect,
+  type ReadonlySignal,
+  signal,
+} from '@preact/signals-core';
+import type { TemplateResult } from 'lit-html';
+
+export type ResourceKind = 'Blob' | 'File' | 'Image';
+
+export type StateKind =
+  | 'loading'
+  | 'uploading'
+  | 'error'
+  | 'error:oversize'
+  | 'none';
+
+export type StateInfo = {
+  icon: TemplateResult;
+  title?: string;
+  description?: string;
+};
+
+export type ResolvedStateInfoPart = {
+  loading: boolean;
+  error: boolean;
+  state: StateKind;
+  url: string | null;
+};
+
+export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
+
+export class ResourceController implements Disposable {
+  readonly blobUrl$ = signal<string | null>(null);
+
+  readonly state$ = signal<Partial<BlobState>>({});
+
+  readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
+    const url = this.blobUrl$.value;
+    const {
+      uploading = false,
+      downloading = false,
+      overSize = false,
+      errorMessage,
+    } = this.state$.value;
+    const hasExceeded = overSize;
+    const hasError = hasExceeded || Boolean(errorMessage);
+    const state = this.determineState(
+      hasExceeded,
+      hasError,
+      uploading,
+      downloading
+    );
+
+    const loading = state === 'uploading' || state === 'loading';
+
+    return { error: hasError, loading, state, url };
+  });
+
+  private engine?: BlobEngine;
+
+  constructor(
+    readonly blobId$: ReadonlySignal<string | undefined>,
+    readonly kind: ResourceKind = 'File'
+  ) {}
+
+  // This is a tradeoff, initializing `Blob Sync Engine`.
+  setEngine(engine: BlobEngine) {
+    this.engine = engine;
+    return this;
+  }
+
+  determineState(
+    hasExceeded: boolean,
+    hasError: boolean,
+    uploading: boolean,
+    downloading: boolean
+  ): StateKind {
+    if (hasExceeded) return 'error:oversize';
+    if (hasError) return 'error';
+    if (uploading) return 'uploading';
+    if (downloading) return 'loading';
+    return 'none';
+  }
+
+  resolveStateWith(
+    info: {
+      loadingIcon: TemplateResult;
+      errorIcon?: TemplateResult;
+    } & StateInfo
+  ): ResolvedStateInfo {
+    const { error, loading, state, url } = this.resolvedState$.value;
+
+    const { icon, title, description, loadingIcon, errorIcon } = info;
+
+    const result = {
+      error,
+      loading,
+      state,
+      icon,
+      title,
+      description,
+      url,
+    };
+
+    if (loading) {
+      result.icon = loadingIcon ?? icon;
+      result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
+    } else if (error) {
+      result.icon = errorIcon ?? icon;
+      result.description = this.state$.value.errorMessage ?? description;
+    }
+
+    return result;
+  }
+
+  updateState(state: Partial<BlobState>) {
+    this.state$.value = { ...this.state$.value, ...state };
+  }
+
+  subscribe() {
+    return effect(() => {
+      const blobId = this.blobId$.value;
+      if (!blobId) return;
+
+      const blobState$ = this.engine?.blobState$(blobId);
+      if (!blobState$) return;
+
+      const subscription = blobState$.subscribe(state => {
+        let { uploading, downloading } = state;
+        if (state.overSize || state.errorMessage) {
+          uploading = false;
+          downloading = false;
+        }
+
+        this.updateState({ ...state, uploading, downloading });
+      });
+
+      return () => subscription.unsubscribe();
+    });
+  }
+
+  async blob() {
+    const blobId = this.blobId$.peek();
+    if (!blobId) return null;
+
+    let blob: Blob | null = null;
+    let errorMessage: string | null = null;
+
+    try {
+      blob = (await this.engine?.get(blobId)) ?? null;
+
+      if (!blob) errorMessage = `${this.kind} not found`;
+    } catch (err) {
+      console.error(err);
+      errorMessage = `Failed to retrieve ${this.kind}`;
+    }
+
+    if (errorMessage) this.updateState({ errorMessage });
+
+    return blob;
+  }
path: blocksuite/affine/components/src/resource/resource.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Graceful handling when `BlobEngine` is absent**  

`blob()` silently treats a missing engine as “not found”, which hides configuration mistakes.  
Emit an explicit error state so callers & UI can react appropriately.

```diff
   async blob() {
     const blobId = this.blobId$.peek();
     if (!blobId) return null;
 
-    let blob: Blob | null = null;
-    let errorMessage: string | null = null;
+    if (!this.engine) {
+      this.updateState({ errorMessage: 'Blob engine is not initialized' });
+      return null;
+    }
+
+    let blob: Blob | null = null;
+    let errorMessage: string | null = 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
   async blob() {
     const blobId = this.blobId$.peek();
     if (!blobId) return null;
 
     if (!this.engine) {
       this.updateState({ errorMessage: 'Blob engine is not initialized' });
       return null;
     }
 
     let blob: Blob | null = null;
     let errorMessage: string | null = null;
 
     try {
       blob = (await this.engine?.get(blobId)) ?? null;
 
       if (!blob) errorMessage = `${this.kind} not found`;
     } catch (err) {
       console.error(err);
       errorMessage = `Failed to retrieve ${this.kind}`;
     }
 
     if (errorMessage) this.updateState({ errorMessage });
 
     return blob;
   }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 0eea954
created_at: 2025-05-09T07:16:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,196 @@
+import type { Disposable } from '@blocksuite/global/disposable';
+import type { BlobEngine, BlobState } from '@blocksuite/sync';
+import {
+  computed,
+  effect,
+  type ReadonlySignal,
+  signal,
+} from '@preact/signals-core';
+import type { TemplateResult } from 'lit-html';
+
+export type ResourceKind = 'Blob' | 'File' | 'Image';
+
+export type StateKind =
+  | 'loading'
+  | 'uploading'
+  | 'error'
+  | 'error:oversize'
+  | 'none';
+
+export type StateInfo = {
+  icon: TemplateResult;
+  title?: string;
+  description?: string;
+};
+
+export type ResolvedStateInfoPart = {
+  loading: boolean;
+  error: boolean;
+  state: StateKind;
+  url: string | null;
+};
+
+export type ResolvedStateInfo = StateInfo & ResolvedStateInfoPart;
+
+export class ResourceController implements Disposable {
+  readonly blobUrl$ = signal<string | null>(null);
+
+  readonly state$ = signal<Partial<BlobState>>({});
+
+  readonly resolvedState$ = computed<ResolvedStateInfoPart>(() => {
+    const url = this.blobUrl$.value;
+    const {
+      uploading = false,
+      downloading = false,
+      overSize = false,
+      errorMessage,
+    } = this.state$.value;
+    const hasExceeded = overSize;
+    const hasError = hasExceeded || Boolean(errorMessage);
+    const state = this.determineState(
+      hasExceeded,
+      hasError,
+      uploading,
+      downloading
+    );
+
+    const loading = state === 'uploading' || state === 'loading';
+
+    return { error: hasError, loading, state, url };
+  });
+
+  private engine?: BlobEngine;
+
+  constructor(
+    readonly blobId$: ReadonlySignal<string | undefined>,
+    readonly kind: ResourceKind = 'File'
+  ) {}
+
+  // This is a tradeoff, initializing `Blob Sync Engine`.
+  setEngine(engine: BlobEngine) {
+    this.engine = engine;
+    return this;
+  }
+
+  determineState(
+    hasExceeded: boolean,
+    hasError: boolean,
+    uploading: boolean,
+    downloading: boolean
+  ): StateKind {
+    if (hasExceeded) return 'error:oversize';
+    if (hasError) return 'error';
+    if (uploading) return 'uploading';
+    if (downloading) return 'loading';
+    return 'none';
+  }
+
+  resolveStateWith(
+    info: {
+      loadingIcon: TemplateResult;
+      errorIcon?: TemplateResult;
+    } & StateInfo
+  ): ResolvedStateInfo {
+    const { error, loading, state, url } = this.resolvedState$.value;
+
+    const { icon, title, description, loadingIcon, errorIcon } = info;
+
+    const result = {
+      error,
+      loading,
+      state,
+      icon,
+      title,
+      description,
+      url,
+    };
+
+    if (loading) {
+      result.icon = loadingIcon ?? icon;
+      result.title = state === 'uploading' ? 'Uploading...' : 'Loading...';
+    } else if (error) {
+      result.icon = errorIcon ?? icon;
+      result.description = this.state$.value.errorMessage ?? description;
+    }
+
+    return result;
+  }
+
+  updateState(state: Partial<BlobState>) {
+    this.state$.value = { ...this.state$.value, ...state };
+  }
+
+  subscribe() {
+    return effect(() => {
+      const blobId = this.blobId$.value;
+      if (!blobId) return;
+
+      const blobState$ = this.engine?.blobState$(blobId);
+      if (!blobState$) return;
+
+      const subscription = blobState$.subscribe(state => {
+        let { uploading, downloading } = state;
+        if (state.overSize || state.errorMessage) {
+          uploading = false;
+          downloading = false;
+        }
+
+        this.updateState({ ...state, uploading, downloading });
+      });
+
+      return () => subscription.unsubscribe();
+    });
+  }
path: blocksuite/affine/components/src/resource/resource.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Memory-leak risk: reactive `effect` is never torn down by `dispose()`**  

`subscribe()` creates an effect and returns the disposer, but the class itself never retains or calls this disposer inside `dispose()`.  
If the caller forgets to keep the return value, the effect keeps running even after the controller is disposed, holding references to signals and the engine observable.

```diff
@@
-export class ResourceController implements Disposable {
+export class ResourceController implements Disposable {
+  /** disposer for the reactive subscription created in `subscribe()` */
+  private unsubscribeEffect?: () => void;
@@
-  subscribe() {
-    return effect(() => {
+  subscribe() {
+    /* clean up an existing subscription before creating a new one */
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = effect(() => {
@@
-    });
+    });
+
+    /* return disposer for completeness */
+    return this.unsubscribeEffect;
   }
@@
-  dispose() {
+  dispose() {
+    /* stop the reactive effect first */
+    this.unsubscribeEffect?.();
+    this.unsubscribeEffect = undefined;
```

This guarantees that all observable subscriptions are cleaned up together with the URL revocation.  
 


<!-- 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 class ResourceController implements Disposable {
  /** disposer for the reactive subscription created in `subscribe()` */
  private unsubscribeEffect?: () => void;

  subscribe() {
    /* clean up an existing subscription before creating a new one */
    this.unsubscribeEffect?.();
    this.unsubscribeEffect = effect(() => {
      const blobId = this.blobId$.value;
      if (!blobId) return;

      const blobState$ = this.engine?.blobState$(blobId);
      if (!blobState$) return;

      const subscription = blobState$.subscribe(state => {
        let { uploading, downloading } = state;
        if (state.overSize || state.errorMessage) {
          uploading = false;
          downloading = false;
        }

        this.updateState({ ...state, uploading, downloading });
      });

      return () => subscription.unsubscribe();
    });

    /* return disposer for completeness */
    return this.unsubscribeEffect;
  }

  dispose() {
    /* stop the reactive effect first */
    this.unsubscribeEffect?.();
    this.unsubscribeEffect = undefined;

    // existing disposal logic (e.g., URL.revokeObjectURL, etc.)
  }

  // ... rest of the class ...
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-09T07:16:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,4 +32,5 @@ export declare const _GLOBAL_:
   | typeof componentToolbarEffects
   | typeof componentToggleButtonEffects
   | typeof componentColorPickerEffects
-  | typeof componentViewDropdownMenuEffects;
+  | typeof componentViewDropdownMenuEffects
+  | typeof componentResourceEffects;
path: blocksuite/affine/all/src/effects.ts
user: login: Saul-Mirone
body: if no errors reported in `yarn typecheck` without this line, you can remove this. These effects will be removed in the future.
created_at: 2025-05-09T14:51:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,141 @@
+import {
+  fontBaseStyle,
+  panelBaseColorsStyle,
+} from '@blocksuite/affine-shared/styles';
+import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
+import {
+  createButtonPopper,
+  stopPropagation,
+} from '@blocksuite/affine-shared/utils';
+import { WithDisposable } from '@blocksuite/global/lit';
+import { InformationIcon } from '@blocksuite/icons/lit';
+import { PropTypes, requiredProperties } from '@blocksuite/std';
+import { css, html, LitElement } from 'lit';
+import { property, query } from 'lit/decorators.js';
+
+@requiredProperties({
+  message: PropTypes.string,
+  reload: PropTypes.instanceOf(Function),
+})
+export class ResourceStatus extends WithDisposable(LitElement) {
+  static override styles = css`
+    button.status {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 24px;
+      height: 24px;
+      border-radius: 50%;
+      font-size: 18px;
+      outline: none;
+      border: none;
+      cursor: pointer;
+      color: ${unsafeCSSVarV2('button/pureWhiteText')};
+      background: ${unsafeCSSVarV2('status/error')};
+      box-shadow: var(--affine-overlay-shadow);
+    }
+
+    ${panelBaseColorsStyle('.popper')}
+    ${fontBaseStyle('.popper')}
+    .popper {
+      display: none;
+      outline: none;
+      padding: 8px;
+      border-radius: 8px;
+      width: 260px;
+      font-size: var(--affine-font-sm);
+      font-style: normal;
+      font-weight: 400;
+      line-height: 22px;
+
+      &[data-show] {
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+      }
+    }
+
+    .content {
+      color: ${unsafeCSSVarV2('text/primary')};
+    }
+
+    .footer {
+      display: flex;
+      justify-content: flex-end;
+    }
+
+    button.reload {
+      display: flex;
+      align-items: center;
+      padding: 2px 12px;
+      border-radius: 8px;
+      border: none;
+      background: none;
+      cursor: pointer;
+      outline: none;
+      color: ${unsafeCSSVarV2('button/primary')};
+    }
+  `;
+
+  private _popper: ReturnType<typeof createButtonPopper> | null = null;
+
+  private _updatePopper() {
+    this._popper?.dispose();
+    this._popper = createButtonPopper({
+      reference: this._trigger,
+      popperElement: this._content,
+      mainAxis: 8,
+      allowedPlacements: ['top-start', 'bottom-start'],
+    });
+  }
+
+  override firstUpdated() {
+    this._updatePopper();
+    this.disposables.addFromEvent(this, 'click', stopPropagation);
+    this.disposables.addFromEvent(this, 'keydown', (e: KeyboardEvent) => {
+      e.stopPropagation();
+      if (e.key === 'Escape') {
+        this._popper?.hide();
+      }
+    });
+    this.disposables.addFromEvent(this._trigger, 'click', (_: MouseEvent) => {
+      this._popper?.toggle();
+    });
+    this.disposables.addFromEvent(
+      this._reloadButton,
+      'click',
+      (_: MouseEvent) => {
+        this._popper?.hide();
+        this.reload();
+      }
+    );
+    this._disposables.add(() => this._popper?.dispose());
path: blocksuite/affine/components/src/resource/status.ts
user: login: graphite-app[bot]
body: There's a potential runtime error in this line. The class extends `WithDisposable` which provides `this.disposables` (not `this._disposables`). To ensure proper cleanup when the component is disposed, please change:

```ts
this._disposables.add(() => this._popper?.dispose());
```

to:

```ts
this.disposables.add(() => this._popper?.dispose());
```
```suggestion
    this.disposables.add(() => this._popper?.dispose());
```
  

*Spotted by [Diamond](https://app.graphite.dev/diamond/?org=toeverything&ref=ai-review-comment)*<i class='graphite__hidden'><br /><br />Is this helpful? React 👍 or 👎 to let us know.</i>
created_at: 2025-05-10T05:43:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -347,3 +347,26 @@ test('should enable pointer event in pdf viewer', async ({ page }) => {
 
   await expect(attachmentSelection).toBeHidden();
 });
+
+test('should re-render pdf viewer', async ({ page }) => {
+  await openHomePage(page);
+  await clickNewPageButton(page);
+  await waitForEmptyEditor(page);
+
+  const title = getBlockSuiteEditorTitle(page);
+  await title.click();
+  await page.keyboard.type('PDF preview');
+
+  await page.keyboard.press('Enter');
+
+  await importAttachment(page, 'lorem-ipsum.pdf');
+
+  const attachment = page.locator('affine-attachment');
+  await attachment.click();
+
+  const toolbar = locateToolbar(page);
+
+  // Switches to embed view
+  await toolbar.getByLabel('Switch view').click();
+  await toolbar.getByLabel('Embed view').click();
+});
path: tests/affine-local/e2e/attachment-preview.spec.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add assertions to verify PDF rendering in the test case**

The test "should re-render pdf viewer" successfully switches to embed view but lacks assertions to verify the PDF viewer renders correctly. Without verifying the actual rendering or testing the reload functionality, the test doesn't fully validate the feature.


Consider extending the test with assertions similar to those in other test cases:

```diff
test('should re-render pdf viewer', async ({ page }) => {
  await openHomePage(page);
  await clickNewPageButton(page);
  await waitForEmptyEditor(page);

  const title = getBlockSuiteEditorTitle(page);
  await title.click();
  await page.keyboard.type('PDF preview');

  await page.keyboard.press('Enter');

  await importAttachment(page, 'lorem-ipsum.pdf');

  const attachment = page.locator('affine-attachment');
  await attachment.click();

  const toolbar = locateToolbar(page);

  // Switches to embed view
  await toolbar.getByLabel('Switch view').click();
  await toolbar.getByLabel('Embed view').click();
+
+  await page.waitForTimeout(500);
+
+  // Verify the PDF viewer is visible
+  const embedContainer = attachment.locator('.affine-attachment-embed-container');
+  await expect(embedContainer).toBeVisible();
+
+  // Test reload functionality
+  await attachment.click();
+  await toolbar.getByRole('button', { name: 'Reload' }).click();
+
+  // Verify the PDF viewer is still visible after reload
+  await expect(embedContainer).toBeVisible();
});
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-10T05:53:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -347,3 +347,42 @@ test('should enable pointer event in pdf viewer', async ({ page }) => {
 
   await expect(attachmentSelection).toBeHidden();
 });
+
+test('should re-render pdf viewer', async ({ page }) => {
+  await openHomePage(page);
+  await clickNewPageButton(page);
+  await waitForEmptyEditor(page);
+
+  const title = getBlockSuiteEditorTitle(page);
+  await title.click();
+  await page.keyboard.type('PDF preview');
+
+  await page.keyboard.press('Enter');
+
+  await importAttachment(page, 'lorem-ipsum.pdf');
+
+  const attachment = page.locator('affine-attachment');
+  await attachment.click();
+
+  const toolbar = locateToolbar(page);
+
+  // Switches to embed view
+  await toolbar.getByLabel('Switch view').click();
+  await toolbar.getByLabel('Embed view').click();
+
+  const portal = attachment.locator('lit-react-portal');
+  await expect(portal).toBeVisible();
+
+  const portalId = await portal.getAttribute('portalid');
+  expect(portalId).not.toBeNull();
+
+  const moreMenu = toolbar.getByLabel('More menu');
+  await moreMenu.click();
+
+  await toolbar.getByRole('button', { name: 'Reload' }).click();
path: tests/affine-local/e2e/attachment-preview.spec.ts
user: login: graphite-app[bot]
body: After switching to embed view, the attachment may lose focus causing the toolbar to disappear. Add an explicit click on the attachment before trying to access the 'More menu' button: `await attachment.click();` after line 382.

*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-10T06:44:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -139,18 +139,16 @@ export const useLitPortalFactory = () => {
 };
 
 // render a react element to a lit template
-export const useLitPortal = (
-  elementOrFactory: React.ReactElement | (() => React.ReactElement)
-) => {
+export const useLitPortal = (elementOrFactory: ElementOrFactory) => {
   const [anchor, setAnchor] = useState<HTMLElement>();
   const template = useMemo(
     () =>
       createLitPortalAnchor(event => {
+        let anchor: HTMLElement | undefined;
         if (event.name !== 'disconnectedCallback') {
-          setAnchor(event.target as HTMLElement);
-        } else {
-          setAnchor(undefined);
+          anchor = event.target as HTMLElement;
         }
+        setAnchor(anchor);
path: packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
user: login: graphite-app[bot]
body: The issue is that when the 'disconnectedCallback' event is fired, the anchor is not being set to undefined. This causes the portal to not properly re-render when the reload button is clicked. The code should be modified to set anchor to undefined when the disconnectedCallback event occurs.

*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-10T07:10:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -139,18 +139,16 @@ export const useLitPortalFactory = () => {
 };
 
 // render a react element to a lit template
-export const useLitPortal = (
-  elementOrFactory: React.ReactElement | (() => React.ReactElement)
-) => {
+export const useLitPortal = (elementOrFactory: ElementOrFactory) => {
   const [anchor, setAnchor] = useState<HTMLElement>();
path: packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx
user: login: graphite-app[bot]
body: The type `ElementOrFactory` is used as a parameter type but doesn't appear to be imported. To maintain type safety and code clarity, please add the appropriate import for this type. This will help prevent potential type errors and improve code maintainability.

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

PR comments:
user: login: fundon
created_at: 2025-05-08T00:04:29Z
body: * **#12194** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12194?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#12180** <a href="https://app.graphite.dev/github/pr/toeverything/AFFiNE/12180?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/12180?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</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=12180).

<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: COLLABORATOR
user: login: coderabbitai[bot]
created_at: 2025-05-08T00:04:36Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

This change refactors attachment block rendering by introducing a reactive computed signal for resource state, reorganizes card and embed rendering logic, and adds a status component for error handling. It modularizes resource state management into a new controller, updates embed config interfaces, and introduces a reusable status web component for displaying and reloading error states.

## Changes

| File(s)                                                                                              | Change Summary |
|------------------------------------------------------------------------------------------------------|---------------|
| blocksuite/affine/blocks/attachment/src/attachment-block.ts, blocksuite/affine/blocks/attachment/src/configs/toolbar.ts | Refactored attachment block rendering: replaced `embedView` getter with computed signal `resolvedState$`, consolidated card and embed rendering into `renderCardView` and `renderEmbedView`, added guarded embed rendering with error handling and reload support, updated toolbar action to call new `reload()` method. |
| blocksuite/affine/blocks/attachment/src/embed.ts, packages/frontend/core/src/blocksuite/extensions/attachment-embed-view.tsx | Updated embed config interface: renamed `template` to `render`, changed return type to allow `null`, added `shouldShowStatus` flag, refactored `AttachmentEmbedService` to provide `getRender` and `shouldShowStatus` methods, updated PDF and audio embed views accordingly. |
| blocksuite/affine/components/src/resource/index.ts, blocksuite/affine/components/src/resource/resource.ts | Removed old resource controller and related types, introduced new `ResourceController` class managing blob resource state and URL lifecycle with reactive signals, re-exported resource modules, registered `ResourceStatus` component. |
| blocksuite/affine/components/src/resource/status.ts | Added new LitElement-based `ResourceStatus` web component showing a status button with pop-up message and reload button, managing popper lifecycle and user interactions. |
| blocksuite/affine/blocks/attachment/src/styles.ts | Added CSS class `.affine-attachment-embed-status` for absolute positioning of the embed status component. |
| blocksuite/affine/foundation/src/effects.ts | Registered resource status component effects by calling `componentResourceEffects()` in main `effects()` function. |
| packages/frontend/component/src/lit-react/lit-portal/lit-portal.tsx | Changed parameter type of `useLitPortal` to `ElementOrFactory`, refactored event handler logic to consolidate anchor state updates into a single call. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant AttachmentBlockComponent
    participant ResourceController
    participant AttachmentEmbedProvider
    participant ResourceStatus

    User->>AttachmentBlockComponent: Render block
    AttachmentBlockComponent->>ResourceController: Get resolvedState$
    alt embed enabled and blobUrl available
        AttachmentBlockComponent->>AttachmentEmbedProvider: getRender()
        alt render error
            AttachmentBlockComponent->>ResourceStatus: Show error with reload()
            User->>ResourceStatus: Click reload
            ResourceStatus->>AttachmentBlockComponent: reload()
            AttachmentBlockComponent->>ResourceController: refreshUrl()
        else render success
            AttachmentBlockComponent->>AttachmentEmbedProvider: Render embed
        end
    else
        AttachmentBlockComponent->>ResourceController: Get resolvedState$
        AttachmentBlockComponent->>AttachmentBlockComponent: Render card view
    end
```

## Assessment against linked issues

| Objective                                                                                  | Addressed | Explanation |
|--------------------------------------------------------------------------------------------|-----------|-------------|
| Add status component to attachment embed view (BS-3438)                                    | ✅        |             |
| Trigger LitPortal re-render (BS-3447)                                                      | ✅        |             |

## Possibly related PRs

- [toeverything/AFFiNE#11763](https://github.com/toeverything/AFFiNE/pull/11763): Refactors attachment block UI and error handling, modularizing rendering and blob state management—directly related to this PR's rendering refactor.
- [toeverything/AFFiNE#12121](https://github.com/toeverything/AFFiNE/pull/12121): Adds a ResourceController abstraction for resource state and blob URL management, which is adopted and extended in this PR.
- [toeverything/AFFiNE#11984](https://github.com/toeverything/AFFiNE/pull/11984): Implements citation rendering in attachment blocks, which this PR also supports and integrates.

## Suggested reviewers

- fundon

## Poem

> A rabbit hops through code anew,  
> With signals bright and status true.  
> Attachments now with care are shown,  
> Errors caught and clearly known.  
> Reload with ease, no need to fear—  
> The resource block is crystal clear!  
> 🐇✨
"""

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

- 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: codecov[bot]
created_at: 2025-05-08T00:10:39Z
body: ## [Codecov](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?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 `30.61224%` with `102 lines` in your changes missing coverage. Please review.
> Project coverage is 53.80%. Comparing base [(`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) to head [(`b3f0f38`)](https://app.codecov.io/gh/toeverything/AFFiNE/commit/b3f0f38b41fec3143185e0139c36af08f889cf6f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything).
> Report is 2 commits behind head on canary.

| [Files with missing lines](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | Patch % | Lines |
|---|---|---|
| [...cksuite/affine/components/src/resource/resource.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&filepath=blocksuite%2Faffine%2Fcomponents%2Fsrc%2Fresource%2Fresource.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-YmxvY2tzdWl0ZS9hZmZpbmUvY29tcG9uZW50cy9zcmMvcmVzb3VyY2UvcmVzb3VyY2UudHM=) | 49.29% | [17 Missing and 19 partials :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |
| [...e/affine/blocks/attachment/src/attachment-block.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&filepath=blocksuite%2Faffine%2Fblocks%2Fattachment%2Fsrc%2Fattachment-block.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-YmxvY2tzdWl0ZS9hZmZpbmUvYmxvY2tzL2F0dGFjaG1lbnQvc3JjL2F0dGFjaG1lbnQtYmxvY2sudHM=) | 0.00% | [34 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |
| [...locksuite/affine/components/src/resource/status.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&filepath=blocksuite%2Faffine%2Fcomponents%2Fsrc%2Fresource%2Fstatus.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-YmxvY2tzdWl0ZS9hZmZpbmUvY29tcG9uZW50cy9zcmMvcmVzb3VyY2Uvc3RhdHVzLnRz) | 30.43% | [16 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |
| [blocksuite/affine/blocks/attachment/src/embed.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&filepath=blocksuite%2Faffine%2Fblocks%2Fattachment%2Fsrc%2Fembed.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-YmxvY2tzdWl0ZS9hZmZpbmUvYmxvY2tzL2F0dGFjaG1lbnQvc3JjL2VtYmVkLnRz) | 0.00% | [9 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |
| [...te/affine/blocks/attachment/src/configs/toolbar.ts](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&filepath=blocksuite%2Faffine%2Fblocks%2Fattachment%2Fsrc%2Fconfigs%2Ftoolbar.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-YmxvY2tzdWl0ZS9hZmZpbmUvYmxvY2tzL2F0dGFjaG1lbnQvc3JjL2NvbmZpZ3MvdG9vbGJhci50cw==) | 0.00% | [5 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) |
| [.../component/src/lit-react/lit-portal/lit-portal.tsx](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?src=pr&el=tree&filepath=packages%2Ffrontend%2Fcomponent%2Fsrc%2Flit-react%2Flit-portal%2Flit-portal.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything#diff-cGFja2FnZXMvZnJvbnRlbmQvY29tcG9uZW50L3NyYy9saXQtcmVhY3QvbGl0LXBvcnRhbC9saXQtcG9ydGFsLnRzeA==) | 33.33% | [2 Missing :warning: ](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180?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   #12180      +/-   ##
==========================================
- Coverage   54.13%   53.80%   -0.33%     
==========================================
  Files        2667     2669       +2     
  Lines      119393   119448      +55     
  Branches    19389    19323      -66     
==========================================
- Hits        64631    64267     -364     
- Misses      52678    53620     +942     
+ Partials     2084     1561     -523     
```

| [Flag](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180/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/12180/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `77.55% <ø> (-0.82%)` | :arrow_down: |
| [unittest](https://app.codecov.io/gh/toeverything/AFFiNE/pull/12180/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=toeverything) | `30.91% <30.61%> (-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/12180?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-10T08:34:26Z
body: ### Merge activity

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

author_association: CONTRIBUTOR