Skip to content

fix(visual-builder): repaint the hover cursor when a peer lock changes (DRFT-921) - #661

Merged
sairaj-cs merged 2 commits into
ce-auto-draft-lpfrom
fix/DRFT-921-cursor-lock-release
Oct 5, 2026
Merged

sairaj-cs merged 2 commits into
ce-auto-draft-lpfrom
fix/DRFT-921-cursor-lock-release

Conversation

@sairaj-cs

@sairaj-cs sairaj-cs commented Oct 5, 2026 •

Copy link
Copy Markdown

Ticket: DRFT-921

Summary

User A locks a field while user B hovers it. When A releases the lock, B's outline unlocks, but B's cursor stays grey (disabled).

There are two causes:

 generateCustomCursor({ fieldType, fieldDisabled })
-  if data-icon === fieldType
+  if data-icon === fieldType and data-disabled === fieldDisabled
     return                      // nothing to repaint
   repaint cursor
   set data-icon
+  set data-disabled
 subscribeEntryFieldLockInfo(() =>
   addOutline(lastAddOutlineParams)
+  if not collab mode
+    generateCursor(lastAddOutlineParams.eventDetails, lastCustomCursor)
 )
  1. The cursor cache used only the field type. A lock change keeps the same field type, so the grey cursor was never repainted.
  2. A lock change repainted only the outline. The cursor was repainted only on the next move to a new element.

Evidence

  • Before: generateCustomCursor > should repaint when only the disabled state changes fails: the cursor still has visual-builder__cursor-disabled after the call with fieldDisabled: false.
    After: the test passes. mouseHover — lock change while hovering (2 tests) passes: the cursor turns grey when a peer takes the lock and turns enabled when the peer releases it.
  • Live (two users, same field):
drft-921-cursor-unlock.mp4

The label, outline and cursor are grey while the field is locked, and all three turn purple at the same moment when the lock is released.

Merge Danger

Door: two-way. Two small client-side changes, no data or API change. A revert removes them.
Blast Radius: the custom cursor in Visual Builder. The cursor is now repainted on every field-lock update while hovering, and once more when only the disabled state changes. Collab (feedback) mode is skipped because it uses its own cursor.

🤖 Generated with Claude Code

…s (DRFT-921)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sairaj-cs
sairaj-cs requested a review from a team as a code owner October 5, 2026 07:18
@snyk-io

snyk-io Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 68.11% 2645 / 3883
🔵 Statements 66.99% 2688 / 4012
🔵 Functions 65.32% 469 / 718
🔵 Branches 62.96% 1634 / 2595
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/visualBuilder/generators/generateCustomCursor.ts 100% 85.71% 100% 100%
src/visualBuilder/listeners/mouseHover.ts 66.07% 55.03% 54.16% 68.55% 58-63, 81, 91, 113, 143, 175, 185, 203-211, 218, 226-234, 243, 255, 265-282, 290-291, 294-339, 361-364, 380-381, 387-390, 392-393, 476, 483, 514
Generated in workflow #931 for commit acc5e87 by the Vitest Coverage Report Action

@hitesh-shetty-cstk hitesh-shetty-cstk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated review

What this changes: The custom hover cursor is repainted when a peer's field lock is acquired or released while the pointer sits on that field. Two changes do it: the cursor's repaint cache now keys on the disabled state as well as the field type, and the lock-store subscription repaints the cursor alongside the outline it already repainted.

Business impact: The canvas hover affordance in Visual Builder. No critical journey is blocked either way, since a wrong cursor colour misleads rather than stops an editor, and the change narrows that gap rather than widening it. The regression surface is the new repaint path firing on every lock update while hovering.

Security: Nothing beyond what the scanners cover. The innerHTML write in generateCustomCursor is unchanged and still builds from the hardcoded icon map, and the new data-disabled attribute is a stringified boolean.

Flow

flowchart TD
    A[Peer lock changes] --> B[fieldLockStore notifies listeners]
    B --> C{Hovered element still outlined?}
    C -- no --> D[return]
    C -- yes --> E[addOutline repaints outline]
    E --> F{collab.enable}
    F -- yes --> G[skip, collab owns the cursor]:::changed
    F -- no --> H[generateCursor re-reads peer lock]:::changed
    H --> I{icon and disabled both unchanged}
    I -- yes --> J[return, no repaint]
    I -- no --> K[repaint, store data-icon and data-disabled]:::changed
    classDef changed fill:#fff3cd,stroke:#d39e00
Loading

Findings: 0 blocker, 1 should fix, 3 nit. All four are inline.

The mechanism holds up on the paths I traced. fieldDisabled is defaulted to false in the destructure, so the new cache key compares two booleans and the data-disabled attribute is always "true" or "false", with no undefined leaking in to defeat the early return. generateCursor returns early on a null cursor and re-reads getPeerLockForField after its awaits, so overlapping repaints converge on the current lock state rather than racing to a stale one. The collab guard matches the hover path: feedback mode paints its own cursor and non-feedback collab hides the cursor, so skipping both is right.

The tests are additive. No assertion was weakened, no case skipped, no snapshot regenerated. The two new listener tests fail if the subscription does not fire, rather than passing on a call left over from the initial hover, because expect.objectContaining({ fieldDisabled: ... }) does not match the resetCustomCursor and loading-cursor calls that omit the property.

Reviewer candidates: @ZuhairAhmed-cs authored 8 of the sampled commits across these files, 7 of them on mouseHover.ts. @hitesh-shetty-cstk authored 10, mostly on mouseHover.ts and its test file. @csAyushDubey is close behind with 7 on mouseHover.ts and is worth pulling in if either of the other two is unavailable.

Not covered: No local run, no build, no test execution. The lock-delta frequency the parent window pushes, which decides how much the new per-notification repaint actually costs, is outside this repository and I could not measure it. The post-message client's timeout behaviour could not be confirmed because node_modules is not installed in this checkout, which is why the first finding is written as conditional. The live two-user evidence in the description is still a video placeholder.

Automated review by Claude Code. A human review is still required.


Generated by Claude Code

Comment thread src/visualBuilder/listeners/mouseHover.ts
Comment thread src/visualBuilder/listeners/mouseHover.ts
Comment thread src/visualBuilder/listeners/mouseHover.ts
Comment thread src/visualBuilder/listeners/__test__/mouseHover.test.ts
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sairaj-cs
sairaj-cs merged commit d07de6b into ce-auto-draft-lp Oct 5, 2026
9 of 10 checks passed
@sairaj-cs
sairaj-cs deleted the fix/DRFT-921-cursor-lock-release branch October 5, 2026 11:59
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.

2 participants