fix: prevent emoji title loss and link duplicates#696
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (40)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Problem
On iOS/WebKit, a leading emoji or the space after it can still be a pending DOM mutation when the editor blurs. Reflect previously flushed the last ProseMirror callback buffer immediately, so the visible title
🧠 Business ideascould be persisted as# Business ideas(or briefly as# 🧠Business ideas). The filename correctly remainednotes/business-ideas.md, but the saved H1 and a device's live title could diverge.That divergence became destructive when another synced file contained
[[Business ideas]]: a device with the stale/exact-only title index could treat the link as missing and silently createnotes/business-ideas-2.md.Before -> After
[[Business ideas]]meets# 🧠 Business ideaswhile exact lookup lagsbusiness-ideas-2.mdnote_createatomically claims one free path and returns collisions without replacing bytesChanges
Commit pending WebKit input before persistence
forceFlush()composition timer and its separate blur queue before serialization.Make sync settlement mean index convergence
SyncEngineOptions.onRemoteChangesmay now be async, and the engine awaits it before later Git work or an idle status.applyIndexChangesand invalidates dependent queries only after the pulled files are indexed.Guard unresolved-link creation
foldKeyremains unchanged, so emoji-distinct titles stay distinct.resolveOrCreateNoteWithTitlechecks the exact index, inspects the title-derived on-disk slug family (titles, frontmatter aliases, and derived subject aliases), accepts only one fallback match, and blocks ambiguous/unreadable matches with visible feedback.Make note creation no-clobber
note_create, staged under.reflect/tmpand persisted withpersist_noclobber.Tests
# 🧠 Business ideasplus externally authored[[Business ideas]], forces the reported exact-index miss, and verifies the original path is reused with zero create calls and no-2file.Verification
pnpm check(passes; only the existing max-lines warnings ingraph-provider.tsxandindexer.ts)pnpm --filter @reflect/core test src/graph/commands.test.ts src/graph/create-note.test.ts src/graph/cross-device-title-resolution.test.ts src/markdown/keys.test.ts src/sync/engine.test.ts— 58 passedpnpm --filter @reflect/desktop test src/dev/dev-bridge.test.ts src/mobile/mobile-screen.test.tsx src/editor/use-wiki-link-navigation.test.tsx src/editor/use-editor-autocomplete.test.tsx src/editor/wiki-autocomplete-entries.test.ts src/editor/pending-input.test.tsx src/editor/note-session.test.ts src/editor/use-note-document.test.tsx src/lib/backup-controller.test.ts— 171 passedcargo fmt --checkcargo test -p reflect-open atomic_create— 2 passedcargo test -p reflect-open outcome_serializes_for_the_typescript_boundary— 1 passedRisk / Rollout
note_createis atomic for each candidate path, not a lock over the entire slug family or external filesystem providers. Normal Git pulls are covered by awaited indexing plus collision re-resolution; a different suffix arriving from an external provider in the exact successful-claim window remains a narrow residual race.Note
Medium Risk
Touches note persistence, sync lifecycle, and create/link paths across Rust and TypeScript; the ProseMirror domObserver reach-in is guarded but depends on internal API shape.
Overview
Fixes WebKit/WebKit-style editors dropping leading emoji (and spacing) when blur or backgrounding races ProseMirror’s delayed DOM flush, and stops stale indexes from minting duplicate slug-family notes when links like
[[Business ideas]]target an emoji-titled file.Persistence: Before every save flush and editor unbind, the note editor commits pending native input by draining ProseMirror’s
domObserver(forceFlush+flush) so composed text is in markdown before the session writes.Creation & IPC: New generation-pinned
note_create/createNoteIfAbsentuses stagedpersist_noclobberwrites (iCloud placeholders count as collisions).createNoteWithTitleswitches from overwrite-capablenote_writeto this path; dev bridge mirrors the contract.Link resolution:
resolveOrCreateNoteWithTitlere-checks the index, scans the on-disk slug family (titles, aliases,foldFallbackTitleKeyfor leading-emoji normalization), blocks ambiguous/unreadable matches with user-visible errors, and re-resolves after atomic collisions instead of blindly suffixing. Wiki navigation,[[autocomplete, and Create-row suppression use the same guard.Sync:
onRemoteChangesis awaited through merge and backup indexing so idle sync means pulled notes are indexed, not just merged on disk.Reviewed by Cursor Bugbot for commit ccd5e08. Bugbot is set up for automated code reviews on this repo. Configure here.