Skip to content

Detect file renames without triggering deletions#587

Open
huntercaron wants to merge 9 commits intomainfrom
feature/code-link-rename-detection
Open

Detect file renames without triggering deletions#587
huntercaron wants to merge 9 commits intomainfrom
feature/code-link-rename-detection

Conversation

@huntercaron
Copy link
Collaborator

@huntercaron huntercaron commented Mar 6, 2026

Description

This PR implements proper file rename detection in the code-link CLI and fixes the echo event bug that was causing false deletions. The changes include:

  1. Rename Detection: Detects when chokidar emits unlink + add events for the same content (indicating a rename) and emits a single "rename" event instead
  2. Rename API Usage: Uses Framer's codeFile.rename() API to properly rename files without deletion
  3. Sanitization Echo Suppression: Suppresses the false echo events that chokidar fires when the watcher itself renames files for sanitization (e.g., race?.tsxrace_.tsx or New Folder With Items/New_Folder_With_Items/)

Without the echo suppression fix, renaming files locally would trigger a delete operation on the remote plugin, undoing the rename. The 100ms buffer for rename detection ensures that both unlink and add events are matched regardless of their arrival order.

Changelog

  • Added rename detection based on content hash matching with 100ms buffer
  • Implemented file-rename message type for CLI-to-plugin communication
  • Added SEND_FILE_RENAME effect in CLI controller to handle rename events
  • Added applyRemoteRename() method in plugin API using Framer's codeFile.rename()
  • Added sanitization echo suppression to prevent false deletes after path sanitization
  • Added same-path rename guard for defensive-in-depth

Testing

  • Renaming files locally renames them in Framer (no deletion modals shown)
  • Moving files in and out of folders locally works as expected

When watcher sanitizes file/folder names (e.g., "race?.tsx" → "race_.tsx"),
the fs.rename() call triggers echo events from chokidar that were being
processed as real user events. This caused:
- False file uploads (echo add events)
- False file deletions (echo unlink events undoing the sanitization)
- Interference with rename detection

Solution:
- Track recently sanitized paths in recentSanitizations set
- Record both unsanitized and sanitized paths after fs.rename()
- Suppress events at top of emitEvent if they match recent sanitizations
- Add same-path rename guard to dispatchEvent for defense-in-depth
- Add tests for echo suppression scenarios

This complements the existing rename detection feature which uses the
Framer SDK's codeFile.rename() API to properly handle file renames
without deletion operations.
Move the file-synced WebSocket message inside the existing-file guard
so the CLI is not told a rename succeeded when the target file was
never found in Framer. Also reorder rename-effect tracking to update
state only after a successful send, and fix minor type/style issues.
@huntercaron huntercaron added Auto submit to Marketplace on merge Submits the plugin to the marketplace after merging and removed Auto submit to Marketplace on merge Submits the plugin to the marketplace after merging labels Mar 6, 2026
Reuse hashFileContent from state-persistence instead of maintaining
a private copy.
@Nick-Lucas Nick-Lucas requested a review from Copilot March 6, 2026 12:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds end-to-end rename support to the code-link workflow (CLI watcher → CLI controller → plugin) by detecting unlink+add rename patterns locally, sending a new file-rename message type, and applying the rename remotely via Framer’s codeFile.rename() API. It also adds suppression for chokidar “echo” events caused by the watcher’s own sanitization renames to avoid false remote deletions.

Changes:

  • Add hash-based unlink+add pairing with a short buffer to emit a single local rename watcher event.
  • Introduce file-rename CLI→plugin message + controller effect to send renames and update local tracking.
  • Implement plugin-side remote rename application via codeFile.rename() and update shared message types accordingly.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
plugins/code-link/src/api.ts Adds applyRemoteRename() to rename Framer code files and acknowledge via file-synced.
plugins/code-link/src/App.tsx Handles incoming file-rename messages and invokes the new API method.
packages/code-link-shared/src/types.ts Extends shared message union/validation to include file-rename.
packages/code-link-cli/src/utils/project.ts Adjusts directory-name sanitization rules (now preserves spaces).
packages/code-link-cli/src/utils/hash-tracker.ts Reuses shared hashing utility from state persistence (removes local crypto hashing).
packages/code-link-cli/src/types.ts Adds rename watcher event kind and oldRelativePath metadata.
packages/code-link-cli/src/helpers/watcher.ts Implements rename detection + sanitization echo suppression + buffer cleanup on close.
packages/code-link-cli/src/helpers/watcher.test.ts Adds unit tests covering rename detection, buffering, and echo suppression.
packages/code-link-cli/src/helpers/installer.ts Minor formatting cleanup (whitespace).
packages/code-link-cli/src/helpers/files.test.ts Adjusts test typings for conflict content values.
packages/code-link-cli/src/controller.ts Adds rename handling in the state machine and a new SEND_FILE_RENAME effect.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@huntercaron
Copy link
Collaborator Author

Dang ok I guess I will start doing copilot + devin + cursor haha

@huntercaron
Copy link
Collaborator Author

@cursor review

@huntercaron
Copy link
Collaborator Author

@devin are you here to review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

if (!framer.isAllowedTo("CodeFile.rename")) {
log.warn("No permission to rename code files")
return false
}
Copy link

Choose a reason for hiding this comment

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

No error sent when rename permission denied

Medium Severity

When framer.isAllowedTo("CodeFile.rename") returns false, applyRemoteRename returns false without sending any socket message back to the CLI. Every other failure path in this method sends an error message via the socket (lines 173–179, 189–195, 221–227). Without a response, the CLI's pendingRenames entry (set in SEND_FILE_RENAME) is never cleaned up. A future UPDATE_FILE_METADATA event for the same fileName would incorrectly trigger old-file cleanup from the stale entry, corrupting hashTracker and fileMetadataCache state.

Additional Locations (1)

Fix in Cursor Fix in Web

}

return []
}
Copy link

Choose a reason for hiding this comment

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

Rename handler lacks echo prevention checks

Medium Severity

The new SEND_FILE_RENAME effect handler sends renames to the plugin without any echo prevention via hashTracker. The existing SEND_LOCAL_CHANGE checks hashTracker.shouldSkip and LOCAL_INITIATED_FILE_DELETE checks hashTracker.shouldSkipDelete, but SEND_FILE_RENAME checks neither. When remote-initiated writes (new file + deleted old file) happen close together, the watcher's rename detection can combine them into a rename event that bypasses the per-event echo guards, causing a spurious rename message to be sent back to the plugin.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

3 participants