Optimistic rendering #382
Description
To improve the perceived responsiveness of git operations, we can use an optimistic rendering strategy throughout the package. When a git operation is requested, the UI should reflect the expected outcome immediately rather than waiting for the operation to actually complete.
For example:
- A user double-clicks on a file to stage it.
- The staging action is enqueued in the AsyncQueue of git operations.
- The file moves from the unstaged list to the staged list on the very next render.
- At some later event loop tick, the git operation completes, and the view accurately reflects the current states of the git index and working directory tree again.
Considerations
We'll need a consistent strategy for handling errors and failed operations. It would be jarring to stage a file and have it keep jumping back up to the unstaged list.
On a related note, we should decide how far we want to go to handle dependent operations and cancellability. The easiest route would be to ensure that optimistically rendered elements are read-only (you can't unstage an optimistically staged file until the index catches up). It would be possible, if complicated, to handle these though: if an operation hasn't begun yet it could be removed from the queue, for example, or operations could be explicitly linked to dependent state so that failed operations would propagate correctly... at some point we'll need to put a finite depth on that rabbit-hole.
It'd be nice to give users some subtle feedback that pending operations are still in progress. One idea would be to have a throbber in the status bar that animates while the view is known to be out of sync. We could also add animation or other indications directly to optimistically-rendered elements; for example, a filename rendered optimistically in the staged list could have its icon greyed out or at a lower alpha value and fade it in once the real index catches up.
Operations that touch the network (fetches, pushes and pulls) should likely be excluded from optimistic rendering and instead follow the current approach of disabling UI elements and blocking. Because these are "expected" to take more time to complete, optimistically rendering their results would cause doubt that they actually did anything. There's some grey area here about other operations like commit that I'd like to hear others' thoughts on.
Implementation
- Introduce an
Operation
hierarchy into our models to represent a pending action. EachOperation
implements two methods: one that returns a Promise performing the git command, and one that mutates the state of model objects (through aRepository
argument, maybe) to reflect the change it will perform. Mutated model objects should likely be marked in some way (isOptimistic() === true
?) to enable UI feedback. - Rather than storing Promises directly,
AsyncQueue
stores and executesOperations
. - When the
Repository
is queried for its state, eachOperation
currently within the queue is given the opportunity to mutate its return value. The mutated state is then reported to the components for rendering.
Based on discussions with @kuychaco, @BinaryMuse and @nathansobo.