Skip to content

fix(outline): scope sidebar expand state per workspace, expand selected page's ancestors - #540

Open
breken-ai wants to merge 1 commit into
AppFlowy-IO:mainfrom
breken-ai:fix-workspace-expand-state
Open

breken-ai wants to merge 1 commit into
AppFlowy-IO:mainfrom
breken-ai:fix-workspace-expand-state

Conversation

@breken-ai

@breken-ai breken-ai commented Sep 9, 2026

Copy link
Copy Markdown

Fixes #526.

Confirmed both causes from the report against current main and put together a fix:

  1. Expand state is stored in a single global localStorage key (outline_expanded in src/components/_shared/outline/utils.ts). The validator effect in Outline.tsx prunes any restored id it can't find in the current workspace's outline - so after a switch, workspace A's leftover ids are validated against workspace B's tree and deleted, and each workspace's saved state keeps clobbering the other's.
  2. On a workspace switch the app navigates to the last-opened page, which is already present in the freshly loaded deep outline. The navigation-hydration effect returns early on if (findView(outline, selectedViewId)) return;, so the ancestor-expansion path never runs and the space containing the selected page stays collapsed.

Changes:

  • Scope expand state per workspace (outline_expanded_<workspaceId>) with a legacy-key read fallback so existing state migrates on first write.
  • When the selected view is already in the outline, expand its ancestor chain - once per selected page (ref-gated), so manually collapsing a container containing the selected page still sticks.
  • Thread the workspace id through Outline, OutlineItem (favorites/recents), and DatabaseView's hydration persistence.
  • Tests: updated the navigation-context and database-container suites for the new behavior, plus a guard test that unrelated collapsed containers are never auto-expanded and the OUTLINE_EXPAND_PATH event flow is unchanged.

Verification: the three touched jest suites (OutlineItem.databaseContainer, Outline.navigationContext, DatabaseView.databaseContainer) run green locally, 25/25 passed (pnpm 10.9.0, --no-coverage --maxWorkers=1).

Built by breken, your AI support engineer - breken.ai - this one's on us.

Summary by Sourcery

Fix workspace outline state restoration and reveal the selected page’s containing hierarchy.

Bug Fixes:

  • Scope outline expansion state to individual workspaces while preserving existing users’ legacy expansion data.
  • Automatically expand the selected page’s ancestor chain when the page is already present in the loaded outline, including after workspace switches.

Enhancements:

  • Thread workspace context through outline expansion persistence and maintain manual collapsing behavior after selected-page reveal.

Tests:

  • Update outline navigation and database-container tests to cover workspace-scoped persistence, selected-page ancestor expansion, unrelated collapsed containers, and unchanged event-driven expansion.

…ed page's ancestors

Fixes AppFlowy-IO#526. Expand state lived in one global localStorage key, so the validator
pruned each workspace's saved ids against the other's outline after every switch,
and the navigation-hydration effect returned early for an already-present
selected page, leaving its space collapsed.
@sourcery-ai

sourcery-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes sidebar state leakage across workspaces by introducing workspace-scoped expansion storage with legacy fallback, and fixes navigation visibility by expanding the selected page’s ancestor chain when that page is already in the loaded outline. The implementation threads workspace context through all persistence and validation paths, gates automatic expansion per selected page, and adds regression coverage for isolation and non-invasive expansion behavior.

Sequence diagram for workspace-scoped outline restoration

sequenceDiagram
    participant Workspace as Workspace switch
    participant Outline
    participant Storage as localStorage
    participant Validator as Outline validator

    Workspace->>Outline: load outline for currentWorkspaceId
    Outline->>Storage: getOutlineExpands(currentWorkspaceId)
    Storage-->>Outline: outline_expanded_workspaceId or legacy outline_expanded
    Outline->>Validator: validate restored expanded ids against workspace outline
    Validator->>Storage: setOutlineExpands(viewId, false, currentWorkspaceId)
    Outline-->>Workspace: restore isolated expansion state
Loading

Sequence diagram for selected page ancestor expansion

sequenceDiagram
    participant Navigation
    participant Outline
    participant Storage as localStorage
    participant Sidebar

    Navigation->>Outline: selectedViewId changes
    Outline->>Outline: findView(outline, selectedViewId)
    alt selected page already in outline
        Outline->>Outline: findViewAncestorIds(outline, selectedViewId)
        Outline->>Storage: setOutlineExpands(ancestorId, true, currentWorkspaceId)
        Outline->>Sidebar: expandHydratedPath(ancestorIds)
    else selected page missing
        Outline->>Outline: ensureViewVisibleInOutline(selectedViewId)
        Outline->>Storage: setOutlineExpands(ancestorId, true, currentWorkspaceId)
        Outline->>Sidebar: expandHydratedPath(ancestorIds)
    end
Loading

File-Level Changes

Change Details Files
Scope outline expansion persistence to the active workspace while preserving legacy state migration.
  • Derive workspace-specific localStorage keys with fallback to the legacy global key.
  • Thread the optional workspace ID through outline state reads, writes, validation, item persistence, and database hydration.
  • Keep existing global-key behavior for callers without a workspace context.
src/components/_shared/outline/utils.ts
src/components/_shared/outline/OutlineItem.tsx
src/components/app/outline/Outline.tsx
src/components/app/DatabaseView.tsx
src/components/_shared/outline/OutlineItem.databaseContainer.test.tsx
src/components/app/outline/__tests__/Outline.navigationContext.test.tsx
src/pages/__tests__/DatabaseView.databaseContainer.test.tsx
Automatically reveal the selected page by expanding only its ancestor chain when it is already present in the loaded outline.
  • Find ancestor IDs recursively and persist/apply the path without triggering navigation hydration.
  • Gate automatic expansion once per workspace and selected page so manual subsequent collapses remain effective.
  • Retain the existing event-driven expand-path flow and avoid expanding unrelated collapsed containers.
src/components/_shared/outline/utils.ts
src/components/app/outline/Outline.tsx
src/components/app/outline/__tests__/Outline.navigationContext.test.tsx
Update regression coverage for workspace isolation, selected-page ancestor expansion, and unchanged event behavior.
  • Assert hydration writes to workspace-scoped storage and migrates legacy state on first write.
  • Cover already-loaded selected pages, cross-workspace expand events, unrelated collapsed containers, and existing event-driven reveals.
src/components/app/outline/__tests__/Outline.navigationContext.test.tsx
src/pages/__tests__/DatabaseView.databaseContainer.test.tsx
src/components/_shared/outline/OutlineItem.databaseContainer.test.tsx

Assessment against linked issues

Issue Objective Addressed Explanation
#526 Scope sidebar expand/collapse state independently per workspace so switching workspaces does not validate and prune expansion IDs belonging to other workspaces.
#526 Automatically expand the ancestor chain of the selected page after switching workspaces, including when the page is already present in the loaded outline.
#526 Make a collapsed space containing pages visually distinguishable from a genuinely empty space. The PR changes persistence and automatic ancestor expansion, but does not add a chevron, page count, placeholder, or other visual indication for collapsed spaces.

Possibly linked issues


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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

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.

[Bug] Sidebar spaces appear empty after switching workspace (expand state is global and pruned per workspace; reload fixes it)

1 participant