Skip to content

Conflict Resolution

The Tech Strategy Tool uses optimistic concurrency with sequence-number-based conflict detection. This page explains how concurrent edits are detected, how conflicts are surfaced to users, and how the system resolves them.

In a collaborative editing environment, two users may edit the same field simultaneously. Without conflict detection, the last write silently overwrites the first, causing data loss that neither user is aware of.

Every field in the strategy document has an associated sequence number representing the last event that modified it. When a user starts editing a field, the client captures the current sequence number as lastSeenSequence.

When an edit event includes lastSeenSequence, the processor uses the HasConflict helper to compare:

  1. Find the sequence number of the last event that modified this field (stored in NameSequence, DescriptionSequence, or FieldSequences on the entity)
  2. Compare it to the lastSeenSequence in the request
  3. If lastSeenSequence < lastModifiedSequence, the field has been changed since the user last read it — conflict detected

On conflict, the processor returns:

FieldValue
status"rejected"
rejectionReason”Conflict: field was modified since your last read”
conflictingServerValueThe current server value the client is out of sync with

Conflict detection is available on the following field-edit events that use lastSeenSequence:

Event TypeConflicted FieldSequence Property
update_nameName of the target entityNameSequence on the entity
update_descriptionDescription of the target entityDescriptionSequence on the entity
update_initiative_progressInitiative progressFieldSequences["progress"]

Other update events (team color, Jira key, assignment and reorder) do not use conflict detection.

The EditableField component in the UI manages the user experience around conflicts:

Diagram
  1. Display — the field shows its current value as static text
  2. Edit — the user clicks to enter edit mode; lastSeenSequence is captured
  3. Persisting — the edit is submitted to the server; a spinner is shown
  4. Error — if the server rejects with a conflict, the error state shows:
    • The current server value (from conflictingServerValue in the response)
    • A Retry button to re-submit the edit
    • A Cancel button to accept the server’s value and return to display

The EditableField component has a ~30-second timeout on the persisting state. If no response is received, it fetches the card from the API to determine the current state and exits the persisting state.

The Tech Strategy Tool uses optimistic concurrency (detect conflicts after the fact) rather than pessimistic locking (prevent concurrent access):

  • No lock management — no need to handle stale locks, lock expiry, or deadlocks
  • Non-blocking reads — users can always see the latest data without waiting for locks
  • Natural for event sourcing — sequence numbers provide a natural ordering for conflict detection
  • Low conflict rate — in practice, strategy editing is low-frequency enough that conflicts are rare

Conflicts are tracked at the field level, not the entity level. This means:

  • Editing a principle’s name while another user edits its description does not create a conflict
  • Only truly conflicting edits (same field, same entity) are rejected
  • This minimizes false conflicts in collaborative editing

The system rejects conflicting edits rather than attempting automatic merging:

  • Text merging is complex and error-prone for short fields like titles
  • Explicit rejection ensures the user sees the conflict and makes a conscious decision
  • The current server value is provided so the user can make an informed choice

Here is a complete example of a conflict being detected and resolved:

  1. Alice opens principle “API Design” (name last modified at seq 42, so NameSequence = 42)
  2. Bob opens the same principle (also sees NameSequence = 42)
  3. Alice submits update_name with “API-First Design” and lastSeenSequence: 42
    • Server applies the change at seq 43; sets NameSequence = 43
    • SSE broadcasts card-changed to all clients
  4. Bob submits update_name with “Cloud-Native Design” and lastSeenSequence: 42
    • Server detects that 42 < 43 (NameSequence — the field was modified)
    • Server rejects with conflictingServerValue: "API-First Design"
  5. Bob sees an error with the current server value “API-First Design”
  6. Bob can either:
    • Retry — re-submit “Cloud-Native Design” (this time with the updated sequence)
    • Cancel — accept Alice’s “API-First Design” and return to display mode