Skip to content

fix: show accurate page reference errors and allow retry - #666

Merged
appflowy merged 13 commits into
mainfrom
codex/fix-page-reference-status-web
Oct 24, 2026
Merged

appflowy merged 13 commits into
mainfrom
codex/fix-page-reference-status-web

Conversation

@appflowy

@appflowy appflowy commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

A failed page-reference metadata lookup currently renders as "No Access" even when the server reports a missing record or a temporary service failure. Preserve the original lookup error and distinguish missing, trashed, deleted, permission-denied, sign-in-required, and temporary-failure states.

Temporary failures now expose Retry using an authoritative metadata refresh. Subscribe before the initial lookup and fence pending requests so folder updates can recover a reference and stale responses cannot overwrite a newer title or a different target. Add Storybook examples for every state. Preserve readable same-page block content when an optional metadata lookup fails, including equation references in detached editor surfaces.

This fixes status display and recovery behavior; it does not recreate a missing server record or change permissions.

Validation

  • CI follows latest-amd64 independently for Cloud, Worker, Search and AI. Report image IDs/version/revision labels instead of failing when independently published services have different source commits. YAML parsing, Bash syntax and a dry-run report with four different revisions passed. All 16 backend shards passed the new reporting step and started Docker services; form-timeline-bdd passed.
  • Playwright BDD: one Chromium scenario passed (8.2s) using the existing localhost Ref Pages Test document and separate Nathan/Annie sessions authenticated through the UI. It asserts the actual reference elements by ID: Nathan sees the target title, Annie sees No Access, and both see This page is in Trash. It rechecks labels after reload and verifies owner navigation versus the denied reference staying on the shared page.
  • Focused BDD TypeScript and typed ESLint checks passed. Screenshots are saved and attached at each checkpoint.
  • The two equation-reference Playwright cases that failed CI now pass locally in Chromium light/dark; 77 focused Jest tests and the TypeScript/ESLint checks pass after that correction.
  • 204 targeted Jest tests passed, including metadata error identity, failure labels, Retry, sync recovery, retargeting, and unmount races.
  • TypeScript, ESLint, and the error-catalog check passed.
  • Chrome Storybook verification covered light/dark themes, narrow layouts, and mouse/keyboard Retry without editing live data.

Existing local fixture BDD

The @local-ref-pages scenario is opt-in with RUN_REF_PAGES_TEST=1, requires a loopback BASE_URL and REF_PAGES_TEST_PASSWORD, and reuses the existing accounts, document, references, and trash state. It does not seed, modify, repair, or clean up page data or permissions.

pnpm exec bddgen test -c playwright.bdd.config.ts
# Set REF_PAGES_TEST_PASSWORD to the shared local test-account password first.
RUN_REF_PAGES_TEST=1 BASE_URL=http://localhost:3000 pnpm exec playwright test -c playwright.bdd.config.ts --grep @local-ref-pages --workers=1 --retries=0 --trace=off

Coverage limit: this BDD checks existing permissions and recoverable trash, not permanent deletion or live permission changes. Manual testing found that revoking access after a title has resolved still requires refresh; granting access recovers the title live.

Checklist

  • Included comments for non-obvious request ordering and recovery behavior.
  • Added regression tests and Storybook examples.
  • Verified integration with existing linked-page and permission-gate behavior.
  • Tested on additional browsers and operating systems.

Summary by Sourcery

Show accurate page-reference failure states and provide safe retry and recovery behavior without obscuring readable content.

New Features:

  • Distinguish missing, trashed, deleted, permission-denied, sign-in-required, and temporary-failure states for page references, with contextual explanations and retry support.
  • Add Storybook coverage and an opt-in local BDD scenario covering page-reference states, navigation, recovery, and permissions.

Bug Fixes:

  • Preserve original metadata lookup errors instead of incorrectly displaying all failures as No Access.
  • Prevent stale metadata and document requests from overwriting newer reference state, titles, or targets during access changes, retries, retargeting, and unmounts.
  • Keep readable same-page block and equation-reference content visible when optional metadata lookup fails.
  • Ensure Trash navigation closes an open page modal before navigating.

Enhancements:

  • Add authoritative metadata refresh and recovery handling for temporary failures and restored access.
  • Improve page-reference error popovers with keyboard, hover, focus, and permission-aware Trash actions.

CI:

  • Resolve Cloud images by digest while allowing independently published Worker, Search, and AI images to use different revisions, and report their image metadata without requiring matching commits.
  • Reduce coverage test parallelism and increase the Node heap limit to make coverage aggregation reliable.

Tests:

  • Add focused regression coverage for error identity, state labels, retry behavior, access recovery, stale-request fencing, unmount cleanup, modal navigation, and equation references.

@sourcery-ai

sourcery-ai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Page-reference metadata failures now retain their original identity and render accurate missing, trash, deletion, permission, authentication, or temporary-failure states. A versioned metadata hook subscribes before lookup, ignores stale requests, supports sync recovery and authoritative Retry, and cleans up safely; tests and Storybook examples cover the complete state and race-condition matrix.

Sequence diagram for page-reference metadata loading and retry

sequenceDiagram
    participant MentionPage
    participant MetadataHook as usePageReferenceMetadata
    participant Events as EventEmitter
    participant AppLayer as AppBusinessLayer
    participant ViewService

    MentionPage->>MetadataHook: loadViewMeta(pageId)
    MetadataHook->>Events: subscribe before lookup
    MetadataHook->>AppLayer: loadViewMeta(pageId)
    AppLayer->>ViewService: get or refresh metadata
    alt metadata available
        ViewService-->>AppLayer: View
        AppLayer-->>MetadataHook: View
        MetadataHook-->>MentionPage: render page reference
    else metadata error
        ViewService-->>AppLayer: original lookup error
        AppLayer-->>MetadataHook: error
        MetadataHook-->>MentionPage: render mapped error state
    end
    opt temporary failure
        MentionPage->>MetadataHook: retry()
        MetadataHook->>AppLayer: loadViewMeta(pageId, authoritative)
        AppLayer->>ViewService: refresh metadata
        ViewService-->>AppLayer: View
        AppLayer-->>MetadataHook: View
        MetadataHook-->>MentionPage: replace error with page reference
    end
Loading

State diagram for page-reference metadata status

stateDiagram-v2
    [*] --> Loading
    Loading --> Available: View returned
    Loading --> Missing: ViewMetadataError(not_found)
    Loading --> InTrash: ViewMetadataError(in_trash)
    Loading --> Deleted: PageNotFound/Gone error
    Loading --> Denied: permission error
    Loading --> Unauthorized: unauthorized error
    Loading --> TemporaryFailure: other lookup error
    TemporaryFailure --> Loading: retry()
    TemporaryFailure --> Available: authoritative refresh succeeds
Loading

File-Level Changes

Change Details Files
Preserve and classify page-reference metadata lookup failures instead of collapsing them into a generic access denial.
  • Added a typed local error for missing and trashed views.
  • Stopped swallowing API lookup errors and introduced explicit not-found handling.
  • Mapped server errors to missing, trashed, deleted, denied, unauthorized, and temporary-failure states.
src/application/errors/view-metadata-error.ts
src/components/app/layers/AppBusinessLayer.tsx
src/components/editor/components/leaf/mention/usePageReferenceMetadata.ts
src/components/editor/components/leaf/mention/MentionPage.tsx
Made page-reference metadata loading recoverable and safe against stale asynchronous results.
  • Added authoritative Retry for temporary failures.
  • Subscribed to metadata and outline updates before the initial lookup to support sync recovery.
  • Fenced requests by version so retargeting, updates, retries, and unmounts cannot apply stale results.
  • Disabled repeated retries while a retry request is pending and preserved newer synced metadata over late failures.
src/components/editor/components/leaf/mention/usePageReferenceMetadata.ts
src/components/editor/components/leaf/mention/MentionPage.tsx
src/components/editor/components/element/__tests__/Element.linked-page.test.tsx
Expanded regression coverage and visual examples for all page-reference states.
  • Verified original error identity, typed local errors, permission-gate behavior, retry recovery, retargeting, stale responses, and listener cleanup.
  • Added Storybook scenarios for available, missing, trashed, deleted, denied, sign-in-required, and temporary-failure states, including an aggregate view.
src/components/app/layers/__tests__/AppBusinessLayer.permissionGate.test.tsx
src/components/editor/components/element/__tests__/Element.linked-page.test.tsx
src/components/editor/components/leaf/mention/MentionPage.stories.tsx

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 found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/components/editor/components/leaf/mention/usePageReferenceMetadata.ts" line_range="53" />
<code_context>
+        ? await loadViewMeta(pageId, undefined, { authoritative: true })
+        : await loadViewMeta(pageId);
+
+      if (requestVersion.current === version) acceptView(view);
+    } catch (error) {
+      if (requestVersion.current === version) {
</code_context>
<issue_to_address>
**Retargeted lookup overwrites cached title**

When the component retargets while a lookup is pending and that lookup resolves after the new target commits but before the old passive effect cleanup runs, the old request passes the version check and calls `acceptView`, which invokes `onPageMentionNameResolved`; in rich-text contexts that writes to the shared page-name cache, so a stale title for the previous target can overwrite a newer cached title.

Fence responses against the current target synchronously before calling `acceptView` or its external callback.
</issue_to_address>

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

? await loadViewMeta(pageId, undefined, { authoritative: true })
: await loadViewMeta(pageId);

if (requestVersion.current === version) acceptView(view);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Retargeted lookup overwrites cached title

When the component retargets while a lookup is pending and that lookup resolves after the new target commits but before the old passive effect cleanup runs, the old request passes the version check and calls acceptView, which invokes onPageMentionNameResolved; in rich-text contexts that writes to the shared page-name cache, so a stale title for the previous target can overwrite a newer cached title.

Fence responses against the current target synchronously before calling acceptView or its external callback.

Prompt for AI agents
In `src/components/editor/components/leaf/mention/usePageReferenceMetadata.ts` at line 53:

**Retargeted lookup overwrites cached title**

When the component retargets while a lookup is pending and that lookup resolves after the new target commits but before the old passive effect cleanup runs, the old request passes the version check and calls `acceptView`, which invokes `onPageMentionNameResolved`; in rich-text contexts that writes to the shared page-name cache, so a stale title for the previous target can overwrite a newer cached title.

Fence responses against the current target synchronously before calling `acceptView` or its external callback.

@appflowy
appflowy merged commit 8d0831c into main Oct 24, 2026
22 of 23 checks passed
@appflowy
appflowy deleted the codex/fix-page-reference-status-web branch October 24, 2026 07:23
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