Skip to content

fix(ui): stop page autosave from resetting the editor and crossing pages#322

Open
cavidelizade wants to merge 1 commit into
Devlaner:mainfrom
cavidelizade:fix/page-autosave
Open

fix(ui): stop page autosave from resetting the editor and crossing pages#322
cavidelizade wants to merge 1 commit into
Devlaner:mainfrom
cavidelizade:fix/page-autosave

Conversation

@cavidelizade

Copy link
Copy Markdown
Contributor

Summary

Two related bugs in the page editor's autosave.

Linked issues

Closes #317

Type of change

  • Bug fix (fix:)

Surface

  • UI (apps/web/)

What changed

1. Autosave round-trip reset the editor / jumped the caret.
usePageEditor reseeded whenever initialHtml changed by exact string inequality. After each body autosave, saveBodyNow fed the server's description_html back as initialHtml, so any normalization difference — or a keystroke landing during the ~1.5s save — reset the document and moved the cursor.

  • Mirrored DescriptionEditor's guard: compare normalized HTML and skip when the incoming value is what we last synced.
  • The parent now seeds the editor from a dedicated seedHtml state that changes only on a genuine document swap (load, route change, version restore), never from an autosave round-trip.

2. A pending save could cross pages.
The debounce timers were cleared only on unmount. Navigating /pages/A → /pages/B reuses the component, so A's pending save could fire and its setPage() overwrite B's header/body.

  • Flush the pending body/title saves when the page identity changes (effect keyed on workspace/project/pageId) so last-second edits aren't dropped.
  • Guard saveBodyNow/saveTitleNow with a pageRef so an in-flight save that resolves after navigation persists server-side but doesn't touch the newly-opened page's state.

Why this approach

The root cause of (1) is that the editor's seed was coupled to the continuously-updated page body. Decoupling it (seedHtml) means the only thing that can reseed the editor is an intentional document change — the normalize guard is then belt-and-suspenders. For (2), flushing on the identity change preserves edits and the route guard is what actually prevents the cross-page write.

Test plan

  • npm run typecheck + npm run lint (with the i18n gate) pass
  • Manual (no web test harness in the repo; dev stack wasn't up in my session — steps for review):
    1. Open a page, type continuously through several autosave cycles → text and caret stay put (previously the caret jumped / a keystroke could be dropped on save).
    2. Edit page A, then immediately open page B before the debounce fires → A's edit persists to A, B is untouched (previously B's content could get A's body).
    3. Restore an older version → editor shows the restored content.

Flagging that I verified this by type-checking, linting and tracing the logic, but did not exercise it in a running app — worth a manual pass on the three flows above before merge.

AI assistance

  • AI tools were used — tool(s): Claude Code (Opus 4.8) — commits carry a Co-Authored-By: trailer

Two problems in the page editor's autosave:

1. usePageEditor reseeded whenever initialHtml changed by exact-string
   inequality. After each body autosave, saveBodyNow fed the server's
   description_html back as initialHtml, so any normalization difference (or a
   keystroke landing during the ~1.5s save) reset the document and jumped the
   caret. Mirror DescriptionEditor's guard: compare normalized HTML and skip
   when the incoming value is what we last synced. The parent now also seeds the
   editor from a dedicated seedHtml that only changes on genuine document swaps
   (load, route change, version restore), never from an autosave round-trip.

2. The body/title debounce timers were cleared only on unmount. Navigating
   /pages/A -> /pages/B reused the component, so A's pending save could fire and
   its setPage() overwrite B's header/body. Flush the pending saves when the
   page identity changes (keyed on workspace/project/pageId) and guard
   saveBodyNow/saveTitleNow so an in-flight save that resolves after navigation
   doesn't touch the new page's state.

Closes Devlaner#317

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cavidelizade
cavidelizade requested a review from a team as a code owner July 16, 2026 10:33
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@cavidelizade, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0801e7aa-1628-471a-a747-aaaa1d87b429

📥 Commits

Reviewing files that changed from the base of the PR and between 61bc903 and e01ffa4.

📒 Files selected for processing (2)
  • apps/web/src/components/page-editor/usePageEditor.ts
  • apps/web/src/pages/PageDetailPage.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cavidelizade

Copy link
Copy Markdown
Contributor Author

@martian56 this fixes #317 — the page autosave was reseeding the editor on its own save round-trip (caret jumps / the odd dropped keystroke), and a pending save could fire against the wrong page after you navigated. One heads-up: I verified this with typecheck + lint and traced the logic carefully, but my local dev stack was down so I couldn't click through it live. If you can give the three flows in the PR description a quick manual pass before merging I'd appreciate it — happy to adjust if anything's off.

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.

Page autosave round-trip can reset editor content and jump the caret

1 participant