Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Interesting write up. Thanks a lot for writing it Sacha.

Okay, so I have a question. Isn't this just a property of the internet as a system?

If we remember the history of human-computer interaction on 2D displays, we first really remember what led to the desktop GUI (Xerox Alto, all that stuff). This domain had tons of research poured into it (of which Raskin is a member) - including from the military, for usability in extreme situations - and by now it's reached a pretty mature stage, and we have developed a nice design language to describe it and advanced technological tools to implement that language. On my Finder, or in Pages, I can reliably undo things nicely. Just today, I deleted a file by mistake- command + z, boom, undone.

(by the way, this is exactly what Apple and mobile devs talks about when they say that well done native apps can never be beat by web apps- those decades of best practice experimental research)

Web apps, on the other hand, run on the internet, which adds some constraints structure wise. All of a sudden, we have at least 2 computers talking to one another, and in most cases many, many more than that. So we have to come up with design principles to make all of this somewhat work together, and we come up with things like RPC and REST. Those things are how our servers talk now, and they influence every technology that we build. They come with some constraints however- notably the ideal of statelessness, in which implementing "undo" would be really hard. Relational databases don't have the semantics to express "undo the last statement that was a user action done with id N", and good luck implementing things like undo in a system where you have caches you can't invalidate, content determined by one way hash functions, etc. If you have a binary blob of 5 lines, that user 1's action modify the first 3 lines, that user's 2 action modifies the last 3, and that user 1 then decides to undo his command?

You'd basically have to version everything, and have super amazing algorithms doing bisects and diffs on your content.

The http internet is really just a way to exchange documents. We've hacked it, using fancy stuff like AJAX, to make it behave more like the traditional GUI that we know and love, but at the core it's still a way to transfer documents. And naturally, HTML, the description language we use for web content, has semantics for describing static documents. Heck we've had to design a whole new language for design (CSS), because it's really just a way to describe documents! So ultimately it lacks the way to implement concepts like undo-ing as first class features.

Let it be noticed that mobile apps do undo-ing very poorly, as well.



I've implemented multi-level undo/redo on both the desktop and the web, and the complexity is no different between the platforms. Client/server is a red herring, because what kills you is unexpected state changes, and a desktop app has to contend with that as well (e.g. what if you want to undo a file delete but a file has been created with the same name by another app?)

The trick is to put everything in commands which keep track of the old ad the new state (or at least the parts you need), so that you can have a command history stack, and undo merely means "run the unexecute method of the command at the current position, and move the pointer to the previous command". If unexecute fails, you throw away all older commands (undo history). If execute fails during redo, you throw away all newer commands (redo history). Multi-level undo does need to be designed in from the start, but once you learn how it works it does not add that much complexity.

The reason that you don't see many web applications that implement this is because web development did not branch off from desktop development, but instead branched off from unix systems administration (by creating glorified shell scripting languages to output html pages, and gradually improving on that). Web developers never learned from their senior desktop developers how to implement a multi-level undo/redo using a command pattern, because the vast majority of web developers never had a senior desktop developer to learn from. I only learned it myself because I did have such a desktop development mentor. Mobile developers are typically repurposed web developers, so no surprise that it suffers from the same affliction.


Re: your 3rd paragraph: yes, I wanted to mention that in my original comment and forgot. Thanks for bringing it up.

A lot of our modern infrastructure comes from the almighty UNIX era- before GUIs had really become as mainstream as there are todays. In that era, things like "undo" were close to non-existent (there is no undo for `rm`- the GUI's "hack" to solve that is the "Recycle Bin" (which is also nonexistent on mobile, interestingly enough)).

A lot of our modern web tools are mere interfaces to those various Unix utilities (phpmyadmin, as mentioned by Sacha, is just a neat little HTML interface to mysqladmin), and thus can't do much to provide functionality that's missing at the source.


Many things will be impossible unless you delay the initial action. I mean, how do you really undo sending an email? You can't. What you do instead, and I'm guessing this is what gmail does, is to delay the sending for 15 seconds to give the user time to regret.

Seems like delaying the action will almost always be possible. Guess i could give the users the misconception that they can undo whenever they want, which is just..


IIRC, "it's not a feature, it's a bug". Engineers noticed it took about 15 seconds to send a mail through Gmail and added an option to cancel it.


No -- it's an artificially-added delay. By switching the option on, your emails are delayed by the time period you choose.


Sometimes the actual deletion must occur before a mistake is realized. I have often seen undo implemented as a 'soft' delete which means the read-side needs to be 'undo aware'. Sometimes undo is implemented by backing up all related data to a separate location which is also non-trivial. Managing the 'restore' half needs logic to account for conflicts and changed state.

It doesn't change the fact that Undo is an amazingly useful feature but its not always as simple as 'delay the action'.


If you're both using an Exchange server, you can undo sending an email. https://office.microsoft.com/en-us/outlook-help/how-message-...


There's nothing that makes internet somehow undo-hostile. But your observation about relational databases not having the support for undo is in the right track. Actually it can be expanded to most data storage mechanisms, sql, nosql etc. Out of the box many data storage systems don't support versioning and that guides the applications towards the "warn, overwrite, no undo" pattern. It's just a lot easier to do apps like that.

For many cases it would be better if the underlying data storage mechanism would support versioning. Image a CMS that's running on top of git. You'd have diffs, history, branching, merging, clones, tags and other very important features out of the box. Doing those features on top of a relational database is a lot of work, but with git you get them free.


REST could accomodate for undo quite fine. The server would just have to return a resource representing the action in the response. The client could then delete the action with another request, which would amount to an undo. This is analogous to the command pattern and would require more effort from the programmer, just like normal undo. You need to keep an action history and make sure that all actions are reversible.


If REST follows HATEOAS fully, the back button is 90% of your undo.


I would like to know more about this implementation. I don't see how going back after a POST action is related to HATEOS.


Hmm. This would require a representation of the complete state history in resource urls, wouldn't it? I can only come up with something like /image/edit1/edit2/edit3/edit4 where going back would bring you to /image/edit1/edit2/edit3.

I suppose you can also do something state-based like /image/state4 going back to /image/state3. This would push the storage of the edit stack to the server.

Is this what you mean?


Yes; with request / responses reanimating a continuation, and with a copy-on-write tree of activation records instead of a stack.

But the usability probably wouldn't be fantastic.


Hey, have you ever seen this thing called etherpad? it's pretty great. Some guys even took the research put into that and made a library anyone can use called shareJS[1]. It uses operational transforms, and yes, they're pretty hairy.. but they've been done, and now anyone can use that work.

Then you have the persistent data structures from clojure. Now I might be crazy, but it seems to me that those data structures are probably the most valuable contributions that language has made to our craft, and make the sort of things you're talking about-- not trivial, but a lot easier.

The whole trick is you have to start thinking 4-dimensionally- Which is difficult because our programming languages tend to have modify-in-place mutable state, when what we should have is variables that remember what they were at specific times, and algorithms that are able to merge conflicts seamlessly- not by locking but by recreating what would have happened if the two events had happened synchronously on the same system.

But then, you only really have to do that if what you're talking about is a multiuser system. You can do all this much easier if what you're willing to build is a simpler system. One that works like a one page JS app- JUST LIKE A DESKTOP APP, and only talks to the server at the point of "saving" or "synchronising" or whatever you want to call it. That's easy. You just program undo just like you do in a desktop app, and what you save to the server is a serialised history.

On the server, you have your git-like versioned databases for maximum undo. We know this can work because dropbox does it. If I delete a file on my machine... like REALLY delete it, dropbox still has it for 30 days.

Wikipedia articles have "infinite" undo.

And that's all web. that's all internats.

Fundamentally, once you have javascript, the html5 appcache, localStorage, what is the difference between the "web app" and the "native app" other than performance and some other native features irrelevant to undo?

the mobile apps still have to phone home to a server too now. Half the apps on the app store don't work without a net connection. What is the actual difference?

In fact, you could write a whole app that just downloads to the phone, runs entirely locally, doesn't talk to the server after the initial download, and runs in a browser. Why couldn't you have undo in that?

Well, why not. so I added it to my app pix.pe[2] when you press the back button. not exactly the most impressive example since you don't have much state to store... but what's the difference between doing it there, and doing the same thing in a native app? it's all just turing complete language code, with a storage mechanism and a way to draw on screen. Once you have those things there's nothing to stop you. Other than undo itself being kind of difficult already, regardless of what platform you're writing into.

[1]: http://sharejs.org/ [2]: http://pix.pe/


There is absolutely no problem implementing undo on the web with restful interfaces. I just built a quite complex data management application where almost anything is undoable in a very Photoshop way (you see the "History" pane in your UI and can undo whatever you desire). If something is not undoable there it's either a bug or NIY (not implemented yet).

The product is hardcore JS single page application backed by document oriented database, JSON, REST, buzz, buzz, buzz, etc.


How do you avoid that the users action have a cascading effect through the system? Was the undo/history option built in from the start?

For example, your app inserts a new doc, some other process sees the new doc and updates a table or inserted into a message queue, it gets picked up by Foo and..


Moreover, that other proces need not be on the same server, or even on a server that you control; worse, it could trigger a meatspace effect. "User now authorized, access granted, door opened. [five minutes pass] Oops, that was a mistake." How do you undo _that_?


Built from the start, yes.

and... what? you mean what would happen if I undo the creation of the doc? Like I was deleting the doc - the system either would not allow me because of the linked docs/events/objects or would do a cascade delete, if it can.


Meant and.. a cascading effect resulting in 100 other actions you're only half-aware of, maybe on other systems =) So building the whole system with this in mind is probably a good idea.


I don't think that undo has much to do with the type of app, but rather with its design. So mobile, web and desktop apps should be able to offer similar functionality. At least for delete many sites are using a deleted flag anyway, which should be easily reversable. Getting back even part of the content might be worthwile.

In your example, if user 1 doesn't see the edits of user 2 there's a bigger problem. Undo would be liniar.


Great points, I hadn't considered that angle while writing the post. Those are definitely hard problems to solve.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: