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.
The Problem
Section titled “The Problem”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.
How It Works
Section titled “How It Works”Sequence tracking
Section titled “Sequence tracking”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.
Conflict detection
Section titled “Conflict detection”When an edit event includes lastSeenSequence, the processor uses the HasConflict helper to compare:
- Find the sequence number of the last event that modified this field (stored in
NameSequence,DescriptionSequence, orFieldSequenceson the entity) - Compare it to the
lastSeenSequencein the request - If
lastSeenSequence < lastModifiedSequence, the field has been changed since the user last read it — conflict detected
Resolution
Section titled “Resolution”On conflict, the processor returns:
| Field | Value |
|---|---|
status | "rejected" |
rejectionReason | ”Conflict: field was modified since your last read” |
conflictingServerValue | The current server value the client is out of sync with |
Supported Fields
Section titled “Supported Fields”Conflict detection is available on the following field-edit events that use lastSeenSequence:
| Event Type | Conflicted Field | Sequence Property |
|---|---|---|
update_name | Name of the target entity | NameSequence on the entity |
update_description | Description of the target entity | DescriptionSequence on the entity |
update_initiative_progress | Initiative progress | FieldSequences["progress"] |
Other update events (team color, Jira key, assignment and reorder) do not use conflict detection.
Client-Side Handling
Section titled “Client-Side Handling”EditableField state machine
Section titled “EditableField state machine”The EditableField component in the UI manages the user experience around conflicts:
- Display — the field shows its current value as static text
- Edit — the user clicks to enter edit mode;
lastSeenSequenceis captured - Persisting — the edit is submitted to the server; a spinner is shown
- Error — if the server rejects with a conflict, the error state shows:
- The current server value (from
conflictingServerValuein the response) - A Retry button to re-submit the edit
- A Cancel button to accept the server’s value and return to display
- The current server value (from
Timeout fallback
Section titled “Timeout fallback”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.
Design Rationale
Section titled “Design Rationale”Why optimistic concurrency?
Section titled “Why optimistic concurrency?”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
Why per-field tracking?
Section titled “Why per-field tracking?”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
Why reject rather than merge?
Section titled “Why reject rather than merge?”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
Example: Conflict Flow
Section titled “Example: Conflict Flow”Here is a complete example of a conflict being detected and resolved:
- Alice opens principle “API Design” (name last modified at seq 42, so
NameSequence = 42) - Bob opens the same principle (also sees
NameSequence = 42) - Alice submits
update_namewith “API-First Design” andlastSeenSequence: 42- Server applies the change at seq 43; sets
NameSequence = 43 - SSE broadcasts
card-changedto all clients
- Server applies the change at seq 43; sets
- Bob submits
update_namewith “Cloud-Native Design” andlastSeenSequence: 42- Server detects that 42 < 43 (
NameSequence— the field was modified) - Server rejects with
conflictingServerValue: "API-First Design"
- Server detects that 42 < 43 (
- Bob sees an error with the current server value “API-First Design”
- 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