I Spent Two Days Debugging a Drag-and-Drop Bug That Was Two Lines of Code
There's a specific kind of pain reserved for bugs that look impossible until the moment they're obvious.
For the past couple of days I've been deep in the Delivery Hub Gantt chart — our Salesforce-native project management tool — trying to figure out why tasks kept snapping back to their original position after being dragged. The feature worked visually: drag a task, it drops in the right place, everything looks correct. Then you interact with anything else on the page — a filter change, a refresh — and the task teleports back to where it started.
I was convinced it was a rendering issue. Or a race condition in the Apex calls. Or something deeply wrong with the event system.
It was two things. Neither was any of those.
Root Cause #1: The Optimistic Update That Wasn't
Every modern UI that does async operations has a pattern: optimistic updates. You assume the server call will succeed and update the UI immediately, then sync the server state in the background. It makes apps feel instant.
The Delivery Hub Gantt had this pattern broken in a subtle way. When you dragged a task, onPatch correctly called the Apex backend to persist the new sortOrder, priorityGroup, and parentId. The server updated. The backend was fine.
But onPatch never updated the local allTasks array.
When any subsequent event triggered a re-render — a filter toggle, a refresh, anything — the Gantt pulled from allTasks to rebuild the view. And allTasks still had the original position data. So the Gantt faithfully rendered the task exactly where it was before you dragged it.
The fix was straightforward once diagnosed: before the async Apex call, immediately patch allTasks[idx] with the new values. Then fire Apex. Then after 50ms, trigger a debounced refresh to sync everything. That 50ms debounce is important — a single drag can fire three patches (sortOrder, priorityGroup, and parentId) near-simultaneously. Batching them into one re-render prevents visual flickering.
The Gantt now shows tasks in their new positions immediately. No waiting on Apex. No snap-back.
This is the same pattern as proFormaRef.current.updateItem() in v8 of our JavaScript build — it applies the change to local state before the async operation completes. We had it in JS. We just hadn't ported it to the LWC correctly.
Root Cause #2: The Silent Guard
While hunting for root cause #1, I found something else: a guard statement that was silently eating drag events.
const grp = getGroupForTask(task);
if (!grp) return;
Any task that didn't have a priorityGroup assigned hit this guard and had its drag event swallowed entirely. No error. No log. Just nothing happening when you dragged it.
This was already fixed in a prior commit — I'm noting it here because it took me embarrassingly long to spot. Silent returns in event handlers are one of the most frustrating debugging patterns because the absence of an action gives you nothing to trace. The fix is either to set a default group or remove the guard when it's not actually required.
Both were removed together. Dragging any task, grouped or ungrouped, now works.
What the Build Looks Like Now
The new build (nimbusganttapp.resource) comes in at 92.7KB.
I did a side-by-side screenshot comparison of the v8 reference state against what the Salesforce tab renders after deploying — it matched exactly. The sidebar, the filter bar, the task ordering. This was a v8-exact match which was the target.
The commit went out as:
build(gantt): 92.7KB — drag optimistic update fix + filter bar v8-exact
Deployed to the Delivery Hub dev org via:
sf project deploy start \
--metadata "StaticResource:nimbusganttapp" \
--target-org "Delivery Hub__dev" \
--ignore-conflicts
The Lesson (That I Keep Having to Relearn)
Every time I chase a bug that seems complex, it turns out to be something embarrassingly simple. In this case:
- I wasn't updating local state before the async call
- A guard was silently blocking drag events
Neither required architectural changes. Neither was a deep framework issue. Both were fixable in under 10 lines of code.
The hard part wasn't the fix. It was isolating which of the twelve things I suspected was actually wrong.
The approach that finally worked: comment out everything except the core drag handler, get one task dragging and persisting correctly from scratch, then re-add the complexity back piece by piece. When something broke the test case, that was the culprit.
If you're building Salesforce-native tooling and running into Gantt or async state issues in LWC, I'm happy to talk through it. Delivery Hub is free and open-source — if you want to use a Gantt in Salesforce without paying DocuSign or Jira-level prices, that's what we built.
Free Tools & Calculators
Interactive tools built by Glen Bradford
Enjoyed this? Get more like it.
Glen's Musings — AI, investing, and building things. Occasional. Free.

Glen Bradford
Investor · Builder · Writer
MBA from Purdue. Former hedge fund manager. Holds 26 series of Fannie Mae and Freddie Mac junior preferred stock. Built Cloud Nimbus for Salesforce consulting. Author of Act As If. Writes about investing, building things, and the longest financial fraud in American history.
More in Life & Philosophy
Keep Exploring
Net Worth Percentile Calculator
Where do you rank financially? Find out instantly.
Read moreAct As If — Glen's Book
Talk minus action equals zero. The philosophy that drives everything.
Read moreGlen's Rules
The principles behind the investing, the building, and the writing.
Read moreAll Life & Philosophy Posts
Browse the full Life & Philosophy blog archive.
Read moreDisclaimer: This blog post reflects the author's personal opinions at the time of writing and is not financial, investment, or legal advice. Glen Bradford holds positions in securities discussed on this site. Past performance is not indicative of future results. Do your own research and consult qualified professionals before making investment decisions. Some content on this site was generated or edited with AI assistance.