Skip to content

Conversation

@appflowy
Copy link
Contributor

@appflowy appflowy commented Jan 29, 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

Add folder outline change notifications with diff-based sidebar updates, enhance duplication and page actions UX, and update HTTP API outline responses to include folder revision IDs.

New Features:

  • Introduce FolderChanged workspace notification type and event to broadcast folder outline diffs across WebSocket and BroadcastChannel.
  • Support incremental sidebar outline updates by applying JSON Patch diffs and tracking folder revision IDs to ignore stale updates.
  • Add new Cypress E2E coverage for cross-tab sidebar sync, WebSocket-driven sidebar refresh, and cloud database duplication behavior.

Enhancements:

  • Update workspace outline loading to handle folderRid metadata and optimize outline refreshes, avoiding redundant reloads and non-visual changes.
  • Refine page and space action menus with explicit test IDs and callbacks for better UX and testability.
  • Rely on WebSocket folder outline notifications instead of manual outline reloads for page rename/create flows.

Build:

  • Add fast-json-patch dependency for applying folder outline JSON Patch diffs.

Tests:

  • Extend HTTP API integration tests to account for the new AppOutlineResponse shape.
  • Add Cypress helpers for expanding spaces by name and re-export them via TestTool for reuse.
  • Add E2E tests validating sidebar refresh via WebSocket, cross-tab synchronization via BroadcastChannel, and cloud database duplication with data independence.

Chores:

  • Adjust various integration tests to destructure outline from getAppOutline and align with the new response format.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 29, 2026

Reviewer's Guide

Adds real-time folder outline diff handling (including BroadcastChannel and WebSocket plumbing) and RID-based de‑duplication, refactors page/space actions to rely on these notifications instead of manual outline reloads, adjusts the app outline HTTP API to return folder metadata, and introduces Cypress/integration tests to verify sidebar refresh, cross-tab sync, and cloud database duplication.

Sequence diagram for folder outline change propagation via WebSocket and BroadcastChannel

sequenceDiagram
  participant Server
  participant WebSocketClient as WebSocket_client
  participant BroadcastChannel as Broadcast_channel
  participant SyncHook as useSync
  participant EventEmitter as eventEmitter
  participant WorkspaceData as useWorkspaceData
  participant BusinessLayer as AppBusinessLayer
  participant Sidebar as Sidebar_UI

  rect rgb(235,235,255)
    Server->>Server: Folder state change
    Server-->>WebSocketClient: WorkspaceNotification(folderChanged)
  end

  WebSocketClient-->>SyncHook: wsNotification.folderChanged
  SyncHook->>EventEmitter: emit(APP_EVENTS.FOLDER_OUTLINE_CHANGED, FolderChanged)

  par In_current_tab
    EventEmitter-->>WorkspaceData: FOLDER_OUTLINE_CHANGED(FolderChanged)
    WorkspaceData->>WorkspaceData: parseFolderRid(folderRid)
    WorkspaceData->>WorkspaceData: compareFolderRid(patchRid, lastFolderRidRef)
    alt stale_or_equal_RID
      WorkspaceData-->>Sidebar: no_update
    else newer_RID
      alt no_outlineDiffJson
        WorkspaceData->>WorkspaceData: loadOutline(workspaceId, force=false)
        WorkspaceData->>Sidebar: setOutline(full_outline)
      else has_outlineDiffJson
        WorkspaceData->>WorkspaceData: JSON.parse(outlineDiffJson)
        WorkspaceData->>WorkspaceData: isOnlyNonVisualOutlineChange(patch)
        alt only_non_visual_changes
          WorkspaceData->>WorkspaceData: updateLastFolderRid(patchRid)
        else visual_changes
          WorkspaceData->>WorkspaceData: applyPatch({outline}, patch)
          WorkspaceData->>Sidebar: setOutline(patched_outline)
          WorkspaceData->>EventEmitter: emit(APP_EVENTS.OUTLINE_LOADED, patched_outline)
          WorkspaceData->>WorkspaceData: updateLastFolderRid(patchRid)
        end
      end
    end
  and Cross_tab_via_BroadcastChannel
    WebSocketClient-->>BroadcastChannel: broadcast FolderChanged
    BroadcastChannel-->>SyncHook: bcNotification.folderChanged
    SyncHook->>EventEmitter: emit(APP_EVENTS.FOLDER_OUTLINE_CHANGED, FolderChanged)
    EventEmitter-->>WorkspaceData: FOLDER_OUTLINE_CHANGED(FolderChanged)
    WorkspaceData->>WorkspaceData: same_RID_and_patch_logic_as_above
    WorkspaceData-->>Sidebar: sidebar_updated_in_other_tab
  end

  rect rgb(235,255,235)
    EventEmitter-->>BusinessLayer: FOLDER_OUTLINE_CHANGED
    BusinessLayer->>BusinessLayer: skipNextFolderOutlineRefreshRef = true
    BusinessLayer->>BusinessLayer: skipNextFolderOutlineRefreshUntilRef = now + TTL
    BusinessLayer->>Sidebar: prevent_duplicate_refresh_after_collab_update
  end
Loading

Class diagram for notification.FolderChanged and related outline types

classDiagram
  class notification_WorkspaceNotification {
    <<message>>
    +profileChange
    +permissionChanged
    +sectionChanged
    +shareViewsChanged
    +mentionablePersonListChanged
    +serverLimit
    +workspaceMemberProfileChanged
    +folderChanged : notification_FolderChanged
    +payload : enum
  }

  class notification_FolderChanged {
    <<message>>
    +outlineDiffJson : string
    +folderRid : string
    +_folderRid : string
    +create(properties)
    +encode(message, writer)
    +encodeDelimited(message, writer)
    +decode(reader, length)
    +decodeDelimited(reader)
    +verify(message)
    +fromObject(object)
    +toObject(message, options)
    +toJSON()
    +getTypeUrl(typeUrlPrefix)
  }

  class AppOutlineResponse {
    +outline : View[]
    +folderRid : string
  }

  class View {
    +folder_rid : string
    +view_id : string
    +name : string
    +icon
    +extra : ViewExtra
  }

  notification_WorkspaceNotification --> notification_FolderChanged : folderChanged
  AppOutlineResponse --> View : outline

  class ViewExtra {
  }

  View --> ViewExtra : extra
Loading

File-Level Changes

Change Details Files
Extend workspace notification protocol to carry folder outline changes and expose them to the app event system.
  • Add FolderChanged message to notification proto, including outlineDiffJson and folderRid fields with full encode/decode/verify helpers.
  • Extend WorkspaceNotification oneof payload with folderChanged and wire it into encode/decode/fromObject/toObject/verify.
  • Update TypeScript declarations to include notification.FolderChanged and the new WorkspaceNotification.folderChanged field.
src/proto/messages.js
src/proto/messages.d.ts
src/proto/notification.proto
Handle incremental folder outline diffs on the client, with RID-based staleness checks and non-visual-change filtering, and emit a unified FOLDER_OUTLINE_CHANGED event.
  • Introduce FolderRid parsing/comparison helpers and track last applied RID in useWorkspaceData via a ref and updateLastFolderRid callback.
  • Parse outlineDiffJson JSON Patch payloads from FolderChanged notifications, skip stale patches based on folderRid, and short-circuit when only non-visual outline fields change.
  • Apply outline diffs using fast-json-patch (handling both full /outline replace and generic patch cases) while preserving the hidden share-with-me space, updating stableOutlineRef, state, and broadcasting OUTLINE_LOADED.
  • Wire FOLDER_OUTLINE_CHANGED into useSync for both WebSocket and BroadcastChannel notifications and register the new APP_EVENTS.FOLDER_OUTLINE_CHANGED constant.
  • In AppBusinessLayer, listen for FOLDER_OUTLINE_CHANGED to set skipNextFolderOutlineRefresh flags instead of wrapping loadOutline, and adjust usePageOperations/updatePage* to rely on WS-driven sidebar refresh instead of manual outline reloads.
  • Adjust AIChat and workspace outline loading to work with the new outline response shape and WebSocket-based refresh flow.
src/components/app/hooks/useWorkspaceData.ts
src/components/ws/useSync.ts
src/application/constants.ts
src/components/app/layers/AppBusinessLayer.tsx
src/components/app/hooks/usePageOperations.ts
src/components/ai-chat/AIChat.tsx
package.json
pnpm-lock.yaml
Change getAppOutline HTTP API shape to include folder RID and update call sites and types accordingly.
  • Make getAppOutline return an AppOutlineResponse containing outline array and folderRid (plumbed from folder_rid) instead of a bare array.
  • Introduce AppOutlineResponse type and update AppService.getAppOutline signature.
  • Update all integration tests that relied on getAppOutline to destructure { outline } and adapt outline assertions accordingly.
  • Update View type to optionally carry folder_rid metadata when present.
src/application/services/js-services/http/http_api.ts
src/application/services/services.type.ts
src/application/types.ts
src/application/services/js-services/http/__tests__/publish.integration.test.ts
src/application/services/js-services/http/__tests__/page.integration.test.ts
src/application/services/js-services/http/__tests__/collab.integration.test.ts
src/application/services/js-services/http/__tests__/invitation.integration.test.ts
src/application/services/js-services/http/__tests__/auth.integration.test.ts
src/application/services/js-services/http/__tests__/file-import.integration.test.ts
src/application/services/js-services/http/__tests__/view.integration.test.ts
Refine UI actions for spaces and pages, adding deterministic test hooks and minor behavior tweaks.
  • Refactor MoreSpaceActions to inline menu items instead of mapping an actions array, wrap manage handler in useCallback, and add data-testid attributes for manage, duplicate, and delete actions.
  • Update page header MoreActionsContent to use useCallback for duplicate handler, add test ids for duplicate and move-to actions, and simplify ref handling via a callback ref.
  • Add expandSpaceByName helper for Cypress tests and expose it through page-utils and TestTool API.
src/components/app/view-actions/MoreSpaceActions.tsx
src/components/app/header/MoreActionsContent.tsx
cypress/support/page/flows.ts
cypress/support/page-utils.ts
Add Cypress E2E coverage for sidebar refresh (via WebSocket), cross-tab sync (via BroadcastChannel), and cloud database duplication behavior.
  • Introduce cross-tab-sync.cy.ts to spin up an iframe instance, perform page create/delete in one context, and assert sidebar updates in the other via BroadcastChannel events.
  • Add document-sidebar-refresh.cy.ts to verify sidebar reacts to WebSocket-driven folder outline changes when creating/renaming documents and AI chats via the inline add controls.
  • Create database-duplicate-cloud.cy.ts to log in as a fixed cloud user, duplicate a seeded database, verify row counts and row document independence between original and copy, and clean up the duplicate.
  • Relax Cypress global uncaught exception handling in these specs and template-duplication.cy.ts to ignore a set of known benign errors, including the new _dEH-related issues.
cypress/e2e/page/cross-tab-sync.cy.ts
cypress/e2e/page/document-sidebar-refresh.cy.ts
cypress/e2e/database/database-duplicate-cloud.cy.ts
cypress/e2e/page/template-duplication.cy.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

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.

2 participants