Skip to content

Fix duplicate#309

Open
appflowy wants to merge 8 commits intomainfrom
fix_duplicate
Open

Fix duplicate#309
appflowy wants to merge 8 commits intomainfrom
fix_duplicate

Conversation

@appflowy
Copy link
Copy Markdown
Contributor

@appflowy appflowy commented Apr 13, 2026

Description


Checklist

General

  • I've included relevant documentation or comments for the changes introduced.
  • I've tested the changes in multiple environments (e.g., different browsers, operating systems).

Testing

  • I've added or updated tests to validate the changes introduced for AppFlowy Web.

Feature-Specific

  • For feature additions, I've added a preview (video, screenshot, or demo) in the "Feature Preview" section.
  • I've verified that this feature integrates seamlessly with existing functionality.

Summary by Sourcery

Improve page, block, and database duplication to avoid stale or empty content and better integrate with server-side duplication flows.

New Features:

  • Support deep duplication of inline databases and database row documents when duplicating blocks or rows.
  • Expose page and row document duplication operations through app, editor, and database contexts for reuse across UI surfaces.
  • Add password-based auth test helpers and selectors to support new end-to-end flows.

Bug Fixes:

  • Prevent duplicated database rows and row-detail documents from ending up empty or out of sync with their source content.
  • Ensure row sub-documents reload from the server when the local cache only contains an empty shell, especially after duplication.
  • Fix page and database duplication from header and block controls to use the correct container view in database contexts and handle errors gracefully.
  • Ensure all relevant collab docs, including unseen rows and row-detail documents, are synced before server-side duplicate operations to avoid partial copies.
  • Make row-detail dialogs reliably closable via Escape in tests by handling editor focus and adding fallbacks.

Enhancements:

  • Refine page duplication API to accept options like target parent, suffix, and whether to open the duplicate or include children.
  • Centralize and reuse page duplication logic via a new page-operations helper and propagate it through app and editor layers.
  • Improve robustness and logging of document loaders and view loader retry logic for row sub-documents and inline databases.
  • Tighten database row meta handling so document-emptiness flags and icon/cover metadata are set more accurately.
  • Simplify and normalize several Playwright helpers for grids, filters, and fields for more reliable selectors and interactions.

Tests:

  • Extend Playwright helpers and selectors to better cover block controls, database interactions, filters, and new auth flows, including password-based signup/signin.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 13, 2026

Reviewer's Guide

Implements robust, deep duplication behavior for pages, database blocks, and rows (including row sub-documents) while tightening sync semantics and wiring the new duplication APIs through editor, database, and app contexts; also cleans up some Playwright helpers and auth utilities.

Sequence diagram for page duplicate via MoreActions menu

sequenceDiagram
  actor User
  participant RightMenu
  participant MoreActionsContent
  participant SyncInternalContext as SyncInternal
  participant useBatchSync as BatchSync
  participant CollabAPI as collabFullSyncBatch
  participant PageService
  participant PageAPI as page_api_duplicatePage
  participant ViewService
  participant Outline as OutlineCache

  User->>RightMenu: click_more_actions(viewId)
  RightMenu->>MoreActionsContent: render(viewId)

  User->>MoreActionsContent: click_duplicate()
  MoreActionsContent->>MoreActionsContent: itemClicked()
  MoreActionsContent->>MoreActionsContent: showBlockingLoader()

  rect rgb(230,230,250)
    MoreActionsContent->>SyncInternal: syncAllToServer(workspaceId)
    SyncInternal->>BatchSync: syncAllToServer(workspaceId)
    BatchSync->>BatchSync: flushAllSync()
    BatchSync->>BatchSync: collect_registered_collabs()
    BatchSync->>BatchSync: load_unregistered_row_docs()
    BatchSync->>BatchSync: load_unregistered_row_subdocs()
    BatchSync->>CollabAPI: collabFullSyncBatch(workspaceId,items)
    CollabAPI-->>BatchSync: ok_or_error
    BatchSync-->>SyncInternal: resolved(ignore_errors)
  end

  MoreActionsContent->>PageService: duplicate(workspaceId,viewId,options)
  PageService->>PageAPI: duplicatePage(workspaceId,viewId,options)
  PageAPI->>PageAPI: build_payload(parent_view_id,include_children,suffix,source,open_after_duplicate)
  PageAPI->>PageAPI: POST /page-view/{viewId}/duplicate
  PageAPI-->>PageService: success
  PageService-->>MoreActionsContent: success

  MoreActionsContent->>OutlineCache: refreshOutline(workspaceId)
  OutlineCache-->>MoreActionsContent: updated_outline
  MoreActionsContent->>ViewService: invalidateCache(workspaceId,parentViewId)
  MoreActionsContent->>ViewService: loadViewChildren(parentViewId)
  ViewService-->>MoreActionsContent: children_with_duplicate

  MoreActionsContent->>MoreActionsContent: hideBlockingLoader()
  MoreActionsContent-->>User: duplicate_complete
Loading

Sequence diagram for inline database block duplicate from ControlsMenu

sequenceDiagram
  actor User
  participant ControlsMenu
  participant Editor as YjsEditor
  participant CustomEditor
  participant duplicateDatabaseBlock
  participant ViewService
  participant ViewAPI as getView
  participant PageOps as duplicatePage
  participant DBViewAPI as createDatabaseView

  User->>ControlsMenu: open_controls_for_block()
  ControlsMenu->>ControlsMenu: compute_options(selectedBlockIds)

  User->>ControlsMenu: click_duplicate_blocks()
  ControlsMenu->>ControlsMenu: duplicateSelectedBlocks()

  loop for_each_selected_block
    ControlsMenu->>Editor: findSlateEntryByBlockId(blockId)
    Editor-->>ControlsMenu: [node,path]
    ControlsMenu->>CustomEditor: duplicateBlock(editor,blockId,prevSiblingId)
    CustomEditor-->>ControlsMenu: duplicatedBlockId

    alt node_is_database_block
      ControlsMenu->>duplicateDatabaseBlock: duplicateDatabaseBlock(sourceNode,duplicatedBlockId)

      duplicateDatabaseBlock->>duplicateDatabaseBlock: parse_source_metadata(parentId,viewIds,databaseId)

      alt linked_database_block
        duplicateDatabaseBlock->>DBViewAPI: createDatabaseView(parentId,payload) * view_count
        DBViewAPI-->>duplicateDatabaseBlock: view_id_list
        duplicateDatabaseBlock->>duplicateDatabaseBlock: replaceDuplicatedBlockData(new_view_ids,existing_database_id)
      else inline_database_block
        duplicateDatabaseBlock->>ViewService: invalidateCache(workspaceId,sourceContainerId)
        duplicateDatabaseBlock->>ViewService: invalidateCache(workspaceId,parentId)
        duplicateDatabaseBlock->>ViewAPI: getView(workspaceId,sourceContainerId,2)
        ViewAPI-->>duplicateDatabaseBlock: sourceContainerView
        duplicateDatabaseBlock->>ViewAPI: getView(workspaceId,parentId,2) as beforeParentView
        ViewAPI-->>duplicateDatabaseBlock: beforeParentView

        duplicateDatabaseBlock->>PageOps: duplicatePage(sourceContainerId,options)
        PageOps-->>duplicateDatabaseBlock: success

        loop poll_for_duplicated_container(max_attempts)
          duplicateDatabaseBlock->>ViewService: invalidateCache(workspaceId,parentId)
          duplicateDatabaseBlock->>ViewAPI: getViewNoCache(workspaceId,parentId,2)
          ViewAPI-->>duplicateDatabaseBlock: afterParentView
          duplicateDatabaseBlock->>duplicateDatabaseBlock: findDuplicatedContainerChild(beforeChildren,afterChildren)
        end

        duplicateDatabaseBlock->>ViewAPI: getViewNoCache(workspaceId,duplicatedContainerId,2)
        ViewAPI-->>duplicateDatabaseBlock: duplicatedContainer
        duplicateDatabaseBlock->>duplicateDatabaseBlock: compute_new_view_ids_and_database_id()
        duplicateDatabaseBlock->>duplicateDatabaseBlock: replaceDuplicatedBlockData(new_view_ids,new_database_id)
      end
    end
  end

  ControlsMenu->>Editor: ReactEditor.focus(editor)
  ControlsMenu->>Editor: select(start_of_first_duplicated_block)
  ControlsMenu-->>User: duplicate_done
  alt any_database_block_duplicated
    ControlsMenu->>ControlsMenu: notify.success(duplicateSuccessfully)
  end
Loading

Sequence diagram for duplicate row with sub-document

sequenceDiagram
  actor User
  participant DatabaseGrid
  participant useDuplicateRowDispatch as DuplicateRowDispatch
  participant DatabaseContext
  participant Cache as RowSubDocCache
  participant DBOps as useDatabaseOperations
  participant CollabAPI as duplicateRowDocumentAPI

  User->>DatabaseGrid: duplicate_row(referenceRowId)
  DatabaseGrid->>DuplicateRowDispatch: duplicateRow(referenceRowId)

  DuplicateRowDispatch->>DuplicateRowDispatch: lookup_reference_row(rowMap)
  DuplicateRowDispatch->>DuplicateRowDispatch: read_reference_meta(icon,cover,documentId,isEmptyDocument)
  DuplicateRowDispatch->>DuplicateRowDispatch: generateRowMeta(new_row_id,IsDocumentEmpty_false,icon,cover)
  DuplicateRowDispatch->>DatabaseContext: createRow(rowKey)
  DatabaseContext-->>DuplicateRowDispatch: rowDoc

  DuplicateRowDispatch->>DuplicateRowDispatch: initialDatabaseRow(new_row_id,databaseId,rowDoc)
  DuplicateRowDispatch->>DuplicateRowDispatch: clone_cells_from_reference_row()
  DuplicateRowDispatch->>DuplicateRowDispatch: write_meta(newMeta)
  DuplicateRowDispatch->>DuplicateRowDispatch: insert_row_order_after_reference()

  alt DatabaseContext_has_duplicateRowDocument
    DuplicateRowDispatch->>DatabaseContext: duplicateRowDocument(databaseId,referenceRowId,new_row_id,clientDocStateB64?)
    activate DatabaseContext
      alt reference_has_documentId
        DatabaseContext->>Cache: getCachedRowSubDoc(documentId)
        Cache-->>DatabaseContext: cachedDoc_or_undefined
        alt cachedDoc_exists
          DatabaseContext->>DatabaseContext: encode_state_as_update(cachedDoc)
          DatabaseContext->>DatabaseContext: base64_encode_state_chunked()
          DatabaseContext->>DBOps: duplicateRowDocument(databaseId,referenceRowId,new_row_id,clientDocStateB64)
        else no_cachedDoc
          DatabaseContext->>DBOps: duplicateRowDocument(databaseId,referenceRowId,new_row_id,undefined)
        end
      else no_reference_documentId
        DatabaseContext->>DBOps: duplicateRowDocument(databaseId,referenceRowId,new_row_id,undefined)
      end
    deactivate DatabaseContext

    DBOps->>CollabAPI: duplicateRowDocument(workspaceId,databaseId,sourceRowId,newRowId,clientDocStateB64)
    CollabAPI-->>DBOps: void
  else no_duplicateRowDocument
    DuplicateRowDispatch->>DuplicateRowDispatch: skip_server_document_duplicate()
  end

  DuplicateRowDispatch-->>DatabaseGrid: return new_row_id
  DatabaseGrid-->>User: row_and_document_duplicated
Loading

Updated class diagram for duplication-related types and contexts

classDiagram
  class DuplicatePageOptions {
    +string parentViewId
    +boolean openAfterDuplicate
    +boolean includeChildren
    +string suffix
    +number source
  }

  class ViewComponentProps {
    +function createRowDocument(documentId:string) Promise~Uint8Array|
    +function duplicateRowDocument(databaseId:string,sourceRowId:string,newRowId:string,clientDocStateB64:string) Promise~void|
    +ViewMetaProps viewMeta
    +function appendBreadcrumb(breadcrumb:AppendBreadcrumb) void
    +function onRendered() void
    +function updatePage(viewId:string,data:UpdatePagePayload) Promise~void|
    +function addPage(parentId:string,payload:CreatePagePayload) Promise~CreatePageResponse|
    +function deletePage(viewId:string) Promise~void|
    +function duplicatePage(viewId:string,options:DuplicatePageOptions) Promise~void|
    +function openPageModal(viewId:string) void
  }

  class AppOperationsContextType {
    +function addPage(parentId:string,payload:CreatePagePayload) Promise~CreatePageResponse|
    +function openPageModal(viewId:string) void
    +function deletePage(viewId:string) Promise~void|
    +function duplicatePage(viewId:string,options:DuplicatePageOptions) Promise~void|
    +function updatePage(viewId:string,payload:UpdatePagePayload) Promise~void|
    +function updatePageIcon(workspaceId:string,viewId:string,icon:unknown) Promise~void|
    +function updatePageName(viewId:string,name:string) Promise~void|
    +function checkIfRowDocumentExists(documentId:string) Promise~boolean|
    +function loadRowDocument(documentId:string) Promise~YDoc|
    +function createRowDocument(documentId:string) Promise~Uint8Array|
    +function duplicateRowDocument(databaseId:string,sourceRowId:string,newRowId:string,clientDocStateB64:string) Promise~void|
  }

  class EditorContextState {
    +string workspaceId
    +function addPage(parentId:string,payload:CreatePagePayload) Promise~CreatePageResponse|
    +function deletePage(viewId:string) Promise~void|
    +function duplicatePage(viewId:string,options:DuplicatePageOptions) Promise~void|
    +function openPageModal(viewId:string) void
    +function createDatabaseView(viewId:string,payload:CreateDatabaseViewPayload) Promise~CreateDatabaseViewResponse|
    +function navigateToView(viewId:string,blockId:string) Promise~void|
    +function createRow(rowKey:string) Promise~YDoc|
    +function generateAISummaryForRow(payload:GenerateAISummaryRowPayload) Promise~string|
  }

  class DatabaseContextState {
    +string workspaceId
    +function createRow(rowKey:string) Promise~YDoc|
    +function checkIfRowDocumentExists(documentId:string) Promise~boolean|
    +function loadRowDocument(documentId:string) Promise~YDoc|
    +function createRowDocument(documentId:string) Promise~Uint8Array|
    +function duplicateRowDocument(databaseId:string,sourceRowId:string,newRowId:string,clientDocStateB64:string) Promise~void|
    +function navigateToView(viewId:string,blockId:string) Promise~void|
    +function createDatabaseView(viewId:string,payload:CreateDatabaseViewPayload) Promise~CreateDatabaseViewResponse|
    +function updatePage(viewId:string,payload:UpdatePagePayload) Promise~void|
    +function addPage(parentId:string,payload:CreatePagePayload) Promise~CreatePageResponse|
    +function openPageModal(viewId:string) void
  }

  class Database2Props {
    +string workspaceId
    +function createRow(rowKey:string) Promise~YDoc|
    +function checkIfRowDocumentExists(documentId:string) Promise~boolean|
    +function loadRowDocument(documentId:string) Promise~YDoc|
    +function createRowDocument(documentId:string) Promise~Uint8Array|
    +function duplicateRowDocument(databaseId:string,sourceRowId:string,newRowId:string,clientDocStateB64:string) Promise~void|
    +function navigateToView(viewId:string,blockId:string) Promise~void|
    +function addPage(parentId:string,payload:CreatePagePayload) Promise~CreatePageResponse|
    +function openPageModal(viewId:string) void
    +function updatePage(viewId:string,payload:UpdatePagePayload) Promise~void|
    +function deletePage(viewId:string) Promise~void|
  }

  class PageAPI {
    +function duplicatePage(workspaceId:string,viewId:string,options:DuplicatePageOptions) Promise~void|
  }

  ViewComponentProps ..> DuplicatePageOptions
  AppOperationsContextType ..> DuplicatePageOptions
  EditorContextState ..> DuplicatePageOptions
  DatabaseContextState ..> DuplicatePageOptions
  Database2Props ..> DuplicatePageOptions
  PageAPI ..> DuplicatePageOptions

  EditorContextState ..> AppOperationsContextType : created_from
  DatabaseContextState ..> AppOperationsContextType : uses
  Database2Props ..> DatabaseContextState : provided_to
  ViewComponentProps ..> AppOperationsContextType : consumes
Loading

File-Level Changes

Change Details Files
Add deep, database-aware block duplication from the editor controls menu with error handling and async support.
  • Refactor block duplicate action into an async duplicateSelectedBlocks callback that preserves selection order and focuses the first duplicated block.
  • Detect database blocks during duplication and delegate to duplicateDatabaseBlock for additional wiring.
  • Wire duplicateSelectedBlocks into the toolbar options and update ControlMenu option handlers to support async functions with centralized error notification.
  • Introduce duplicateCopySuffix derived from i18n for consistent copy naming.
src/components/editor/components/toolbar/block-controls/ControlsMenu.tsx
Implement database block duplication that re-creates or reuses database views and containers correctly, including polling and cache invalidation.
  • Add helper getViewNoCache and import ViewService/getView/database utils needed for database duplication logic.
  • When duplicating linked databases belonging to the same parent, create new embedded database views for each source view and update duplicated block data with new IDs.
  • When duplicating inline databases (folder containers), call duplicatePage on the container, then poll parent view children via cache-busted getViewNoCache to find the new container, ensuring new view/database IDs and preserving tab count.
  • Update duplicated block Yjs data with createDatabaseNodeData, ensuring view_ids and database_id point at the new database.
src/components/editor/components/toolbar/block-controls/ControlsMenu.tsx
Strengthen pre-duplicate sync by batching all collab documents and row/row-subdocs to the server, including unregistered rows and closed row-detail documents.
  • Extend syncAllToServer to gather registered collab contexts, all database rows (including those never opened), and all cached row sub-documents, encoding their state and adding them to a batch payload.
  • Open unregistered row docs via IndexedDB with skipCache and push non-empty row states as DatabaseRow collabs, skipping empty shells to avoid overwriting real server data.
  • Scan cached row sub-document IDs and, for any not already registered, load document collabs (from cache or IndexedDB) and include them as Document collabs in the batch.
  • Preserve abortable, retried collabFullSyncBatch behavior with exponential backoff and log outcomes; keep API the same for callers.
src/components/ws/sync/useBatchSync.ts
Move row duplication logic into a dedicated dispatch module and extend it to deep-copy row documents via a new backend API, while fixing meta handling.
  • Extract useDuplicateRowDispatch into src/application/database-yjs/dispatch/row.ts and re-export it from dispatch.ts to keep public API stable.
  • When duplicating a row, always mark RowMetaKey.IsDocumentEmpty as false and only write non-null/undefined meta keys to Yjs meta map.
  • After creating the new row and inserting it into all views, attempt to call duplicateRowDocument from DatabaseContext to deep-copy the row document on the server, optionally passing base64-encoded client doc state if available in cache.
  • Gracefully log but ignore duplicateRowDocument failures so row duplication still succeeds even if the document deep-copy fails.
src/application/database-yjs/dispatch.ts
src/application/database-yjs/dispatch/row.ts
Introduce a server API and client wiring for row document duplication, and make row sub-doc loading more robust against empty-shell cache entries.
  • Add duplicateRowDocument HTTP API in collab-api.ts and wrap it in useDatabaseOperations as duplicateRowDocument, exposed through AppBusinessLayer and AppOperationsContext.
  • Propagate duplicateRowDocument and duplicatePage capabilities through EditorContext, DatabaseContext, Database component, AppPage, and ViewModal so both inline and modal database UIs can request deep row document duplication.
  • Export getCachedRowSubDocIds alongside getCachedRowSubDoc, and adapt openRowSubDocument to treat cache entries with <=1 blocks as empty shells, fetching from server with retry/backoff when necessary.
  • Update DatabaseRowSubDocument logic to update IsDocumentEmpty meta immediately upon edit and then ensure the document exists on the server, and to suppress openPageModal in the embedded editor context to avoid nested modals.
src/application/services/js-services/http/collab-api.ts
src/components/app/hooks/useDatabaseOperations.ts
src/components/app/layers/AppBusinessLayer.tsx
src/components/app/contexts/AppOperationsContext.ts
src/components/editor/EditorContext.tsx
src/application/database-yjs/context.ts
src/application/services/js-services/cache/index.ts
src/application/view-loader/index.ts
src/components/database/components/database-row/DatabaseRowSubDocument.tsx
src/components/database/Database.tsx
src/pages/AppPage.tsx
Add richer page duplication options and route them through the app header and page operations, aligning block-level and page-level duplication behavior.
  • Extend page-api.duplicatePage to accept DuplicatePageOptions (suffix, parentViewId, includeChildren, openAfterDuplicate, source) and send them to the backend.
  • Add DuplicatePageOptions type and plumb duplicatePage through ViewComponentProps, Editor/App operations contexts, usePageOperations, AppBusinessLayer, AppPage, and ViewModal.
  • In MoreActionsContent (page header menu), wrap pre-duplicate batch sync in an 8s timeout and call PageService.duplicate with options to open, include children, add localized copy suffix, and tag the source; then refresh outline and parent children.
  • In ControlsMenu’s database duplication flow, use duplicatePage with matching options when duplicating containers inline so database copies are consistent with full-page duplicate.
src/application/services/js-services/http/page-api.ts
src/application/types.ts
src/components/app/hooks/usePageOperations.ts
src/components/app/layers/AppBusinessLayer.tsx
src/components/app/contexts/AppOperationsContext.ts
src/pages/AppPage.tsx
src/components/app/header/MoreActionsContent.tsx
src/components/editor/components/toolbar/block-controls/ControlsMenu.tsx
Improve top-right header actions so share/duplicate operate on database containers when viewing database rows or tabs, while keeping presence scoped to the actual doc.
  • Update RightMenu to derive an actionViewId: when the current view’s parent is a database container, use the container’s view_id for Share and MoreActions, otherwise use the route viewId.
  • Continue to pass the original route viewId to Users so presence tracking remains at the currently open document/row.
  • Use app outline plus findView/isDatabaseContainer helpers to resolve parent container reliably.
src/components/app/header/RightMenu.tsx
Tighten database row meta generation and empty-document heuristics used by sub-documents and document loader utilities.
  • Change generateRowMeta so it only writes RowMetaKey.IsDocumentEmpty when a non-null/undefined value is passed, avoiding accidental overwrites with default true.
  • Simplify isDocumentEmptyResolved in DatabaseRowSubDocument and adjust logging and ensureRowDocumentExists retries for better diagnostics.
  • Enhance useDocumentLoader to log retries, handle missing loadView gracefully, and use Log instead of console.error.
src/application/database-yjs/row_meta.ts
src/components/database/components/database-row/DatabaseRowSubDocument.tsx
src/components/editor/components/blocks/database/hooks/useDocumentLoader.ts
Refine test utilities for auth, rows, filters, grids, and block controls to support new duplication flows and password-based login flows.
  • Normalize Playwright helper formatting and selector usage (e.g., .first(), JS evaluate clicks) across field-type and filter helpers, and add BlockSelectors for the add-block button and controls menu/actions.
  • Enhance row-detail helpers so Escape is pressed up to 3 times and falls back to clicking the close button, ensuring dialog reliably closes in tests.
  • Add password-based auth helpers: createConfirmedPasswordUser, generalized generateActionLink, waitForAppReady, signUpAndLoginWithPasswordViaUi, and signInWithPasswordViaUi, and refactor signInAndWaitForApp to reuse waitForAppReady for consistent app-ready checks.
playwright/support/field-type-helpers.ts
playwright/support/filter-test-helpers.ts
playwright/support/row-detail-helpers.ts
playwright/support/auth-flow-helpers.ts
playwright/support/auth-utils.ts
playwright/support/selectors.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The new database/page duplication logic introduces multiple hard-coded retry/timeout values (e.g. polling loops in duplicateDatabaseBlock, openRowSubDocument, and the 8s pre-sync timeout); consider centralizing these constants or using a shared helper to make tuning and reasoning about retry behavior easier and avoid subtle inconsistencies.
  • The inline duplication flow in ControlsMenu and useDuplicateRowDispatch now performs multiple async steps (HTTP calls, polling, base64 encoding large Yjs updates) directly in UI handlers; extracting some of this into reusable utilities (e.g. a duplicateRowDocumentWithState helper and a generic pollForNewChildView) would simplify the components and reduce the risk of subtle bugs in the orchestration logic.
  • There are now several places constructing the duplicate copy suffix via t('menuAppHeader.pageNameSuffix') (e.g. MoreActionsContent, ControlsMenu); consider extracting a small helper to compute this suffix so behavior stays consistent across different duplication entry points and is easier to adjust in the future.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new database/page duplication logic introduces multiple hard-coded retry/timeout values (e.g. polling loops in `duplicateDatabaseBlock`, `openRowSubDocument`, and the 8s pre-sync timeout); consider centralizing these constants or using a shared helper to make tuning and reasoning about retry behavior easier and avoid subtle inconsistencies.
- The inline duplication flow in `ControlsMenu` and `useDuplicateRowDispatch` now performs multiple async steps (HTTP calls, polling, base64 encoding large Yjs updates) directly in UI handlers; extracting some of this into reusable utilities (e.g. a `duplicateRowDocumentWithState` helper and a generic `pollForNewChildView`) would simplify the components and reduce the risk of subtle bugs in the orchestration logic.
- There are now several places constructing the duplicate copy suffix via `t('menuAppHeader.pageNameSuffix')` (e.g. `MoreActionsContent`, `ControlsMenu`); consider extracting a small helper to compute this suffix so behavior stays consistent across different duplication entry points and is easier to adjust in the future.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

appflowy and others added 3 commits April 13, 2026 09:51
- Fix closeRowDetailWithEscape: defocus editor before Escape, use
  backdrop click as fallback instead of clicking the expand button
  (which navigated to full page instead of closing)
- Fix typeInRowDocument: scroll to editor, use data-testid selector,
  use keyboard.type instead of pressSequentially for Slate compat
- Fix row-document focus test: add Yjs sync wait and scroll on reopen
- Fix row-document indicator test: defocus editor before close
- Fix duplicate-row-doc-content test: use full-page editor for typing,
  reload-based retry for server-side async document duplication
- Memoize duplicateCopySuffix in MoreActionsContent and ControlsMenu
  to prevent unnecessary useCallback recreation (react best practice)
- Fix duplicate view-id mapping to use index-based matching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant