diff --git a/docs/getting-started-tutorial/1-view-first-development.adoc b/docs/getting-started-tutorial/1-view-first-development.adoc
new file mode 100644
index 0000000000..a8e33a34d1
--- /dev/null
+++ b/docs/getting-started-tutorial/1-view-first-development.adoc
@@ -0,0 +1,72 @@
+:idprefix:
+:idseparator: -
+:toc: right
+:toclevels: 2
+
+# View-first Development
+
+If you're developing a user-facing web site or application, one of Lift's
+greatest improvements over existing systems is view-first development.
+View-first development thoroughly separates the process of creating the user
+interface from the process of putting data from the system into it. This way,
+you can stay focused on users when you're creating the user interface and
+focus on the interface between your backend and the HTML only when
+you're working on the backend.
+
+The flip side of view-first development is that it takes some getting used to
+if you are accustomed to the typical web MVC framework. The first stop when
+figuring out what's going on in a typical web MVC setup is the controller. In
+Lift, your first stop is your HTML file. Everything starts in the HTML, where
+you decide what it is that you want to present to the user. You don't just think
+about user interactions first, you *build* them first, and let them guide your
+development forward and inform it at every step of the way. Turning a usability
+tested, high-fidelity mockup into a live page has never been so
+straightforward.
+
+For our chat app, we're going to focus first on two use cases, formulated as
+user stories:
+
+ - As a chatter, I want to post a message so that others can see it.
+ - As a chatter, I want to see messages from me and others so that I can keep
+ track of the conversation and contribute in context.
+
+To start with, we'll set up a simple `chat.html` page in our `src/main/webapp`
+directory (where all HTML files go). All we really need in there for now is a
+list of chat messages so far, and a box to put our own chat message into. So,
+here's some base HTML to get us going:
+
+```html:src/main/webapp/index.html
+
+
+
+ Chat!
+
+
+
+
+
+ - Hi!
+ - Oh, hey there.
+ - How are you?
+ - Good, you?
+
+
+
+
+
+```
+
+While we're not using it here, it's probably a good idea to start off with
+http://html5boilerplate.com[HTML5 Boilerplate]. Indeed, the default Lift
+templates all start with exactly that footnote:[Ok, so not exactly. IE
+conditional comments need a little additional work in Lift, because Lift is
+smart enough to strip all HTML comments in production mode.].
+
+When it comes to user testing, notice that our view is fully-valid HTML, with
+placeholder data. It is, in effect, a high-fidelity mockup. And now that we've
+got our view sorted out (and, ideally, tested with users), we can start hooking
+up link:2-the-lift-menu-system.adoc[the Lift side].
diff --git a/docs/getting-started-tutorial/2-the-lift-menu-system.adoc b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc
new file mode 100644
index 0000000000..373db1f283
--- /dev/null
+++ b/docs/getting-started-tutorial/2-the-lift-menu-system.adoc
@@ -0,0 +1,32 @@
+:idprefix:
+:idseparator: -
+:toc: right
+:toclevels: 2
+
+# The Lift Menu System
+
+Another distinguishing characteristic of Lift is that it is *secure by
+default*. Amongst other things, this means that if you enable Lift's `SiteMap`
+menu system, you can't access a file in your `src/main/webapp` directory through
+your application unless you explicitly define that it's meant to be accessed.
+
+Hooking up a simple page in `SiteMap` is easy, and seems redundant; rest
+assured, we'll explore the real power of `SiteMap` as the application becomes
+more complicated. All you have to do for the chat page is add a line to your
+`SiteMap.scala` that names the page and points to the file in the `webapp`
+directory:
+
+```src/scala/bootstrap/liftweb/Boot.scala
+...
+ Menu.i("Chat") / "chat"
+...
+```
+
+The string passed to `i` is the name of this menu. We can use that to
+link:menu-links[automatically render links for our menu]. It gets processed
+through Lift's internationalization system, but since we've got no
+internationalization set up for now it'll just go through unchanged. The part
+after the `/` specifies where the template will be found—in our case, in the
+`chat.html` file directly under `src/main/webapp`.
+
+With that out of the way, we can move on to link:3-adding-snippet-bindings.adoc[bringing our HTML to life].
diff --git a/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc
new file mode 100644
index 0000000000..00334f75c9
--- /dev/null
+++ b/docs/getting-started-tutorial/3-adding-snippet-bindings.adoc
@@ -0,0 +1,74 @@
+:idprefix:
+:idseparator: -
+:toc: right
+:toclevels: 2
+
+# Adding Snippet Bindings
+
+In most frameworks, a page's data is looked up by a controller, and backend
+code clutters the HTML to produce the correct rendering of the data. This
+process is usually done through what amounts to little more than string
+munging. Lift throws this paradigm away entirely in favor of a much better
+approach based on entities called snippets.
+
+Snippets let you refer to a block of code that is responsible for rendering a
+particular part of your page. You add these references by augmenting your HTML
+with a few completely valid `data-` attributes that get stripped before the
+HTML is then sent to the browser. These snippets then take your HTML, fully
+parsed into a valid DOM tree, and transform it, providing true decoupling
+between your business logic and your template, and an extra level of
+security footnote:[We already mentioned that Lift is secure by default, and
+another way that manifests is that the template HTML is turned into a
+first-class XML tree early in the processing cycle, and snippets just transform
+that tree. That means script injection and a variety of other attacks are
+significantly more difficult against a Lift codebase.].
+
+
+Let's look at our chat app specifically. We're going to bind two things: the
+list of chat messages, and the text input that lets us actually chat. To the
+`ol` that contains the chat messages, we add:
+
+```html:src/main/webapp/index.html
+
+```
+
+And to the input form:
+
+```html:src/main/webapp/index.html
+
+```
+
+Our `sendMessage` snippet looks like this:
+
+```scala
+...
+ def sendMessage(contents: NodeSeq) = contents
+...
+```
+
+We want to bind two things above. The first is the text field, which we want to
+bind so that we can get a message from the user, and the second is the submit
+button, so that we can process the new message. Here's how we can do that:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+import net.liftweb.http.SHtml
+
+...
+ def sendMessage = {
+ var message: String = ""
+
+ "#new-message" #> SHtml.text(message, message = _) &
+ "type=submit" #> SHtml.submitButton(() => {
+ messageEntries ::= message
+ })
+ }
+...
+```
+
+First things first, we're using the `SHtml` singleton. This singleton contains
+Lift's form handling helpers. We're using two of them here. The first is
+`SHtml.text`. This returns an `input type="text"` whose initial value is the
+first parameter you pass to it. The second parameter is a function that runs
+when the field is submitted. It takes in a single `String`, which is the value
+the user submitted, and does something with it. In our case, we use Scala
+shorthand to indicate the field's handler will just assign the value submitted
+by the user to the `message` variable.
+
+The second form helper we're using is `SHtml.submitButton`. This returns an
+`input type="submit"` that runs the function you pass to it when the form is
+submitted. In this case, when the form submits, we're going to prepend the
+value of `message` to the existing message entries list.
+
+Before continuing, let's change the `messages` snippet so it doesn't keep
+adding a new message on each page load:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+ def messages = {
+ ClearClearable &
+ "li *" #> messageEntries
+ }
+...
+```
+
+Now we can restart the server, and when we reload the page we'll be able to
+post messages and see them appear on the list of entries.
+
+So now we have a basic chat page. We've fulfilled our two initial use cases:
+
+ - As a chatter, I want to post a message so that others can see it.
+ - As a chatter, I want to see messages from me and others so that I can keep
+ track of the conversation and contribute in context.
+
+But, this clearly isn't a particularly usable chat. For one, we don't actually
+know who's posting what message. For another, the current implementation of the
+messages relies on a single variable that updates when a user posts to it. This
+works fine when there's just one user posting a time, but once multiple users
+start submitting the post form simultaneously, we start getting into serious
+threading and data consistency issues.
+
+Let's link:6-adding-usernames.adoc[deal with usernames first].
diff --git a/docs/getting-started-tutorial/6-adding-usernames.adoc b/docs/getting-started-tutorial/6-adding-usernames.adoc
new file mode 100644
index 0000000000..2289cfe850
--- /dev/null
+++ b/docs/getting-started-tutorial/6-adding-usernames.adoc
@@ -0,0 +1,153 @@
+:idprefix:
+:idseparator: -
+:toc: right
+:toclevels: 2
+
+# Adding Usernames
+
+We're about to add another use case to our chat system:
+
+ - As a chatter, I want to see who posted a message so that I have better
+ context for the conversation.
+
+The first thing we'll do is change the HTML to look like we want it to. Let's
+add the username:
+
+```html:src/main/webapp/index.html
+...
+
+ Antonio Hi!
+
+
+ David Oh, hey there.
+
+
+ Antonio How are you?
+
+
+ Antonio Good, you?
+
+...
+```
+
+Initially, we'll generate a username for the current user. We can store it in a
+`SessionVar`. `SessionVar`s in Lift are used to store things that should exist
+for the duration of a user's session. A user's session exists as long as Lift
+is aware of the user viewing a page related to that session. If Lift sees no
+activity from a given session after 20 minutes foonote:[This is configurable,
+of course. See `LiftRules.sessionInactivityTimeout`.], the session will be
+thrown away, as will the associated `SessionVar` values and related data.
+
+For now, let's look at adding the `SessionVar` to the `Chat` snippet:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+object username extends SessionVar[String]("username")
+
+object Chat {
+...
+```
+
+Here, we create a new `SessionVar`, whose default value will be “username” if it
+is accessed without having been set. We can change that to be random:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+object username extends SessionVar[String]("User " + randomString(5))
+```
+
+We're using a Lift helper called `randomString`. We just pass it a length and
+it gives us back a random string of that length. This'll make sure that each
+user session has a (reasonably) unique username.
+
+Now, we need to store usernames alongside messages. Let's do that by making the
+messageEntries list contain a case class instance instead of a simple `String`:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+case class ChatMessage(poster: String, body: String) // <1>
+class Chat {
+ var messageEntries = List[ChatMessage]() // <2>
+
+ def messages = {
+ ClearClearable &
+ "li" #> messageEntries.map { entry => // <3>
+ ".poster *" #> entry.poster &
+ ".body *" #> entry.body
+ }
+ }
+...
+```
+<1> First, we introduce a new case class, `ChatMessage`, that carries a poster
+ and a message body.
+<2> We also update `messageEntries` to be a list of ``ChatMessage``s instead of
+ plain ``String``s.
+<3> One of the big changes here is how we update the `messages` snippet method.
+ Before, we just mapped the content of `li` to the list of ``String``s.
+ However, `ChatMessage` objects can't be dealt with so simply. Instead, the
+ left side becomes a simple selection of `li`. The right side is now a list
+ of CSS selector transforms -- one for each `ChatMessage`. As before, Lift
+ copies the contents of the `li` once for each entry in the list, and then
+ transforms it according to that particular entry. In this case, rather than
+ just putting a string into the `li`, we set the contents of the `.poster`
+ and `.body` elements inside it.
+
+Now let's update the binding of the `sendMessage` form to deal with the new
+`ChatMessage` class:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+ def sendMessage = {
+ var message = ChatMessage("", "") // <1>
+
+ "#new-message" #> SHtml.text( // <2>
+ message,
+ { messageBody: String => message = Full(ChatMessage(username.get, messageBody)) } // <3>
+ ) &
+ "type=submit" #> SHtml.submitButton(() => { // <4>
+ for (body <- message) {
+ messageEntries ::= message
+ }
+ })
+ }
+}
+```
+<1> Before, we used an empty `String` as our starting value for the message.
+ However, we can't do that anymore here. Our only option would be to use
+ `null`, but `null` is very dangerous footnote:[Why is not dealing with
+ `null` desirable? Using a `Box` lets you deal with "this value isn't there"
+ as an inherent type. `null`, on the other hand, is something that can
+ masquerade as any value (for example, you can put `null` into either a
+ `ChatMessage` or a `String`), and the compiler can't check for you that you
+ made sure this optional value was set before using it. With a `Box`, the
+ compiler will enforce the checks so that you'll know if there's a
+ possibility of a value not being set.] and, as a rule, we avoid using it in
+ Scala. Instead, we use an `Empty` `Box`, and, when we receive a message
+ body, we create a `Full` `Box` with the newly posted `ChatMessage`.
+<2> Here, we update the handler for the `#new-message` text field. Before, the
+ handler function was `message = _`; when `message` was a `String`, we could
+ simply assign the message the user sent directly to it, and we were good to
+ go. However, `message` is now a `ChatMessage` -- it has to carry not only
+ the message body that the user typed, but also their username. To do that,
+ we write a complete handler function that takes in the body that the user
+ submitted with the form and, combined with the current user's username,
+ creates a `ChatMessage`. This `ChatMessage` is what we now put into the
+ `message` variable.
+<3> Notably, `username.get` is how you fetch the current value of the `username`
+ `SessionVar`. Don't confuse it with the `.get` on `Option`, which is very
+ dangerous! If you prefer to use a method that is less easily confused with
+ ``Option``'s `.get` (as many Lift developers and committers do), you can use
+ `.is` instead, which does the same thing.
+<4> As a result of the `Box` wrapping the submitted `ChatMessage`, we have to
+ update the submission handler. We use a `for` comprehension to unpack the
+ value of `message`. The body of that comprehension won't run unless
+ `message` is `Full`, so we can't try to insert an empty message into the
+ message list.
+
+Now that we have a reasonably nice chat system with actual usernames, it's time
+to look at the underlying issue of *consistency*. If two users posted a chat
+message at the same time right now, who knows what would happen to the
+`messageEntries` list? We could end up with only one of their messages, or with
+both, or with an undefined state of nastiness.
+
+Before letting a user set their own username, let's deal with this issue by
+serializing the posting of and access to messages using a simple mechanism:
+link:7-using-actors-for-chat.adoc[an actor].
diff --git a/docs/getting-started-tutorial/7-using-actors-for-chat.adoc b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc
new file mode 100644
index 0000000000..35d3ef4004
--- /dev/null
+++ b/docs/getting-started-tutorial/7-using-actors-for-chat.adoc
@@ -0,0 +1,160 @@
+:idprefix:
+:idseparator: -
+:toc: right
+:toclevels: 2
+
+# Using Actors for Chats
+
+Actors are fairly simple: they receive messages, do something with them, and
+potentially respond to them. This isn't unlike the process that you go through
+when you call a method on a regular class; however, when you send a message to
+an actor, it is processed after any earlier messages you sent, never at the
+same time. This makes it a lot easier to reason about what's going on at any
+given point, even if multiple threads are sending messages to the actor.
+
+## Storing the Message List
+
+We're going to use a fairly basic actor for now. As before, we're going to have
+a single chat room for the entire site we're building, so we'll use a singleton
+object. Lift provides a very simple actor implementation, which we'll be using
+here. There are more complicated actor systems, like the one provided by
+http://aka.io[Akka], but they're only necessary in cases where you need more
+flexibility or fault tolerance. We'll stick to the easy stuff, starting with a
+new file at `src/main/scala/code/actor/ChatActor.scala`:
+
+```scala:src/main/scala/code/actor/ChatActor.scala
+package code
+package actor
+
+import net.liftweb.actor._
+
+case class ChatMessage(poster: String, body: String)
+
+case class MessagePosted(message: ChatMessage)
+
+object ChatActor extends LiftActor {
+ private var messageEntries = List[ChatMessage]()
+
+ def messageHandler = {
+ case MessagePosted(newMessage) =>
+ messageEntries ::= newMessage
+ }
+}
+```
+
+This provides a very basic actor that can receive a new message and add it to
+its internal list. We've moved the `ChatMessage` class from the `Chat` snippet
+to this file. Typically, messages to actors are case classes. This is because
+they're easy to pattern match (as you can see, message handling is done via
+pattern matching footnote:[Strictly speaking, `messageHandler` is a
+`PartialFunction`. This means that it can match any subset of objects that it
+wants to.]) and because they're generally immutable, so there's no chance of
+someone else trying to modify the message as we're processing it.
+
+To ask the actor to add a message, we'll send it the `MessagePosted` message
+using the `!` operator. Here's how we can update our code in the `Chat`
+snippet:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+import actor._
+...
+ def sendMessage = {
+ ...
+ "type=submit" #> SHtml.submitButton(() => {
+ for (body <- message) {
+ ChatActor ! MessagePosted(message)
+ }
+ })
+ }
+...
+```
+
+Now, whenever a message is posted, it will be sent to the `ChatActor`, which
+will update its internal list.
+
+This is, however, only half of the equation. Putting messages into the actor
+isn't useful if we can't get them back out!
+
+## Retrieving Messages
+
+To retrieve messages, we can add a new message for the `ChatActor`:
+
+```scala:src/main/scala/code/actor/ChatActor.scala
+...
+case class MessagePosted(message: ChatMessage)
+case object GetMessages
+...
+```
+
+And a handler for it:
+
+```scala:src/main/scala/code/actor/ChatActor.scala
+...
+ def messageHandler = {
+ ...
+ case GetMessages =>
+ reply(messageEntries)
+ }
+...
+```
+
+When handling `GetMessages`, we use the `reply` method. This method lets us
+send an answer back to the person who sent us this message. By default,
+messages don't send answers, and the `!` operator is non-blocking, meaning it
+adds the message to the end of the actor's list of messages to process and then
+lets the original code continue running without waiting for the actor to deal
+with it.
+
+To wait for a reply, we have to use the `!?` operator instead. We do this when
+listing messages by updating the `Chat` snippet:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+ def messages = {
+ val messageEntries = Box.asA[List[ChatMessage]](ChatActor !? GetMessages) openOr List()
+
+ ClearClearable &
+ "li" #> messageEntries.map { entry =>
+ ".poster *" #> entry.poster &
+ ".body *" #> entry.body
+ }
+ }
+...
+```
+
+Two things to notice here. First off, we use `ChatActor !? GetMessages` to
+retrieve the messages. This will block until the `ChatActor` can process our
+message and send the reply back to us. Unfortunately, because we're not
+invoking a method, there is no type safety in the `!?` operator, so the
+compiler doesn't know what the type that `GetMessages` will return to us is.
+Because of that, we have to do some casting. To deal with this, Lift provides a
+very handy utility function, `Box.asA[T]`; it attempts to convert its parameter
+to the type `T`, and, if it succeeds, provides a `Full` `Box` with the
+converted value of the appropriate type. If it fails, it provides an `Empty`
+`Box` instead.
+
+To deal with the fact that the `Box` may be `Full` or `Empty`, we use `openOr`
+on the `Box`. We do this because the type of `messageEntries` is now a
+`Box[List[ChatMessage]]`, meaning a box that *contains* a list of chat
+messages. `openOr` will give us the plain list of messages if the `Box` is
+`Full`, and return the second parameter, an empty `List`, if the `Box` is
+`Empty`.
+
+It's worth mentioning that it seems like we *know* we'll be getting a
+`List[ChatMessage]` from the actor. However, the compiler *doesn't*, and that
+means it can't guarantee to us that future changes won't render that assumption
+false. Using `Box.asA` ensures that, if someone changes the `ChatActor` later
+to reply with something else, our snippet won't blow up in the user's face—it
+will just not display the existing messages. The intrepid reader can then go
+and fix the issue.
+
+Another annoyance in the code as it stands now is that if 8000 people are
+posting messages and I log into the site, my page won't load until those 8000
+messages are processed by the actor. That's because of how `reply` works: we
+wait until the actor gets to our message and then replies to it. There are far
+better ways of dealing with both of these issues, which we'll talk about when
+we talk about using `CometActor`s link:9-comet-actors[later].
+
+First, though, let's go back and look at how we can let the user link:8-customizable-usernames.adoc[change their
+username so they don't have to use our nasty automatically-generated name].
diff --git a/docs/getting-started-tutorial/8-customizable-usernames.adoc b/docs/getting-started-tutorial/8-customizable-usernames.adoc
new file mode 100644
index 0000000000..76a2cab454
--- /dev/null
+++ b/docs/getting-started-tutorial/8-customizable-usernames.adoc
@@ -0,0 +1,69 @@
+:idprefix:
+:idseparator: -
+:toc: right
+:toclevels: 2
+
+# Customizable Usernames
+
+Let's deal with the next use case:
+
+ - As a chatter, I want to change what name other users see for me when I post
+ a message.
+
+What we really want is a text box on the client that will let us edit the name.
+We'll add it to the top of our chat area in `chat.html`:
+
+```html:src/main/webapp/index.html
+...
+
+
+...
+```
+
+The ideal way for this to work would be for you to be able to change the value
+of the field and have it save once the cursor leaves the field (i.e., on blur).
+We can do exactly that using Lift's `ajaxText` helper in `Chat.scala`:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+ def nameField = {
+ "input" #> SHtml.ajaxText(username.is, username.set _)
+ }
+...
+```
+
+How's that for ludicrously easy? We create an `ajaxText` whose initial value
+will be the value of the `username` `SessionVar` that we created initially to
+track the user's name. The second parameter to `ajaxText` is what gets run when
+a change occurs on the client, and we hook it up directly to the ``SessionVar``'s
+`set` method, so that changing the text field on the client changes the
+`SessionVar`.
+
+However, maybe we want to provide some feedback to the user to let them know
+the name has been updated. We can get a little more detailed:
+
+```scala:src/main/scala/code/snippet/Chat.scala
+...
+ def nameField = {
+ "input" #> SHtml.ajaxText(username.is, { updatedUsername: String =>
+ username.set(updatedUsername)
+
+ Alert("Updated your username!")
+ }
+ }
+...
+```
+
+Now, when the change gets saved, the user will get a popup that will say
+“Updated your username!”. Note that `ajaxText` fields are set up to submit
+their changes on blur *or* when the user hits `enter` in the field.
+
+Now that the user can update their name, it's time to make things truly real
+time. Until now, to see the messages someone else has posted, we'd have to
+reload the page. Only our messages were posted to the page in real time. Not
+much of a chat at all, is it!
+
+It's time to link:9-comet-actors.adoc[break out the `CometActor`].
diff --git a/docs/getting-started-tutorial/9-comet-actors.adoc b/docs/getting-started-tutorial/9-comet-actors.adoc
new file mode 100644
index 0000000000..4d0fce4ba0
--- /dev/null
+++ b/docs/getting-started-tutorial/9-comet-actors.adoc
@@ -0,0 +1,22 @@
+:idprefix:
+:idseparator: -
+:toc: right
+:toclevels: 2
+
+# Comet Actors
+
+Lift has very robust support for pushing information to the client without the
+user having to take explicit action, and it's all mediated through a
+`CometActor`. `CometActor` works in many ways exactly like the regular actor
+we're already using to track chat messages, only `CometActor`s have a couple
+more tricks up their sleeve. Most notably for our purposes: they can re-render
+themselves in response to stuff going on inside the server, and send the
+updated version to the client. This finally gives us a way to update Jane's
+chat message list when Jill posts a message, without Jane having to do
+anything.
+
+Our first move will be to change how exactly we handle binding chat messages.
+First, we'll do a quick conversion that puts everything in a `CometActor`, but
+doesn't add any additional functionality. Instead of calling
+
+TODO Apparently I stopped midsentence here, so there's more to fill in ;)