lightning icon indicating copy to clipboard operation
lightning copied to clipboard

Implement Unsaved Changes Indicator (Red Dot) in React

Open theroinaochieng opened this issue 2 months ago • 1 comments

User story

As a workflow editor user, I want to see a visual indicator when I have unsaved changes so that I know my work isn't persisted yet.

Details

Currently the unsaved changes indicator (red dot) is implemented in LiveView using Ecto changeset change tracking. This issue covers migrating the visual indicator to React while establishing proper change tracking.

Current Implementation:

  • Indicator component: with_changes_indicator/1 at edit.ex:1192-1204
  • Tracking logic: Uses @changeset.changes |> Enum.any?()
  • Two display locations: wraps workflow name/toggle in top bar (edit.ex:166-228) and wraps save button in inspector panel (edit.ex:402-413)
  • Visual: Red dot (bg-danger-500, 12px diameter) positioned absolute top-right
  • Change detection flow: form changes → send_form_changedhandle_info({:form_changed, ...}) → changeset update

Components Status:

  • Red dot indicator - needs migration to React
  • Change tracking - needs implementation in React state

Summary: This issue creates a reusable React component for displaying the unsaved changes indicator (red dot). The component will be a simple visual wrapper that receives a boolean prop from LiveView indicating whether there are unsaved changes. The server (LiveView) will remain authoritative for change tracking via Ecto changesets, and React will purely handle the visual presentation.

Depends on: #3681 (TypeScript types)

Implementation notes

  1. Create UnsavedChangesIndicator.tsx component
  2. Add hasUnsavedChanges: boolean prop passed from LiveView
  3. Server maintains authoritative dirty state via changeset
  4. Component renders red dot when hasUnsavedChanges === true
  5. Match visual design: 12px red circle, absolute positioned top-right with -4px offset
  6. Use data attribute data-is-dirty="true" for testing

Change Detection Options:

  • Option A (Recommended): Server-side tracking - LiveView sends hasUnsavedChanges boolean based on changeset
  • Option B: Client-side tracking - React maintains dirty state based on form edits (more complex)

Visual Requirements:

  • Red circle: w-3 h-3 (12px)
  • Background: bg-danger-500
  • Position: absolute -m-1 top-0 right-0 z-10
  • Rounded: rounded-full

Release notes

  • Unsaved changes indicator (red dot) migrated to React component

User acceptance criteria

  • [ ] Red dot appears when workflow has unsaved changes
  • [ ] Red dot disappears after successful save
  • [ ] Indicator shows in both locations (top bar and inspector panel)
  • [ ] Visual design matches existing implementation
  • [ ] Indicator appears/disappears without page reload
  • [ ] Data attribute data-is-dirty reflects state correctly

theroinaochieng avatar Oct 03 '25 10:10 theroinaochieng

We attempted this but hit a fundamental problem with CRDT architecture.

What we tried:

  1. State vectors - Track operation counts per client to detect changes
  2. State hashing - Hash the full operation history to compare against baseline

The problem: Both approaches track operation history, not content. This caused false positives where the editor would show "unsaved changes" 1-2 seconds after opening, even when the user didn't touch anything.

This happened because Y.Doc syncs its initial state as operations. Our tracking saw those sync operations as "changes" and couldn't distinguish them from real user edits.

The difference between the two approaches:

  • State vectors: Track how many operations each client performed (metadata only)
  • State hashing: Hash the full operation history

Both failed because operation history ≠ content state. Reverting a change (Foo → Bar → Foo) still shows as dirty because 3 operations occurred.

What would work: Serialize the workflow to JSON and hash the content. This correctly detects only content changes. But it's too expensive to run on every keystroke in real-time editing.

Current status: Some backend infrastructure exists in PR #3635, but the false positive issue blocks frontend implementation. Deprioritizing for now - may revisit if we find a better approach.

stuartc avatar Oct 10 '25 08:10 stuartc