Merging from the 2.1 I/O branch through r8059

Review by: mmendez@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8223 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/build.xml b/bikeshed/build.xml
index c0647f6..4ab7590 100644
--- a/bikeshed/build.xml
+++ b/bikeshed/build.xml
@@ -26,6 +26,7 @@
     <gwt.javac excludes="**/sample/**">
       <classpath>
         <pathelement location="${gwt.tools.redist}/json/r2_20080312/json.jar" />
+        <pathelement location="${gwt.root}/bikeshed/war/temp-libs/validation-api-1.0.0.GA.jar" />
         <pathelement location="${gwt.dev.jar}" />
         <pathelement location="${gwt.user.jar}" />
       </classpath>
diff --git a/bikeshed/eclipse.README b/bikeshed/eclipse.README
index a36dd98..67913f8 100644
--- a/bikeshed/eclipse.README
+++ b/bikeshed/eclipse.README
@@ -13,28 +13,28 @@
 classes/
 # Matches gwtc output in the war directory, e.g. war/com.google.gwt.bikeshed.tree.Tree/
 com.google.gwt.*
-bikeshed/war/expensesCustomized/
-bikeshed/war/expensesScaffold/
-bikeshed/war/mail/
-bikeshed/war/pagedExpenses/
-bikeshed/war/simplecelllist/
-bikeshed/war/stocksdesktop/
-bikeshed/war/stocksmobile/
-bikeshed/war/tree/
-bikeshed/war/validation/
-appengine-generated/
+bikeshed/war/cookbook/
+bikeshed/war/expenses/
+bikeshed/war/expensesMobile/
+bikeshed/war/scaffold/
+bikeshed/war/scaffoldMobile/
+bikeshed/war/stocks/
+bikeshed/war/stocksMobile/
 
 * Install the Google Plugin for Eclipse
 * Create a new Java project with trunk/bikeshed/ as its existing source
+  * exclude bikeshed/super and bikeshed/test-super
 * Bring up the project properties
   * Google > Web Toolkit > Use Google Web Toolkit
   * Google > App Engine > Use Google App Engine
   * Google > App Engine > ORM
-    * Remove src and test
+    * Remove all existing entries
     * Add 
       * src/com/google/gwt/sample/bikeshed/stocks/server
       * src/com/google/gwt/sample/bikeshed/stocks/shared
       * src/com/google/gwt/sample/expenses/server/domain
   * Java Build Path > Libraries > Add Variable > GWT_TOOLS, Extend > redist/json/r2_20080312/json.jar
 * Copy tools/redist/json/r2_20080312/json.jar to bikeshed/war/WEB_INF/lib
+* Copy all jars from  bikeshed/war/temp-lib to bikeshed/war/WEB-INF/lib
 * Right click on the bikeshed project and choose Run as > Web Application. Choose from the various .html files
+
diff --git a/bikeshed/src/META-INF/persistence.xml b/bikeshed/src/META-INF/persistence.xml
index f4731b4..3733ffc 100644
--- a/bikeshed/src/META-INF/persistence.xml
+++ b/bikeshed/src/META-INF/persistence.xml
@@ -7,6 +7,7 @@
     <persistence-unit name="transactions-optional">
         <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
         <class>com.google.gwt.sample.expenses.server.domain.Employee</class>
+        <class>com.google.gwt.sample.expenses.server.domain.Expense</class>
         <class>com.google.gwt.sample.expenses.server.domain.Report</class>
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
         <properties>
diff --git a/bikeshed/src/com/google/gwt/app/App.gwt.xml b/bikeshed/src/com/google/gwt/app/App.gwt.xml
index f553b63..8d391fa 100644
--- a/bikeshed/src/com/google/gwt/app/App.gwt.xml
+++ b/bikeshed/src/com/google/gwt/app/App.gwt.xml
@@ -1,11 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Could not determine the version of your GWT SDK; using the module DTD from GWT 1.6.4. You may want to change this. -->
-<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distro-source/core/src/gwt-module.dtd">
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
 <module>
   <inherits name='com.google.gwt.user.User'/>
   <inherits name='com.google.gwt.valuestore.ValueStore'/>
 
-  <source path="misc"/>
   <source path="place"/>
   <source path="client"/>
-</module>
\ No newline at end of file
+  <source path="util"/>
+  <generate-with class="com.google.gwt.app.rebind.EditorSupportGenerator">
+    <when-type-assignable class='com.google.gwt.app.client.EditorSupport'/>
+  </generate-with>
+</module>
diff --git a/bikeshed/src/com/google/gwt/app/client/EditorSupport.java b/bikeshed/src/com/google/gwt/app/client/EditorSupport.java
new file mode 100644
index 0000000..aba862b
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/app/client/EditorSupport.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.app.client;
+
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.valuestore.ui.RecordEditView;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The DataBinder base class for all the editor functionality.
+ * 
+ * @param <R> the Record type
+ * @param <V> the View type
+ */
+public interface EditorSupport<R extends Record, V extends RecordEditView<R>> {
+  Set<Property<?>> getProperties();
+  
+  void init(final V view);
+
+  boolean isChanged(V view);
+
+  void setEnabled(V view, boolean enabled);
+  
+  void setValue(V view, R value);
+
+  void showErrors(V view, Map<String, String> errorMap);
+}
diff --git a/bikeshed/src/com/google/gwt/app/place/AbstractActivity.java b/bikeshed/src/com/google/gwt/app/place/AbstractActivity.java
index 2d9e5c7..8ea5182 100644
--- a/bikeshed/src/com/google/gwt/app/place/AbstractActivity.java
+++ b/bikeshed/src/com/google/gwt/app/place/AbstractActivity.java
@@ -17,7 +17,7 @@
 
 /**
  * Simple Activity implementation that is always willing to stop,
- * and does nothing onStop and onCancel
+ * and does nothing onStop and onCancel.
  */
 public abstract class AbstractActivity implements Activity {
 
diff --git a/bikeshed/src/com/google/gwt/app/place/Activity.java b/bikeshed/src/com/google/gwt/app/place/Activity.java
index 4f171bc..b36909d 100644
--- a/bikeshed/src/com/google/gwt/app/place/Activity.java
+++ b/bikeshed/src/com/google/gwt/app/place/Activity.java
@@ -15,41 +15,43 @@
  */
 package com.google.gwt.app.place;
 
-import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.app.util.IsWidget;
 
 /**
- * Implemented by objects that control a piece of user interface,
- * with a life cycle managed by an {@link ActivityManager}, in 
- * response to {@link PlaceChangeEvent} events as the user
- * navigates through the app. 
+ * Implemented by objects that control a piece of user interface, with a life
+ * cycle managed by an {@link ActivityManager}, in response to
+ * {@link PlaceChangeEvent} events as the user navigates through the app.
  */
 public interface Activity {
 
   /**
-   * Callback object used for asynchronous {@link Activity#start} requests,
-   * provides the widget this activity drives.
+   * Implemented by objects responsible for displaying the widgets that
+   * activities drive.
    */
-  public interface Callback {
-    void onStarted(Widget widget);
+  public interface Display {
+    void showActivityWidget(IsWidget widget);
   }
 
   /**
    * Called when {@link #start} has not yet replied to its callback, but the
    * user has lost interest.
    */
-  public void onCancel();
+  void onCancel();
 
   /**
    * Called when the Activity's widget has been removed from view.
    */
-  public void onStop();
+  void onStop();
 
   /**
-   * Called when the Activity should prepare its {@link Widget} to the user.
+   * Called when the Activity should prepare its {@link IsWidget} for the user.
+   * Once the widget is ready (typically after an RPC response has been
+   * received), receiver should present it via
+   * {@link Display#showActivityWidget(IsWidget)}.
    * 
-   * @param callback allows the widget to be presented asynchronously
+   * @param panel the panel to display this activity's widget when it is ready
    */
-  public void start(Callback callback);
+  void start(Display panel);
 
-  public boolean willStop();
+  boolean willStop();
 }
diff --git a/bikeshed/src/com/google/gwt/app/place/ActivityManager.java b/bikeshed/src/com/google/gwt/app/place/ActivityManager.java
index eb5dee6..a1c1263 100644
--- a/bikeshed/src/com/google/gwt/app/place/ActivityManager.java
+++ b/bikeshed/src/com/google/gwt/app/place/ActivityManager.java
@@ -15,9 +15,9 @@
  */
 package com.google.gwt.app.place;
 
-import com.google.gwt.app.place.Activity.Callback;
+import com.google.gwt.app.place.Activity.Display;
+import com.google.gwt.app.util.IsWidget;
 import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.user.client.ui.Widget;
 
 /**
  * Manages {@link Activity} objects that should be kicked off in response to
@@ -31,22 +31,30 @@
     PlaceChangeEvent.Handler<P>, PlaceChangeRequestedEvent.Handler<P> {
 
   /**
-   * Implemented by the view of an ActivityManager,
+   * Wraps our real display to prevent an Activity from taking it over if it is
+   * not the currentActivity.
    */
-  public interface View {
-    /**
-     * Displays widget, swapping out the previous.
-     * 
-     * @param widget the widget to display
-     */
-    void setWidget(Widget widget);
+  private class ProtectedDisplay implements Display {
+    private final Activity activity;
+
+    ProtectedDisplay(Activity activity) {
+      this.activity = activity;
+    }
+
+    public void showActivityWidget(IsWidget view) {
+      if (this.activity == ActivityManager.this.currentActivity) {
+        startingNext = false;
+        display.showActivityWidget(view);
+      }
+    }
   }
 
   private final ActivityMapper<P> mapper;
-  private final HandlerManager eventBus;
 
+  private final HandlerManager eventBus;
   private Activity currentActivity;
-  private View display;
+  private Activity.Display display;
+
   private boolean startingNext = false;
 
   /**
@@ -71,31 +79,34 @@
   public void onPlaceChange(PlaceChangeEvent<P> event) {
     Activity nextActivity = mapper.getActivity(event.getNewPlace());
 
-    if (currentActivity != null) {
-      display.setWidget(null);
-      currentActivity.onStop();
+    if (currentActivity == nextActivity) {
+      return;
     }
 
     if (startingNext) {
       currentActivity.onCancel();
       currentActivity = null;
       startingNext = false;
+    } else if (currentActivity != null) {
+      display.showActivityWidget(null);
+      currentActivity.onStop();
     }
 
     if (nextActivity == null) {
-      display.setWidget(null);
+      display.showActivityWidget(null);
       currentActivity = null;
       return;
     }
 
     currentActivity = nextActivity;
     startingNext = true;
-    currentActivity.start(new Callback() {
-      public void onStarted(Widget widget) {
-        startingNext = false;
-        display.setWidget(widget);
-      }
-    });
+
+    /*
+     * Now start the thing. Wrap the actual display with a per-call instance
+     * that protects the display from canceled or stopped activities, and which
+     * maintain our startingNext state.
+     */
+    currentActivity.start(new ProtectedDisplay(currentActivity));
   }
 
   /**
@@ -134,7 +145,7 @@
    * 
    * @param display
    */
-  public void setDisplay(View display) {
+  public void setDisplay(Activity.Display display) {
     boolean wasActive = (null != this.display);
     boolean willBeActive = (null != display);
     this.display = display;
diff --git a/bikeshed/src/com/google/gwt/app/place/ActivityMapper.java b/bikeshed/src/com/google/gwt/app/place/ActivityMapper.java
index f8c383b..200959a 100644
--- a/bikeshed/src/com/google/gwt/app/place/ActivityMapper.java
+++ b/bikeshed/src/com/google/gwt/app/place/ActivityMapper.java
@@ -17,7 +17,7 @@
 
 /**
  * Finds the activity to run for a given {@link Place}, used to configure
- * an {@link ActivityManager}
+ * an {@link ActivityManager}.
  * 
  * @param <P> the type of place that can be mapped
  */
diff --git a/bikeshed/src/com/google/gwt/app/place/PlaceChangeEvent.java b/bikeshed/src/com/google/gwt/app/place/PlaceChangeEvent.java
index 82d28e6..af8b2db 100644
--- a/bikeshed/src/com/google/gwt/app/place/PlaceChangeEvent.java
+++ b/bikeshed/src/com/google/gwt/app/place/PlaceChangeEvent.java
@@ -27,7 +27,7 @@
     GwtEvent<PlaceChangeEvent.Handler<P>> {
 
   /**
-   * Implemented by handlers of PlaceChangeEvent
+   * Implemented by handlers of PlaceChangeEvent.
    * @param <P> the type of the new Place
    */
   public interface Handler<P extends Place> extends EventHandler {
diff --git a/bikeshed/src/com/google/gwt/app/place/PlaceChangeRequestedEvent.java b/bikeshed/src/com/google/gwt/app/place/PlaceChangeRequestedEvent.java
index 41a848e..db68fb9 100644
--- a/bikeshed/src/com/google/gwt/app/place/PlaceChangeRequestedEvent.java
+++ b/bikeshed/src/com/google/gwt/app/place/PlaceChangeRequestedEvent.java
@@ -27,7 +27,7 @@
     GwtEvent<PlaceChangeRequestedEvent.Handler<P>> {
 
   /**
-   * Implemented by handlers of PlaceChangeRequestedEvent
+   * Implemented by handlers of PlaceChangeRequestedEvent.
    * @param <P> the type of the requested Place
    */
   public interface Handler<P extends Place> extends EventHandler {
diff --git a/bikeshed/src/com/google/gwt/app/place/PlaceChanged.java b/bikeshed/src/com/google/gwt/app/place/PlaceChanged.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/app/place/PlaceChanged.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/app/rebind/EditorSupportGenerator.java b/bikeshed/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
new file mode 100644
index 0000000..f72167a
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/app/rebind/EditorSupportGenerator.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.app.rebind;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.Style.FontWeight;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.HasValue;
+import com.google.gwt.user.client.ui.TakesValue;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.PrintWriterManager;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.valuestore.shared.Property;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Generates implementations of
+ * {@link com.google.gwt.requestfactory.shared.RequestFactory RequestFactory}
+ * and its nested interfaces.
+ */
+public class EditorSupportGenerator extends Generator {
+
+  private class SuperInterfaceType {
+    private final JClassType recordType;
+    private final JClassType viewType;
+
+    SuperInterfaceType(JClassType interfaceType, TreeLogger logger)
+        throws UnableToCompleteException {
+      JClassType superInterfaces[] = interfaceType.getImplementedInterfaces();
+      JClassType superinterfaceType = superInterfaces[0];
+      if (superinterfaceType.isInterface() == null
+          || superinterfaceType.isParameterized() == null) {
+        logger.log(TreeLogger.ERROR, "The superclass of "
+            + superinterfaceType.getQualifiedSourceName()
+            + " is either not an interface or not a generic type");
+        throw new UnableToCompleteException();
+      }
+
+      JParameterizedType parameterizedType = superinterfaceType.isParameterized();
+      JClassType typeParameters[] = parameterizedType.getTypeArgs();
+      recordType = typeParameters[0];
+      viewType = typeParameters[1];
+    }
+  }
+
+  @Override
+  public String generate(TreeLogger logger, GeneratorContext generatorContext,
+      String interfaceName) throws UnableToCompleteException {
+    // The TypeOracle knows about all types in the type system
+    TypeOracle typeOracle = generatorContext.getTypeOracle();
+
+    // Get a reference to the type that the generator should implement
+    JClassType interfaceType = typeOracle.findType(interfaceName);
+
+    // Ensure that the requested type exists
+    if (interfaceType == null) {
+      logger.log(TreeLogger.ERROR, "Could not find requested typeName: "
+          + interfaceName);
+      throw new UnableToCompleteException();
+    }
+    if (interfaceType.isInterface() == null) {
+      // The incoming type wasn't a plain interface, we don't support
+      // abstract base classes
+      logger.log(TreeLogger.ERROR, interfaceType.getQualifiedSourceName()
+          + " is not an interface.", null);
+      throw new UnableToCompleteException();
+    }
+
+    SuperInterfaceType superinterfaceType = new SuperInterfaceType(
+        interfaceType, logger);
+    String implName = getImplName(superinterfaceType);
+    String packageName = interfaceType.getPackage().getName();
+    PrintWriterManager printWriters = new PrintWriterManager(generatorContext,
+        logger, packageName);
+    PrintWriter out = printWriters.tryToMakePrintWriterFor(implName);
+
+    // If an implementation already exists, we don't need to do any work
+    if (out != null) {
+      generateOnce(logger, generatorContext, out, interfaceType, packageName,
+          implName, superinterfaceType);
+      printWriters.commit();
+    }
+
+    return packageName + "." + implName;
+  }
+
+  private String findGetterMethod(JField property, JField uiField,
+      JClassType takesValueType, JClassType hasTextType, JClassType stringType,
+      TreeLogger logger) {
+
+    JClassType valueType = property.getType().isClass().isParameterized().getTypeArgs()[0];
+
+    JClassType uiFieldClassType = uiField.getType().isClass();
+    
+    if (takesValueType.isAssignableFrom(uiFieldClassType)) {
+      for (JClassType implemented : uiFieldClassType.getImplementedInterfaces()) {
+        JParameterizedType parameterized = implemented.isParameterized(); 
+        if (parameterized != null && (takesValueType == parameterized.getRawType()) 
+          && (valueType == parameterized.getTypeArgs()[0])) {
+          return "getValue";
+        }
+      }
+    }
+
+    if ((stringType == valueType)
+        && hasTextType.isAssignableFrom(uiFieldClassType)) {
+      return "getText";
+    }
+
+    logger.log(TreeLogger.WARN, String.format("Unable to take values from field \"%s\""
+        + " due to EditorSupport still being a complete hack.", uiField.getName()));
+
+    return null;
+  }
+
+  /**
+   * returns true if the change handlers are to be generated.
+   */
+  private boolean generateChangeHandlers(JClassType type,
+      JClassType takesValueType) {
+    if (type.isAssignableTo(takesValueType)) {
+      return true;
+    }
+    return false;
+  }
+
+  private void generateOnce(TreeLogger logger,
+      GeneratorContext generatorContext, PrintWriter out,
+      JClassType interfaceType, String packageName, String implName,
+      SuperInterfaceType superinterfaceType) throws UnableToCompleteException {
+
+    logger = logger.branch(TreeLogger.DEBUG, String.format(
+        "Generating implementation of %s", interfaceType.getName()));
+
+    ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
+        packageName, implName);
+    f.addImport(ValueChangeEvent.class.getName());
+    f.addImport(ValueChangeHandler.class.getName());
+    f.addImport(superinterfaceType.recordType.getQualifiedSourceName());
+    f.addImport(Property.class.getName());
+    f.addImport(DivElement.class.getName());
+    f.addImport(Document.class.getName());
+    f.addImport(SpanElement.class.getName());
+    f.addImport(FontWeight.class.getName().replace("$", "."));
+
+    f.addImport(HashSet.class.getName());
+    f.addImport(Map.class.getName());
+    f.addImport(Set.class.getName());
+    f.addImplementedInterface(interfaceType.getName());
+
+    SourceWriter sw = f.createSourceWriter(generatorContext, out);
+    sw.println();
+
+    JClassType takesValueType = generatorContext.getTypeOracle().findType(
+        TakesValue.class.getName());
+    JClassType hasTextType = generatorContext.getTypeOracle().findType(
+        HasText.class.getName());
+    JClassType stringType = generatorContext.getTypeOracle().findType(
+        String.class.getName());
+    JClassType recordType = superinterfaceType.recordType;
+    JClassType viewType = superinterfaceType.viewType;
+    writeGetPropertiesMethod(sw, recordType);
+
+    Set<JField> uiPropertyFields = getUiPropertyFields(viewType, recordType);
+    writeInit(sw, viewType, recordType, uiPropertyFields, takesValueType,
+        logger);
+    writeIsChangedMethod(sw, recordType, viewType, uiPropertyFields,
+        takesValueType, hasTextType, stringType, logger);
+    writeSetEnabledMethod(sw, viewType, uiPropertyFields, takesValueType);
+    writeSetValueMethod(sw, recordType, viewType, uiPropertyFields,
+        generatorContext, logger);
+    writeShowErrorsMethod(sw, viewType);
+
+    sw.outdent();
+    sw.println("}");
+  }
+
+  private Collection<JMethod> getAccessibleMethods(JClassType classType) {
+    boolean isInterface = false;
+    if (classType.isInterface() != null) {
+      isInterface = true;
+    }
+    Map<String, JMethod> methodsBySignature = new HashMap<String, JMethod>();
+    LinkedList<JClassType> classesToBeProcessed = new LinkedList<JClassType>();
+    classesToBeProcessed.add(classType);
+    JClassType tempClassType = null;
+    while (classesToBeProcessed.peek() != null) {
+      tempClassType = classesToBeProcessed.remove();
+      JMethod declaredMethods[] = tempClassType.getMethods();
+      for (JMethod method : declaredMethods) {
+        if (method.isPrivate()) {
+          continue;
+        }
+        String signature = method.getJsniSignature();
+        JMethod existing = methodsBySignature.put(signature, method);
+        if (existing != null) {
+          // decide which implementation to keep
+          if (existing.getEnclosingType().isAssignableTo(
+              method.getEnclosingType())) {
+            methodsBySignature.put(signature, existing);
+          }
+        }
+      }
+      if (isInterface) {
+        classesToBeProcessed.addAll(Arrays.asList(tempClassType.getImplementedInterfaces()));
+      } else {
+        classesToBeProcessed.add(tempClassType.getSuperclass());
+      }
+    }
+    return methodsBySignature.values();
+  }
+
+  private Set<String> getAccessiblePropertyFields(JClassType classType) {
+    boolean isInterface = false;
+    if (classType.isInterface() != null) {
+      isInterface = true;
+    }
+    Map<String, JField> fieldsByName = new HashMap<String, JField>();
+    LinkedList<JClassType> classesToBeProcessed = new LinkedList<JClassType>();
+    classesToBeProcessed.add(classType);
+    JClassType tempClassType = null;
+    while (classesToBeProcessed.peek() != null) {
+      tempClassType = classesToBeProcessed.remove();
+      JField declaredFields[] = tempClassType.getFields();
+      for (JField field : declaredFields) {
+        if (field.isPrivate()
+            || !(field.getType().getQualifiedSourceName().equals(Property.class.getName()))) {
+          continue;
+        }
+        JField existing = fieldsByName.put(field.getName(), field);
+        if (existing != null) {
+          if (existing.getEnclosingType().isAssignableTo(
+              field.getEnclosingType())) {
+            fieldsByName.put(field.getName(), existing);
+          }
+        }
+      }
+      if (isInterface) {
+        classesToBeProcessed.addAll(Arrays.asList(tempClassType.getImplementedInterfaces()));
+      } else {
+        classesToBeProcessed.add(tempClassType.getSuperclass());
+      }
+    }
+    return fieldsByName.keySet();
+  }
+
+  /**
+   * returns the name of the Impl class.
+   */
+  private String getImplName(SuperInterfaceType superinterfaceType) {
+    return superinterfaceType.viewType.getName() + "_EditorSupport_Impl";
+  }
+
+  private JMethod getPropertyFunction(JClassType recordType,
+      String propertyFunctionName) {
+
+    for (JMethod method : getAccessibleMethods(recordType)) {
+      if (method.getName().equals(propertyFunctionName)
+          && (method.getParameters() == null || method.getParameters().length == 0)) {
+        return method;
+      }
+    }
+    return null;
+  }
+
+  private String getPropertyFunctionName(String name, TreeLogger logger)
+      throws UnableToCompleteException {
+    if (name == null || name.length() < 1) {
+      logger.log(TreeLogger.ERROR, "UiField name " + name
+          + " is either null or less than a character long");
+      throw new UnableToCompleteException();
+    }
+    return "get" + name.substring(0, 1).toUpperCase()
+        + name.substring(1, name.length());
+  }
+
+  /**
+   * Handle non-integer return types.
+   */
+  private String getSuffix(JMethod method, JClassType stringType) {
+    JClassType returnType = (JClassType) method.getReturnType();
+    if (returnType.isAssignableTo(stringType)) {
+      return "";
+    }
+    return ".toString()";
+  }
+
+  private Set<JField> getUiPropertyFields(JClassType viewType,
+      JClassType recordType) {
+    Set<String> recordFieldNames = getAccessiblePropertyFields(recordType);
+    Set<JField> uiPropertyFields = new HashSet<JField>();
+    for (JField field : viewType.getFields()) {
+      if (field.getAnnotation(UiField.class) != null
+          && recordFieldNames.contains(field.getName())) {
+        uiPropertyFields.add(field);
+      }
+    }
+    return uiPropertyFields;
+  }
+
+  /**
+   * Write the implementation for the getProperties() method.
+   */
+  private void writeGetPropertiesMethod(SourceWriter sw, JClassType recordType) {
+    sw.indent();
+    sw.println("public Set<Property<?>> getProperties() {");
+    sw.indent();
+    sw.println("Set<Property<?>> rtn = new HashSet<Property<?>>();");
+    for (JField field : recordType.getFields()) {
+      if (field.getType().getQualifiedSourceName().equals(
+          Property.class.getName())) {
+        sw.println("rtn.add(" + recordType.getName() + "." + field.getName()
+            + ");");
+      }
+    }
+    sw.println("return rtn;");
+    sw.outdent();
+    sw.println("}");
+    sw.outdent();
+  }
+
+  private void writeInit(SourceWriter sw, JClassType viewType,
+      JClassType recordType, Set<JField> uiPropertyFields,
+      JClassType takesValueType, TreeLogger logger) {
+    sw.indent();
+    sw.println("public void init(final " + viewType.getName() + " view) {");
+    sw.indent();
+    for (JField uiField : uiPropertyFields) {
+      if (!generateChangeHandlers((JClassType) uiField.getType(),
+          takesValueType)) {
+        continue;
+      }
+      sw.println("view." + uiField.getName()
+          + ".addValueChangeHandler(new ValueChangeHandler<String>() {");
+      sw.indent();
+      sw.println("public void onValueChange(ValueChangeEvent<String> event) {");
+      sw.indent();
+      JField recordField = recordType.getField(uiField.getName());
+      if (recordField == null) {
+        logger.log(TreeLogger.DEBUG, "Unable to find field name "
+            + uiField.getName() + " in " + recordType.getQualifiedSourceName());
+        continue;
+      }
+      sw.println("view.getDeltaValueStore().set(" + recordType.getName() + "."
+          + recordField.getName() + ", view.getValue(),");
+      sw.indent();
+      sw.println("event.getValue());");
+      sw.outdent();
+      sw.outdent();
+      sw.println("}");
+      sw.outdent();
+      sw.println("});");
+    }
+    sw.outdent();
+    sw.println("}");
+    sw.outdent();
+  }
+
+  private void writeIsChangedMethod(SourceWriter sw, JClassType recordType,
+      JClassType viewType, Set<JField> uiPropertyFields,
+      JClassType takesValueType, JClassType hasTextType, JClassType stringType,
+      TreeLogger logger) {
+    sw.indent();
+    sw.println("public boolean isChanged(" + viewType.getName() + " view) {");
+    sw.indent();
+    for (JField uiField : uiPropertyFields) {
+      JField property = recordType.getField(uiField.getName());
+      if (property != null) {
+        String getter = findGetterMethod(property, uiField, takesValueType,
+            hasTextType, stringType, logger);
+        if (getter != null) {
+          sw.println(String.format(
+              "view.getDeltaValueStore().set(%s.%s, view.getValue(), view.%s.%s());",
+              recordType.getName(), property.getName(), uiField.getName(),
+              getter));
+        }
+      }
+    }
+    sw.println("return view.getDeltaValueStore().isChanged();");
+    sw.outdent();
+    sw.println("}");
+    sw.outdent();
+  }
+
+  private void writeSetEnabledMethod(SourceWriter sw, JClassType viewType,
+      Set<JField> uiPropertyFields, JClassType takesValueType) {
+    sw.indent();
+    sw.println("public void setEnabled(" + viewType.getName()
+        + " view, boolean enabled) {");
+    sw.indent();
+    sw.println("// Note that we require package protection, just like UiBinder does.");
+    for (JField uiField : uiPropertyFields) {
+      if (!((JClassType) uiField.getType()).isAssignableTo(takesValueType)) {
+        continue;
+      }
+      sw.println("view." + uiField.getName() + ".setEnabled(enabled);");
+    }
+    sw.outdent();
+    sw.println("}");
+    sw.outdent();
+  }
+
+  private void writeSetValueMethod(SourceWriter sw, JClassType recordType,
+      JClassType viewType, Set<JField> uiPropertyFields,
+      GeneratorContext generatorContext, TreeLogger logger)
+      throws UnableToCompleteException {
+    JClassType hasTextType = generatorContext.getTypeOracle().findType(
+        HasText.class.getName());
+    JClassType takesValueType = generatorContext.getTypeOracle().findType(
+        HasValue.class.getName());
+    JClassType stringType = generatorContext.getTypeOracle().findType(
+        "java.lang.String");
+    sw.indent();
+    sw.println("public void setValue(" + viewType.getName() + " view, "
+        + recordType.getName() + " record) {");
+    sw.indent();
+
+    for (JField uiField : uiPropertyFields) {
+      JClassType classType = uiField.getType().isClassOrInterface();
+      if (classType == null) {
+        continue;
+      }
+      String propertyFunctionName = getPropertyFunctionName(uiField.getName(),
+          logger);
+      JMethod propertyFunction = getPropertyFunction(recordType,
+          propertyFunctionName);
+      if (propertyFunction == null) {
+        logger.log(TreeLogger.WARN,
+            "Not generating setValue/setText call for field " + uiField);
+        continue;
+      }
+      JType paramTypes[] = new JType[1];
+      paramTypes[0] = propertyFunction.getReturnType();
+
+      // TODO No method name matching magic! Rely on interfaces or nothing!
+      // Where are the checks that the property value matches the param type on
+      // TakesValue?
+
+      JMethod setValueMethod = classType.findMethod("setValue", paramTypes);
+      String suffix = "";
+      String functionName = "";
+      if (setValueMethod != null) {
+        // the setValue method works!, no need to change suffix
+        functionName = "setValue";
+      } else {
+        if (classType.isAssignableTo(takesValueType)) {
+          functionName = "setValue";
+        } else {
+          if (classType.isAssignableTo(hasTextType)) {
+            functionName = "setText";
+          } else {
+            functionName = "setValue";
+          }
+        }
+        suffix = getSuffix(propertyFunction, stringType);
+      }
+      sw.println("view." + uiField.getName() + "." + functionName + "(record."
+          + propertyFunctionName + "()" + suffix + ");");
+    }
+    sw.outdent();
+    sw.println("}");
+    sw.outdent();
+  }
+
+  private void writeShowErrorsMethod(SourceWriter sw, JClassType viewType) {
+    sw.indent();
+    sw.println("public void showErrors(" + viewType.getName()
+        + " view, Map<String, String> errorMap) {");
+    sw.indent();
+    sw.println("view.errors.setInnerText(\"\");");
+    sw.println("if (errorMap == null || errorMap.isEmpty()) {");
+    sw.indent();
+    sw.println("return;");
+    sw.outdent();
+    sw.println("}");
+    sw.println();
+
+    sw.println("Document doc = Document.get();");
+    sw.println("for (Map.Entry<String, String> entry : errorMap.entrySet()) {");
+    sw.println("  /*");
+    sw.println("   * Note that we are careful not to use setInnerHtml, to ensure we don't");
+    sw.println("   * render user created markup: xsite attack protection");
+    sw.println("   */");
+    sw.println("");
+    sw.indent();
+    sw.println("DivElement div = doc.createDivElement();");
+    sw.println("div.setInnerText(\" \" + entry.getValue());");
+    sw.println("");
+    sw.println("SpanElement name = doc.createSpanElement();");
+    sw.println("name.getStyle().setFontWeight(FontWeight.BOLD);");
+    sw.println("name.setInnerText(entry.getKey());");
+    sw.println("");
+    sw.println("div.insertFirst(name);");
+    sw.println("");
+    sw.println("view.errors.appendChild(div);");
+    sw.outdent();
+    sw.println("}");
+
+    sw.outdent();
+    sw.println("}");
+    sw.outdent();
+  }
+
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ClickableTextCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ClickableTextCell.java
deleted file mode 100644
index 0d0390d..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ClickableTextCell.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.cells.client;
-
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
-
-/**
- * A {@link Cell} used to render text.  Clicking on the call causes its
- * @{link ValueUpdater} to be called.
- */
-public class ClickableTextCell extends Cell<String, Void> {
-
-  private static ClickableTextCell instance;
-
-  public static ClickableTextCell getInstance() {
-    if (instance == null) {
-      instance = new ClickableTextCell();
-    }
-    return instance;
-  }
-
-  private ClickableTextCell() {
-  }
-
-  @Override
-  public Void onBrowserEvent(Element parent, String value, Void viewData,
-      NativeEvent event, ValueUpdater<String, Void> valueUpdater) {
-    String type = event.getType();
-    if (type.equals("click")) {
-      valueUpdater.update(value, null);
-    }
-    return null;
-  }
-
-  @Override
-  public void render(String value, Void viewData, StringBuilder sb) {
-    if (value != null) {
-      sb.append(value);
-    }
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/EllipsisCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/EllipsisCell.java
deleted file mode 100644
index 6b48a9a..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/EllipsisCell.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.cells.client;
-
-/**
- * Call that displays overflow using an ellipsis.
- */
-public class EllipsisCell extends Cell<String, Void> {
-
-  @Override
-  public void render(String value, Void viewData, StringBuilder sb) {
-    sb.append("<div style='overflow:hidden; white-space:nowrap; text-overflow:ellipsis;'>");
-    sb.append(value);
-    sb.append("</div>");
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextCell.java
deleted file mode 100644
index 0a3ec3e..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextCell.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.cells.client;
-
-/**
- * A {@link Cell} used to render text.
- */
-public class TextCell extends Cell<String, Void> {
-
-  private static TextCell instance;
-
-  public static TextCell getInstance() {
-    if (instance == null) {
-      instance = new TextCell();
-    }
-    return instance;
-  }
-
-  private TextCell() {
-  }
-
-  @Override
-  public void render(String value, Void viewData, StringBuilder sb) {
-    if (value != null) {
-      sb.append(value);
-    }
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/List.gwt.xml b/bikeshed/src/com/google/gwt/bikeshed/list/List.gwt.xml
index a6377da..a1ff279 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/List.gwt.xml
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/List.gwt.xml
@@ -15,7 +15,7 @@
 -->
 <module>
   <inherits name='com.google.gwt.user.User'/>
-  <inherits name='com.google.gwt.bikeshed.cells.Cells'/>
+  <inherits name='com.google.gwt.cell.Cell'/>
   <source path="shared" />
   <source path="client" />
 </module>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/AbstractPager.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/AbstractPager.java
new file mode 100644
index 0000000..c0db17f
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/AbstractPager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.client;
+
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.PagingListView.Pager;
+
+/**
+ * An abstract pager that exposes many methods useful for paging.
+ * 
+ * @param <T> the type of the PagingListView being controlled
+ */
+public abstract class AbstractPager<T> extends Composite implements Pager<T> {
+
+  /**
+   * If true, all operations should be limited to the data size.
+   */
+  private boolean isRangeLimited;
+
+  /**
+   * The {@link PagingListView} being paged.
+   */
+  private final PagingListView<T> view;
+
+  public AbstractPager(PagingListView<T> view) {
+    this.view = view;
+    view.setPager(this);
+  }
+
+  /**
+   * Go to the first page.
+   */
+  public void firstPage() {
+    setPage(0);
+  }
+
+  /**
+   * Get the current page index.
+   * 
+   * Since the page start index can be set to any value, its possible to be
+   * between pages. In this case, the return value is the number of times
+   * {@link #previousPage()} can be called.
+   * 
+   * @return the page index
+   */
+  public int getPage() {
+    int pageSize = view.getPageSize();
+    return (view.getPageStart() + pageSize - 1) / pageSize;
+  }
+
+  /**
+   * Get the number of pages based on the data size.
+   * 
+   * @return the page count
+   */
+  public int getPageCount() {
+    int pageSize = view.getPageSize();
+    return (view.getDataSize() + pageSize - 1) / pageSize;
+  }
+
+  /**
+   * Get the {@link PagingListView} being paged.
+   * 
+   * @return the {@link PagingListView}
+   */
+  public PagingListView<T> getPagingListView() {
+    return view;
+  }
+
+  /**
+   * Returns true if there is enough data such that a call to
+   * {@link #nextPage()} will succeed in moving the starting point of the table
+   * forward.
+   */
+  public boolean hasNextPage() {
+    return view.getPageStart() + view.getPageSize() < view.getDataSize();
+  }
+
+  /**
+   * Returns true if there is enough data such that the specified page is within
+   * range.
+   */
+  public boolean hasPage(int index) {
+    return view.getPageSize() * index < view.getDataSize();
+  }
+
+  /**
+   * Returns true if there is enough data such that a call to
+   * {@link #previousPage()} will succeed in moving the starting point of the
+   * table backward.
+   */
+  public boolean hasPreviousPage() {
+    return view.getPageStart() > 0 && view.getDataSize() > 0;
+  }
+
+  /**
+   * Check if the page should be limited to the actual data size.
+   * 
+   * @return true if the range is limited to the data size
+   */
+  public boolean isRangeLimited() {
+    return isRangeLimited;
+  }
+
+  /**
+   * Go to the last page.
+   */
+  public void lastPage() {
+    setPage(getPageCount() - 1);
+  }
+
+  /**
+   * Set the page start to the last index that will still show a full page.
+   */
+  public void lastPageStart() {
+    setPageStart(view.getDataSize() - view.getPageSize());
+  }
+
+  /**
+   * Advance the starting row by 'pageSize' rows.
+   */
+  public void nextPage() {
+    setPageStart(view.getPageStart() + view.getPageSize());
+  }
+
+  public void onRangeOrSizeChanged(PagingListView<T> listView) {
+    if (isRangeLimited) {
+      setPageStart(view.getPageStart());
+    }
+  }
+
+  /**
+   * Move the starting row back by 'pageSize' rows.
+   */
+  public void previousPage() {
+    setPageStart(view.getPageStart() - view.getPageSize());
+  }
+
+  /**
+   * Go to a specific page.
+   * 
+   * @param index the page index
+   */
+  public void setPage(int index) {
+    if (!isRangeLimited || hasPage(index)) {
+      // We don't use the local version of setPageStart because the user
+      // probably wants to use absolute page indexes.
+      view.setPageStart(view.getPageSize() * index);
+    }
+  }
+
+  /**
+   * Set the page start index.
+   * 
+   * @param index the index
+   */
+  public void setPageStart(int index) {
+    if (isRangeLimited) {
+      index = Math.min(index, view.getDataSize() - view.getPageSize());
+    }
+    index = Math.max(0, index);
+    view.setPageStart(index);
+  }
+
+  /**
+   * Set whether or not the page range should be limited to the actual data
+   * size. If true, all operations will adjust so that there is always data
+   * visible on the page.
+   * 
+   * @param isRangeLimited
+   */
+  public void setRangeLimited(boolean isRangeLimited) {
+    this.isRangeLimited = isRangeLimited;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/CellList.css b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellList.css
new file mode 100644
index 0000000..c0864fa
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellList.css
@@ -0,0 +1,15 @@
+.evenItem {
+  
+}
+
+.oddItem {
+  
+}
+
+@sprite .selectedItem {
+  gwt-image: 'cellListSelectedBackground';
+  background-color: #628cd5;
+  color: white;
+  height: auto;
+  overflow: auto;
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/CellList.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellList.java
new file mode 100644
index 0000000..68db2c8
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellList.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.client;
+
+import com.google.gwt.bikeshed.list.client.impl.CellListImpl;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+
+import java.util.List;
+
+/**
+ * A single column list of cells.
+ * 
+ * @param <T> the data type of list items
+ */
+public class CellList<T> extends Widget implements PagingListView<T> {
+
+  /**
+   * The default page size.
+   */
+  private static final int DEFAULT_PAGE_SIZE = 25;
+
+  /**
+   * Styles used by this widget.
+   */
+  public static interface Style extends CssResource {
+
+    /**
+     * Applied to even items.
+     */
+    String evenItem();
+
+    /**
+     * Applied to odd items.
+     */
+    String oddItem();
+
+    /**
+     * Applied to selected items.
+     */
+    String selectedItem();
+  }
+
+  /**
+   * A ClientBundle that provides images for this widget.
+   */
+  public static interface Resources extends ClientBundle {
+
+    /**
+     * The background used for selected items.
+     */
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource cellListSelectedBackground();
+
+    /**
+     * The styles used in this widget.
+     */
+    @Source("CellList.css")
+    Style cellListStyle();
+  }
+
+  private static Resources DEFAULT_RESOURCES;
+
+  private static Resources getDefaultResources() {
+    if (DEFAULT_RESOURCES == null) {
+      DEFAULT_RESOURCES = GWT.create(Resources.class);
+    }
+    return DEFAULT_RESOURCES;
+  }
+
+  private final Cell<T> cell;
+  private final Element emptyMessageElem;
+  private final CellListImpl<T> impl;
+  private final Style style;
+  private ValueUpdater<T> valueUpdater;
+
+  /**
+   * Construct a new {@link CellList}.
+   * 
+   * @param cell the cell used to render each item
+   */
+  public CellList(final Cell<T> cell) {
+    this(cell, getDefaultResources());
+  }
+
+  /**
+   * Construct a new {@link CellList} with the specified {@link Resources}.
+   * 
+   * @param cell the cell used to render each item
+   * @param resources the resources used for this widget
+   */
+  // TODO(jlabanca): Should cell support ViewData?
+  public CellList(final Cell<T> cell, Resources resources) {
+    this.cell = cell;
+    this.style = resources.cellListStyle();
+    this.style.ensureInjected();
+
+    // Create the DOM hierarchy.
+    Element childContainer = Document.get().createDivElement();
+
+    emptyMessageElem = Document.get().createDivElement();
+    emptyMessageElem.setInnerHTML("<i>no data</i>");
+    showOrHide(emptyMessageElem, false);
+
+    // TODO: find some way for cells to communicate what they're interested in.
+    DivElement outerDiv = Document.get().createDivElement();
+    outerDiv.appendChild(childContainer);
+    outerDiv.appendChild(emptyMessageElem);
+    setElement(outerDiv);
+    sinkEvents(Event.ONCLICK | Event.ONCHANGE | Event.MOUSEEVENTS);
+
+    // Create the implementation.
+    impl = new CellListImpl<T>(this, DEFAULT_PAGE_SIZE, childContainer) {
+
+      @Override
+      protected boolean dependsOnSelection() {
+        return cell.dependsOnSelection();
+      }
+
+      @Override
+      protected void emitHtml(StringBuilder sb, List<T> values, int start,
+          SelectionModel<? super T> selectionModel) {
+        int length = values.size();
+        int end = start + length;
+        for (int i = start; i < end; i++) {
+          T value = values.get(i - start);
+          boolean isSelected = selectionModel == null ? false
+              : selectionModel.isSelected(value);
+          sb.append("<div __idx='").append(i).append("'");
+          sb.append(" class='");
+          sb.append(i % 2 == 0 ? style.evenItem() : style.oddItem());
+          if (isSelected) {
+            sb.append(" ").append(style.selectedItem());
+          }
+          sb.append("'>");
+          cell.render(value, null, sb);
+          sb.append("</div>");
+        }
+      }
+
+      @Override
+      protected void onSizeChanged() {
+        super.onSizeChanged();
+        showOrHide(emptyMessageElem, impl.getDataSize() == 0);
+      }
+
+      @Override
+      protected void setSelected(Element elem, boolean selected) {
+        setStyleName(elem, style.selectedItem(), selected);
+      }
+    };
+  }
+
+  public int getDataSize() {
+    return impl.getDataSize();
+  }
+
+  public int getPageSize() {
+    return impl.getPageSize();
+  }
+
+  public int getPageStart() {
+    return impl.getPageStart();
+  }
+
+  public Range getRange() {
+    return impl.getRange();
+  }
+
+  @Override
+  public void onBrowserEvent(Event event) {
+    super.onBrowserEvent(event);
+
+    // Forward the event to the cell.
+    Element target = event.getEventTarget().cast();
+    String idxString = "";
+    while ((target != null)
+        && ((idxString = target.getAttribute("__idx")).length() == 0)) {
+      target = target.getParentElement();
+    }
+    if (idxString.length() > 0) {
+      int idx = Integer.parseInt(idxString);
+      T value = impl.getData().get(idx - impl.getPageStart());
+      cell.onBrowserEvent(target, value, null, event, valueUpdater);
+      if (event.getTypeInt() == Event.ONMOUSEDOWN && !cell.consumesEvents()) {
+        SelectionModel<? super T> selectionModel = impl.getSelectionModel();
+        if (selectionModel != null) {
+          selectionModel.setSelected(value, true);
+        }
+      }
+    }
+  }
+
+  public void setData(int start, int length, List<T> values) {
+    impl.setData(values, start);
+  }
+
+  public void setDataSize(int size, boolean isExact) {
+    impl.setDataSize(size);
+  }
+
+  public void setDelegate(Delegate<T> delegate) {
+    impl.setDelegate(delegate);
+  }
+
+  public void setPager(Pager<T> pager) {
+    impl.setPager(pager);
+  }
+
+  public void setPageSize(int pageSize) {
+    impl.setPageSize(pageSize);
+  }
+
+  public void setPageStart(int pageStart) {
+    impl.setPageStart(pageStart);
+  }
+
+  public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
+    impl.setSelectionModel(selectionModel, true);
+  }
+
+  /**
+   * Set the value updater to use when cells modify items.
+   * 
+   * @param valueUpdater the {@link ValueUpdater}
+   */
+  public void setValueUpdater(ValueUpdater<T> valueUpdater) {
+    this.valueUpdater = valueUpdater;
+  }
+
+  /**
+   * Show or hide an element.
+   * 
+   * @param element the element
+   * @param show true to show, false to hide
+   */
+  private void showOrHide(Element element, boolean show) {
+    if (show) {
+      element.getStyle().clearDisplay();
+    } else {
+      element.getStyle().setDisplay(Display.NONE);
+    }
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/CellTable.css b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellTable.css
new file mode 100644
index 0000000..aa7f4df
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellTable.css
@@ -0,0 +1,63 @@
+.cellTable {
+  border: 1px solid #88b0f2;
+}
+
+.firstColumn {
+  
+}
+
+@sprite .footer {
+  gwt-image: 'cellTableFooterBackground';
+  background-color: #b4d0f8;
+  border-top: 1px solid #88b0f2;
+  border-left: 1px solid #88b0f2;
+  border-right: 1px solid #eef;
+  padding: 5px 20px;
+  text-align: left;
+  color: #4b4a4a;
+  text-shadow: #ddf 1px 1px 0
+}
+
+@sprite .header {
+  gwt-image: 'cellTableHeaderBackground';
+  background-color: #b4d0f8;
+  border-bottom: 1px solid #88b0f2;
+  border-left: 1px solid #88b0f2;
+  border-right: 1px solid #eef;
+  padding: 0px 20px;
+  text-align: left;
+  color: #4b4a4a;
+  text-shadow: #ddf 1px 1px 0; 
+}
+
+.cell {
+  padding: 4px 10px;
+}
+
+.firstColumnFooter {
+  border-left: 0px;
+}
+
+.firstColumnHeader {
+  border-left: 0px;
+}
+
+.evenRow {
+  background-color: #ffffff;
+}
+
+.oddRow {
+  background-color: #eef4fb;
+}
+
+.hoveredRow {
+  background-color: #88b0f2;
+}
+
+@sprite .selectedRow {
+  gwt-image: 'cellTableSelectedBackground';
+  background-color: #628cd5;
+  color: white;
+  height: auto;
+  overflow: auto;
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/CellTable.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellTable.java
new file mode 100644
index 0000000..b36022d
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/CellTable.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.client;
+
+import com.google.gwt.bikeshed.list.client.impl.CellListImpl;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A list view that supports paging and columns.
+ * 
+ * @param <T> the data type of each row
+ */
+public class CellTable<T> extends Widget implements PagingListView<T> {
+
+  /**
+   * The default page size.
+   */
+  private static final int DEFAULT_PAGESIZE = 15;
+
+  /**
+   * Styles used by this widget.
+   */
+  public static interface Style extends CssResource {
+
+    /**
+     * Applied to every cell.
+     */
+    String cell();
+
+    /**
+     * Applied to the table.
+     */
+    String cellTable();
+
+    /**
+     * Applied to even rows.
+     */
+    String evenRow();
+
+    /**
+     * Applied to the first column.
+     */
+    String firstColumn();
+
+    /**
+     * Applied to the first column footers.
+     */
+    String firstColumnFooter();
+
+    /**
+     * Applied to the first column headers.
+     */
+    String firstColumnHeader();
+
+    /**
+     * Applied to footers cells.
+     */
+    String footer();
+
+    /**
+     * Applied to headers cells.
+     */
+    String header();
+
+    /**
+     * Applied to the hovered row.
+     */
+    String hoveredRow();
+
+    /**
+     * Applied to odd rows.
+     */
+    String oddRow();
+
+    /**
+     * Applied to selected rows.
+     */
+    String selectedRow();
+  }
+
+  /**
+   * A ClientBundle that provides images for this widget.
+   */
+  public static interface Resources extends ClientBundle {
+
+    /**
+     * The background used for header cells.
+     */
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource cellTableHeaderBackground();
+
+    /**
+     * The background used for footer cells.
+     */
+    @Source("cellTableHeaderBackground.png")
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource cellTableFooterBackground();
+
+    /**
+     * The background used for selected cells.
+     */
+    @Source("cellListSelectedBackground.png")
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource cellTableSelectedBackground();
+
+    /**
+     * The styles used in this widget.
+     */
+    @Source("CellTable.css")
+    Style cellTableStyle();
+  }
+
+  private static Resources DEFAULT_RESOURCES;
+
+  private static Resources getDefaultResources() {
+    if (DEFAULT_RESOURCES == null) {
+      DEFAULT_RESOURCES = GWT.create(Resources.class);
+    }
+    return DEFAULT_RESOURCES;
+  }
+
+  private List<Column<T, ?>> columns = new ArrayList<Column<T, ?>>();
+  private List<Header<?>> footers = new ArrayList<Header<?>>();
+  private List<Header<?>> headers = new ArrayList<Header<?>>();
+
+  /**
+   * Set to true when the footer is stale.
+   */
+  private boolean headersStale;
+
+  private TableRowElement hoveringRow;
+  private final CellListImpl<T> impl;
+
+  /**
+   * If null, each T will be used as its own key.
+   */
+  private ProvidesKey<T> providesKey;
+
+  /**
+   * If true, enable selection via the mouse.
+   */
+  private boolean isSelectionEnabled;
+
+  private final Style style;
+  private final TableElement table;
+  private final TableSectionElement tbody;
+  private final TableSectionElement tfoot;
+  private TableSectionElement thead;
+
+  /**
+   * Constructs a table with a default page size of 15.
+   */
+  public CellTable() {
+    this(DEFAULT_PAGESIZE);
+  }
+
+  /**
+   * Constructs a table with the given page size.
+   * 
+   * @param pageSize the page size
+   */
+  public CellTable(final int pageSize) {
+    this(pageSize, getDefaultResources());
+  }
+
+  /**
+   * Constructs a table with the given page size with the specified
+   * {@link Resources}.
+   * 
+   * @param pageSize the page size
+   * @param resources the resources to use for this widget
+   */
+  public CellTable(final int pageSize, Resources resources) {
+    this.style = resources.cellTableStyle();
+    this.style.ensureInjected();
+
+    setElement(table = Document.get().createTableElement());
+    table.setCellSpacing(0);
+    thead = table.createTHead();
+    table.appendChild(tbody = Document.get().createTBodyElement());
+    tfoot = table.createTFoot();
+    setStyleName(this.style.cellTable());
+
+    // Create the implementation.
+    this.impl = new CellListImpl<T>(this, pageSize, tbody) {
+
+      private final TableElement tmpElem = Document.get().createTableElement();
+
+      @Override
+      public void setData(List<T> values, int start) {
+        createHeadersAndFooters();
+        super.setData(values, start);
+      }
+
+      @Override
+      protected Element convertToElements(String html) {
+        tmpElem.setInnerHTML("<tbody>" + html + "</tbody>");
+        return tmpElem.getTBodies().getItem(0);
+      }
+
+      @Override
+      protected boolean dependsOnSelection() {
+        for (Column<T, ?> column : columns) {
+          if (column.dependsOnSelection()) {
+            return true;
+          }
+        }
+        return false;
+      }
+
+      @Override
+      protected void emitHtml(StringBuilder sb, List<T> values, int start,
+          SelectionModel<? super T> selectionModel) {
+        int length = values.size();
+        int end = start + length;
+        for (int i = start; i < end; i++) {
+          T value = values.get(i - start);
+          boolean isSelected = (selectionModel == null || value == null)
+              ? false : selectionModel.isSelected(value);
+          sb.append("<tr __idx='").append(i).append("'");
+          sb.append(" class='");
+          sb.append(i % 2 == 0 ? style.evenRow() : style.oddRow());
+          if (isSelected) {
+            sb.append(" ").append(style.selectedRow());
+          }
+          sb.append("'>");
+          boolean first = true;
+          for (Column<T, ?> column : columns) {
+            // TODO(jlabanca): How do we sink ONFOCUS and ONBLUR?
+            sb.append("<td class='").append(style.cell());
+            if (first) {
+              first = false;
+              sb.append(" ").append(style.firstColumn());
+            }
+            sb.append("'>");
+            if (value != null) {
+              column.render(value, sb);
+            }
+            sb.append("</td>");
+          }
+          sb.append("</tr>");
+        }
+      }
+
+      @Override
+      protected void setSelected(Element elem, boolean selected) {
+        setStyleName(elem, style.selectedRow(), selected);
+      }
+
+      @Override
+      protected void updateSelection() {
+        // Refresh headers.
+        for (Header<?> header : headers) {
+          if (header != null && header.dependsOnSelection()) {
+            createHeaders(false);
+            break;
+          }
+        }
+
+        // Refresh footers.
+        for (Header<?> footer : footers) {
+          if (footer != null && footer.dependsOnSelection()) {
+            createHeaders(true);
+            break;
+          }
+        }
+
+        // Update data.
+        super.updateSelection();
+      }
+    };
+
+    setPageSize(pageSize);
+
+    // TODO: Total hack. It would almost definitely be preferable to sink only
+    // those events actually needed by cells.
+    sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.KEYEVENTS
+        | Event.ONCHANGE | Event.FOCUSEVENTS);
+  }
+
+  /**
+   * Adds a column to the table.
+   */
+  public void addColumn(Column<T, ?> col) {
+    addColumn(col, (Header<?>) null, (Header<?>) null);
+  }
+
+  /**
+   * Adds a column to the table with an associated header.
+   */
+  public void addColumn(Column<T, ?> col, Header<?> header) {
+    addColumn(col, header, null);
+  }
+
+  /**
+   * Adds a column to the table with an associated header and footer.
+   */
+  public void addColumn(Column<T, ?> col, Header<?> header, Header<?> footer) {
+    headers.add(header);
+    footers.add(footer);
+    columns.add(col);
+    headersStale = true;
+    refresh();
+  }
+
+  /**
+   * Adds a column to the table with an associated String header.
+   */
+  public void addColumn(Column<T, ?> col, String headerString) {
+    addColumn(col, new TextHeader(headerString), null);
+  }
+
+  /**
+   * Adds a column to the table with an associated String header and footer.
+   */
+  public void addColumn(Column<T, ?> col, String headerString,
+      String footerString) {
+    addColumn(col, new TextHeader(headerString), new TextHeader(footerString));
+  }
+
+  // TODO: remove(Column)
+
+  public int getBodyHeight() {
+    int height = getClientHeight(tbody);
+    return height;
+  }
+
+  public int getDataSize() {
+    return impl.getDataSize();
+  }
+
+  public T getDisplayedItem(int indexOnPage) {
+    if (indexOnPage < 0 || indexOnPage >= getNumDisplayedItems()) {
+      throw new IndexOutOfBoundsException("indexOnPage = " + indexOnPage);
+    }
+    return impl.getData().get(indexOnPage);
+  }
+
+  public List<T> getDisplayedItems() {
+    return new ArrayList<T>(impl.getData());
+  }
+
+  public int getHeaderHeight() {
+    int height = getClientHeight(thead);
+    return height;
+  }
+
+  public int getNumDisplayedItems() {
+    return impl.getDisplayedItemCount();
+  }
+
+  public int getPageSize() {
+    return impl.getPageSize();
+  }
+
+  public int getPageStart() {
+    return impl.getPageStart();
+  }
+
+  public ProvidesKey<T> getProvidesKey() {
+    return providesKey;
+  }
+
+  public Range getRange() {
+    return impl.getRange();
+  }
+
+  public int getSize() {
+    return impl.getDataSize();
+  }
+
+  /**
+   * Check whether or not mouse selection is enabled.
+   * 
+   * @return true if enabled, false if disabled
+   */
+  public boolean isSelectionEnabled() {
+    return isSelectionEnabled;
+  }
+
+  @Override
+  public void onBrowserEvent(Event event) {
+    super.onBrowserEvent(event);
+
+    // Find the cell where the event occurred.
+    EventTarget eventTarget = event.getEventTarget();
+    TableCellElement cell = null;
+    if (eventTarget != null && Element.is(eventTarget)) {
+      cell = findNearestParentCell(Element.as(eventTarget));
+    }
+    if (cell == null) {
+      return;
+    }
+
+    // Forward the event to the associated header, footer, or column.
+    TableRowElement tr = TableRowElement.as(cell.getParentElement());
+    TableSectionElement section = TableSectionElement.as(tr.getParentElement());
+    int col = cell.getCellIndex();
+    if (section == thead) {
+      Header<?> header = headers.get(col);
+      if (header != null) {
+        header.onBrowserEvent(cell, event);
+      }
+    } else if (section == tfoot) {
+      Header<?> footer = footers.get(col);
+      if (footer != null) {
+        footer.onBrowserEvent(cell, event);
+      }
+    } else if (section == tbody) {
+      int row = tr.getSectionRowIndex();
+
+      if (event.getType().equals("mouseover")) {
+        if (hoveringRow != null) {
+          hoveringRow.removeClassName(style.hoveredRow());
+        }
+        hoveringRow = tr;
+        tr.addClassName(style.hoveredRow());
+      } else if (event.getType().equals("mouseout")) {
+        hoveringRow = null;
+        tr.removeClassName(style.hoveredRow());
+      }
+
+      T value = impl.getData().get(row);
+      Column<T, ?> column = columns.get(col);
+      column.onBrowserEvent(cell, impl.getPageStart() + row, value, event,
+          providesKey);
+
+      // Update selection.
+      if (isSelectionEnabled && event.getTypeInt() == Event.ONMOUSEDOWN) {
+        SelectionModel<? super T> selectionModel = impl.getSelectionModel();
+        if (selectionModel != null) {
+          selectionModel.setSelected(value, true);
+        }
+      }
+    }
+  }
+
+  /**
+   * Redraw the table using the existing data.
+   */
+  public void redraw() {
+    impl.redraw();
+  }
+
+  /**
+   * Redraw the table, requesting data from the delegate.
+   */
+  public void refresh() {
+    impl.refresh();
+  }
+
+  public void refreshFooters() {
+    createHeaders(true);
+  }
+
+  public void refreshHeaders() {
+    createHeaders(false);
+  }
+
+  public void setData(int start, int length, List<T> values) {
+    impl.setData(values, start);
+  }
+
+  public void setDataSize(int size, boolean isExact) {
+    impl.setDataSize(size);
+  }
+
+  public void setDelegate(Delegate<T> delegate) {
+    impl.setDelegate(delegate);
+  }
+
+  public void setPager(PagingListView.Pager<T> pager) {
+    impl.setPager(pager);
+  }
+
+  /**
+   * Set the number of rows per page and refresh the table.
+   * 
+   * @param pageSize the page size
+   * 
+   * @throw {@link IllegalArgumentException} if pageSize is negative or 0
+   */
+  public void setPageSize(int pageSize) {
+    impl.setPageSize(pageSize);
+  }
+
+  /**
+   * Set the starting index of the current visible page. The actual page start
+   * will be clamped in the range [0, getSize() - 1].
+   * 
+   * @param pageStart the index of the row that should appear at the start of
+   *          the page
+   */
+  public void setPageStart(int pageStart) {
+    impl.setPageStart(pageStart);
+  }
+
+  /**
+   * Sets the {@link ProvidesKey} instance that will be used to generate keys
+   * for each record object as needed.
+   * 
+   * @param providesKey an instance of {@link ProvidesKey} used to generate keys
+   *          for record objects.
+   */
+  // TODO - when is this valid? Do we rehash column view data if it changes?
+  public void setProvidesKey(ProvidesKey<T> providesKey) {
+    this.providesKey = providesKey;
+  }
+
+  /**
+   * Enable mouse and keyboard selection.
+   * 
+   * @param isSelectionEnabled true to enable, false to disable
+   */
+  public void setSelectionEnabled(boolean isSelectionEnabled) {
+    this.isSelectionEnabled = isSelectionEnabled;
+  }
+
+  public void setSelectionModel(SelectionModel<? super T> selectionModel) {
+    impl.setSelectionModel(selectionModel, true);
+  }
+
+  /**
+   * Render the header of footer.
+   * 
+   * @param isFooter true if this is the footer table, false if the header table
+   */
+  private void createHeaders(boolean isFooter) {
+    List<Header<?>> theHeaders = isFooter ? footers : headers;
+    TableSectionElement section = isFooter ? tfoot : thead;
+    String className = isFooter ? style.footer() : style.header();
+
+    boolean hasHeader = false;
+    StringBuilder sb = new StringBuilder();
+    sb.append("<tr>");
+    boolean first = true;
+    for (Header<?> header : theHeaders) {
+      sb.append("<th class='").append(className);
+      if (first) {
+        first = false;
+        sb.append(" ");
+        sb.append(isFooter ? style.firstColumnFooter()
+            : style.firstColumnHeader());
+      }
+      sb.append("'>");
+      if (header != null) {
+        hasHeader = true;
+        header.render(sb);
+      }
+      sb.append("</th>");
+    }
+    sb.append("</tr>");
+
+    section.setInnerHTML(sb.toString());
+
+    // If the section isn't used, hide it.
+    setVisible(section, hasHeader);
+  }
+
+  private void createHeadersAndFooters() {
+    if (headersStale) {
+      headersStale = false;
+      createHeaders(false);
+      createHeaders(true);
+    }
+  }
+
+  private TableCellElement findNearestParentCell(Element elem) {
+    while ((elem != null) && (elem != table)) {
+      // TODO: We need is() implementations in all Element subclasses.
+      // This would allow us to use TableCellElement.is() -- much cleaner.
+      String tagName = elem.getTagName();
+      if ("td".equalsIgnoreCase(tagName) || "th".equalsIgnoreCase(tagName)) {
+        return elem.cast();
+      }
+      elem = elem.getParentElement();
+    }
+    return null;
+  }
+
+  private native int getClientHeight(Element element) /*-{
+    return element.clientHeight;
+  }-*/;
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
index 7528d48..089c005 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,38 +15,40 @@
  */
 package com.google.gwt.bikeshed.list.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.cell.client.HasCell;
+import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.view.client.HasViewData;
+import com.google.gwt.view.client.ProvidesKey;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * A representation of a column in a table.  The column may maintain view data
- * for each cell on demand.  New view data, if needed, is created by the
- * cell's onBrowserEvent method, stored in the Column, and passed to future
- * calls to Cell's {@link Cell#onBrowserEvent} and @link{Cell#render} methods.
- *
+ * A representation of a column in a table. The column may maintain view data
+ * for each cell on demand. New view data, if needed, is created by the cell's
+ * onBrowserEvent method, stored in the Column, and passed to future calls to
+ * Cell's {@link Cell#onBrowserEvent} and @link{Cell#render} methods.
+ * 
  * @param <T> the row type
  * @param <C> the column type
- * @param <V> the view data type
  */
 // TODO - when can we get rid of a view data object?
-// TODO - should viewData implement some interface? (e.g., with commit/rollback/dispose)
+// TODO - should viewData implement some interface? (e.g., with
+// commit/rollback/dispose)
 // TODO - have a ViewDataColumn superclass / SimpleColumn subclass
-public abstract class Column<T, C, V> {
+public abstract class Column<T, C> implements HasViewData, HasCell<T, C> {
 
-  protected final Cell<C, V> cell;
+  protected final Cell<C> cell;
 
-  protected FieldUpdater<T, C, V> fieldUpdater;
+  protected FieldUpdater<T, C> fieldUpdater;
 
-  protected Map<Object, V> viewDataMap = new HashMap<Object, V>();
+  protected Map<Object, Object> viewDataMap = new HashMap<Object, Object>();
 
-  public Column(Cell<C, V> cell) {
+  public Column(Cell<C> cell) {
     this.cell = cell;
   }
 
@@ -55,24 +57,28 @@
   }
 
   /**
-   * Returns true if the contents of the column may depend on the current
-   * state of the selection model associated with the table that is displaying
-   * this column.  The default implementation returns false.
+   * Returns true if the contents of the column may depend on the current state
+   * of the selection model associated with the table that is displaying this
+   * column. The default implementation returns false.
    */
   public boolean dependsOnSelection() {
     return false;
   }
 
-  public Cell<C, V> getCell() {
+  public Cell<C> getCell() {
     return cell;
   }
 
-  public FieldUpdater<T, C, V> getFieldUpdater() {
+  public FieldUpdater<T, C> getFieldUpdater() {
     return fieldUpdater;
   }
 
   public abstract C getValue(T object);
 
+  public Object getViewData(Object key) {
+    return viewDataMap.get(key);
+  }
+
   /**
    * @param providesKey an instance of ProvidesKey<T>, or null if the record
    *          object should act as its own key.
@@ -80,14 +86,13 @@
   public void onBrowserEvent(Element elem, final int index, final T object,
       NativeEvent event, ProvidesKey<T> providesKey) {
     Object key = providesKey == null ? object : providesKey.getKey(object);
-    V viewData = viewDataMap.get(key);
-    V newViewData = cell.onBrowserEvent(elem,
-        getValue(object), viewData, event, fieldUpdater == null ? null
-            : new ValueUpdater<C, V>() {
-              public void update(C value, V viewData) {
-                fieldUpdater.update(index, object, value, viewData);
-              }
-            });
+    Object viewData = viewDataMap.get(key);
+    Object newViewData = cell.onBrowserEvent(elem, getValue(object), viewData,
+        event, fieldUpdater == null ? null : new ValueUpdater<C>() {
+          public void update(C value) {
+            fieldUpdater.update(index, object, value);
+          }
+        });
     if (newViewData != viewData) {
       viewDataMap.put(key, newViewData);
     }
@@ -97,7 +102,11 @@
     cell.render(getValue(object), viewDataMap.get(object), sb);
   }
 
-  public void setFieldUpdater(FieldUpdater<T, C, V> fieldUpdater) {
+  public void setFieldUpdater(FieldUpdater<T, C> fieldUpdater) {
     this.fieldUpdater = fieldUpdater;
   }
+
+  public void setViewData(Object key, Object viewData) {
+    viewDataMap.put(key, viewData);
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/HasCell.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/HasCell.java
deleted file mode 100644
index 69315ca..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/HasCell.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.list.client;
-
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-
-/**
- * An interface for extracting a value from an underlying data type, provide a
- * cell to render that value, and provide a FieldUpdater to perform notification
- * of updates to the cell.
- * 
- * @param <T> the underlying data type
- * @param <C> the cell data type
- * @param <V> the view data type
- */
-public interface HasCell<T, C, V> {
-
-  Cell<C, V> getCell();
-
-  FieldUpdater<T, C, V> getFieldUpdater();
-
-  C getValue(T object);
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
index 72e0ea5..7183768 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
@@ -15,8 +15,8 @@
  */
 package com.google.gwt.bikeshed.list.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 
@@ -27,11 +27,11 @@
  */
 public abstract class Header<H> {
 
-  private final Cell<H, Void> cell;
+  private final Cell<H> cell;
 
-  private ValueUpdater<H, Void> updater;
+  private ValueUpdater<H> updater;
 
-  public Header(Cell<H, Void> cell) {
+  public Header(Cell<H> cell) {
     this.cell = cell;
   }
 
@@ -49,7 +49,7 @@
     cell.render(getValue(), null, sb);
   }
 
-  public void setUpdater(ValueUpdater<H, Void> updater) {
+  public void setUpdater(ValueUpdater<H> updater) {
     this.updater = updater;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/IdentityColumn.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/IdentityColumn.java
index 3a4204c..c70f600 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/IdentityColumn.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/IdentityColumn.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.bikeshed.list.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.cell.client.Cell;
 
 /**
  * A passthrough column, useful for giving cells access to the entire row
@@ -23,12 +23,12 @@
  *
  * @param <T> the row type
  */
-public class IdentityColumn<T> extends SimpleColumn<T, T> {
+public class IdentityColumn<T> extends Column<T, T> {
 
   /**
    * @param cell
    */
-  public IdentityColumn(Cell<T, Void> cell) {
+  public IdentityColumn(Cell<T> cell) {
     super(cell);
   }
 
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/PageSizePager.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/PageSizePager.java
new file mode 100644
index 0000000..b4ae081
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/PageSizePager.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.client;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.PagingListView.Pager;
+
+/**
+ * A simple {@link Pager} that controls the page size.
+ * 
+ * @param <T> the data type of list items
+ */
+public class PageSizePager<T> extends Composite implements Pager<T> {
+
+  /**
+   * The increment by which to grow or shrink the page size.
+   */
+  private final int increment;
+
+  /**
+   * The main layout widget.
+   */
+  private final FlexTable layout = new FlexTable();
+
+  // TODO(jlabanca): I18N button text.
+  private final Anchor showMoreButton = new Anchor("Show More");
+  private final Anchor showLessButton = new Anchor("Show Less");
+
+  public PageSizePager(final PagingListView<T> listView, final int increment) {
+    this.increment = increment;
+    initWidget(layout);
+    layout.setCellPadding(0);
+    layout.setCellSpacing(0);
+
+    // Show more button.
+    showMoreButton.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        int pageSize = Math.min(listView.getPageSize() + increment,
+            listView.getDataSize());
+        listView.setPageSize(pageSize);
+      }
+    });
+    showLessButton.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        int pageSize = Math.max(listView.getPageSize() - increment, increment);
+        listView.setPageSize(pageSize);
+      }
+    });
+
+    // Add the buttons to the pager.
+    layout.setWidget(0, 0, showLessButton);
+    layout.setText(0, 1, " | ");
+    layout.setWidget(0, 2, showMoreButton);
+
+    // Update the button state.
+    listView.setPager(this);
+    onRangeOrSizeChanged(listView);
+  }
+
+  public void onRangeOrSizeChanged(PagingListView<T> listView) {
+    // Assumes a page start index of 0.
+    boolean hasLess = listView.getPageSize() > increment;
+    boolean hasMore = listView.getPageSize() < listView.getDataSize();
+    showLessButton.setVisible(hasLess);
+    showMoreButton.setVisible(hasMore);
+    layout.setText(0, 1, (hasLess && hasMore) ? " | " : "");
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
deleted file mode 100644
index 25463d6..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
+++ /dev/null
@@ -1,525 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.list.client;
-
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter.DefaultRange;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeHandler;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.EventTarget;
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.dom.client.NodeList;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.TableCellElement;
-import com.google.gwt.dom.client.TableElement;
-import com.google.gwt.dom.client.TableRowElement;
-import com.google.gwt.dom.client.TableSectionElement;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A list view that supports paging and columns.
- * 
- * @param <T> the data type of each row
- */
-public class PagingTableListView<T> extends Widget implements PagingListView<T> {
-
-  private class TableSelectionHandler implements SelectionChangeHandler {
-    public void onSelectionChange(SelectionChangeEvent event) {
-      refreshSelection();
-    }
-  }
-
-  private static final int DEFAULT_SIZE = 10;
-
-  private List<Column<T, ?, ?>> columns = new ArrayList<Column<T, ?, ?>>();
-  private ArrayList<Boolean> dataSelected = new ArrayList<Boolean>();
-  private ArrayList<T> dataValues = new ArrayList<T>();
-  private Delegate<T> delegate;
-  private List<Header<?>> footers = new ArrayList<Header<?>>();
-  private List<Header<?>> headers = new ArrayList<Header<?>>();
-  private TableRowElement hoveringRow;
-  private Pager<T> pager;
-  private int pageSize = -1;
-
-  private int pageStart = 0;
-
-  /**
-   * If null, each T will be used as its own key.
-   */
-  private ProvidesKey<T> providesKey;
-  private HandlerRegistration selectionHandler;
-
-  private SelectionModel<? super T> selectionModel;
-  private int size = 0;
-  private TableElement table;
-  private TableSectionElement tbody;
-  private TableSectionElement tfoot;
-
-  private TableSectionElement thead;
-
-  /**
-   * Constructs a table with a default page size of 10.
-   */
-  public PagingTableListView() {
-    this(DEFAULT_SIZE);
-  }
-
-  /**
-   * Constructs a table with the given page size.
-   * 
-   * @param pageSize the page size
-   */
-  public PagingTableListView(final int pageSize) {
-    setElement(table = Document.get().createTableElement());
-    table.setCellSpacing(0);
-    thead = table.createTHead();
-    table.appendChild(tbody = Document.get().createTBodyElement());
-    tfoot = table.createTFoot();
-
-    setPageSize(pageSize);
-
-    // TODO: Total hack. It would almost definitely be preferable to sink only
-    // those events actually needed by cells.
-    sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.KEYEVENTS
-        | Event.ONCHANGE | Event.FOCUSEVENTS);
-  }
-
-  /**
-   * Adds a column to the table.
-   */
-  public void addColumn(Column<T, ?, ?> col) {
-    addColumn(col, null, null);
-  }
-
-  /**
-   * Adds a column to the table with an associated header.
-   */
-  public void addColumn(Column<T, ?, ?> col, Header<?> header) {
-    addColumn(col, header, null);
-  }
-
-  /**
-   * Adds a column to the table with an associated header and footer.
-   */
-  public void addColumn(Column<T, ?, ?> col, Header<?> header, Header<?> footer) {
-    headers.add(header);
-    footers.add(footer);
-    createHeadersAndFooters(); // TODO: defer header recreation
-    columns.add(col);
-    createRows();
-
-    refresh();
-  }
-
-  /**
-   * Adds a column to the table with an associated String header.
-   */
-  public void addColumn(Column<T, ?, ?> col, String headerString) {
-    addColumn(col, new TextHeader(headerString), null);
-  }
-
-  // TODO: remove(Column)
-
-  public int getBodyHeight() {
-    int height = getClientHeight(tbody);
-    return height;
-  }
-
-  public int getDataSize() {
-    return size;
-  }
-
-  public T getDisplayedItem(int indexOnPage) {
-    if (indexOnPage < 0 || indexOnPage >= getNumDisplayedItems()) {
-      throw new IndexOutOfBoundsException("indexOnPage = " + indexOnPage);
-    }
-    return dataValues.get(indexOnPage);
-  }
-
-  public List<T> getDisplayedItems() {
-    return new ArrayList<T>(dataValues);
-  }
-
-  public int getHeaderHeight() {
-    int height = getClientHeight(thead);
-    return height;
-  }
-
-  public int getNumDisplayedItems() {
-    return Math.min(getPageSize(), size - pageStart);
-  }
-
-  public int getPageSize() {
-    return pageSize;
-  }
-
-  public int getPageStart() {
-    return pageStart;
-  }
-
-  public ProvidesKey<T> getProvidesKey() {
-    return providesKey;
-  }
-
-  public Range getRange() {
-    return new DefaultRange(pageStart, pageSize);
-  }
-
-  public int getSize() {
-    return size;
-  }
-
-  @Override
-  public void onBrowserEvent(Event event) {
-    EventTarget target = event.getEventTarget();
-    Node node = Node.as(target);
-    TableCellElement cell = findNearestParentCell(node);
-    if (cell == null) {
-      return;
-    }
-
-    TableRowElement tr = TableRowElement.as(cell.getParentElement());
-    TableSectionElement section = TableSectionElement.as(tr.getParentElement());
-    int col = cell.getCellIndex();
-    if (section == thead) {
-      Header<?> header = headers.get(col);
-      if (header != null) {
-        header.onBrowserEvent(cell, event);
-      }
-    } else if (section == tfoot) {
-      Header<?> footer = footers.get(col);
-      if (footer != null) {
-        footer.onBrowserEvent(cell, event);
-      }
-    } else if (section == tbody) {
-      int row = tr.getSectionRowIndex();
-
-      if (event.getType().equals("mouseover")) {
-        if (hoveringRow != null) {
-          hoveringRow.removeClassName("hover");
-        }
-        hoveringRow = tr;
-        tr.addClassName("hover");
-      } else if (event.getType().equals("mouseout")) {
-        hoveringRow = null;
-        tr.removeClassName("hover");
-      }
-
-      T value = dataValues.get(row);
-      Column<T, ?, ?> column = columns.get(col);
-
-      column.onBrowserEvent(cell, pageStart + row, value, event, providesKey);
-    }
-  }
-
-  /**
-   * Redraw the table, requesting data from the delegate.
-   */
-  public void refresh() {
-    if (delegate != null) {
-      delegate.onRangeChanged(this);
-    }
-    updateRowVisibility();
-  }
-
-  /**
-   * Refresh those portions of the table that depend on the state of the
-   * {@link SelectionModel}.
-   */
-  public void refreshSelection() {
-    // Refresh headers
-    Element th = thead.getFirstChild().getFirstChild().cast();
-    for (Header<?> header : headers) {
-      if (header.dependsOnSelection()) {
-        StringBuilder sb = new StringBuilder();
-        header.render(sb);
-        th.setInnerHTML(sb.toString());
-      }
-      th = th.getNextSibling().cast();
-    }
-
-    int numCols = columns.size();
-
-    // Refresh body
-    NodeList<TableRowElement> rows = tbody.getRows();
-    for (int indexOnPage = 0; indexOnPage < pageSize; indexOnPage++) {
-      TableRowElement row = rows.getItem(indexOnPage);
-
-      T q = dataValues.get(indexOnPage);
-      boolean qSelected = dataSelected.get(indexOnPage);
-      boolean selected = q != null && selectionModel.isSelected(q);
-
-      // Process the row only if the selection has changed
-      if (qSelected != selected) {
-        dataSelected.set(indexOnPage, selected);
-
-        if (selected) {
-          // item became selected
-          row.setClassName("pagingTableListView selected");
-        } else {
-          // item became unselected
-          row.setClassName("pagingTableListView "
-              + ((indexOnPage & 0x1) == 0 ? "evenRow" : "oddRow"));
-        }
-
-        for (int c = 0; c < numCols; ++c) {
-          Column<T, ?, ?> column = columns.get(c);
-          if (column.dependsOnSelection()) {
-            TableCellElement cell = row.getCells().getItem(c);
-            StringBuilder sb = new StringBuilder();
-            columns.get(c).render(q, sb);
-            cell.setInnerHTML(sb.toString());
-          }
-        }
-      }
-    }
-  }
-
-  public void setData(int start, int length, List<T> values) {
-    int numCols = columns.size();
-
-    NodeList<TableRowElement> rows = tbody.getRows();
-    for (int r = start; r < start + length; ++r) {
-      int indexOnPage = r - pageStart;
-      TableRowElement row = rows.getItem(indexOnPage);
-      T q = values.get(r - start);
-      if (selectionModel != null && selectionModel.isSelected(q)) {
-        row.setClassName("pagingTableListView selected");
-      } else {
-        row.setClassName("pagingTableListView "
-            + ((indexOnPage & 0x1) == 0 ? "evenRow" : "oddRow"));
-      }
-
-      dataValues.set(indexOnPage, q);
-      for (int c = 0; c < numCols; ++c) {
-        TableCellElement cell = row.getCells().getItem(c);
-        StringBuilder sb = new StringBuilder();
-        columns.get(c).render(q, sb);
-        // TODO(jlabanca): Render as one HTML string instead of embedding HTML
-        // in each cell.
-        cell.setInnerHTML(sb.toString());
-
-        // TODO: Really total hack! There's gotta be a better way...
-        Element child = cell.getFirstChildElement();
-        if (child != null) {
-          Event.sinkEvents(child, Event.ONFOCUS | Event.ONBLUR);
-        }
-      }
-    }
-  }
-
-  public void setDataSize(int size, boolean isExact) {
-    this.size = size;
-    if (size == 0) {
-      pageStart = 0;
-    } else if (pageStart > size - 1) {
-      pageStart = size - 1;
-    }
-    refresh();
-    updatePager();
-  }
-
-  public void setDelegate(Delegate<T> delegate) {
-    this.delegate = delegate;
-  }
-
-  public void setPager(PagingListView.Pager<T> pager) {
-    this.pager = pager;
-  }
-
-  /**
-   * Set the number of rows per page and refresh the table.
-   * 
-   * @param pageSize the page size
-   * 
-   * @throw {@link IllegalArgumentException} if pageSize is negative or 0
-   */
-  public void setPageSize(int pageSize) {
-    if (pageSize <= 0) {
-      throw new IllegalArgumentException("pageSize = " + pageSize);
-    }
-    if (this.pageSize == pageSize) {
-      return;
-    }
-    this.pageSize = pageSize;
-
-    // If on last page and page size increases, move the page start upwards
-    if (pageStart + pageSize > size) {
-      pageStart = Math.max(0, size - pageSize);
-    }
-    updatePager();
-
-    // TODO - avoid requesting data if the page size has decreased
-    createRows();
-    refresh();
-  }
-
-  /**
-   * Set the starting index of the current visible page. The actual page start
-   * will be clamped in the range [0, getSize() - 1].
-   * 
-   * @param pageStart the index of the row that should appear at the start of
-   *          the page
-   */
-  public void setPageStart(int pageStart) {
-    this.pageStart = Math.max(Math.min(pageStart, size - 1), 0);
-    updatePager();
-    refresh();
-  }
-
-  /**
-   * Sets the {@link ProvidesKey} instance that will be used to generate keys
-   * for each record object as needed.
-   * 
-   * @param providesKey an instance of {@link ProvidesKey} used to generate keys
-   *          for record objects.
-   */
-  // TODO - when is this valid? Do we rehash column view data if it changes?
-  public void setProvidesKey(ProvidesKey<T> providesKey) {
-    this.providesKey = providesKey;
-  }
-
-  public void setSelectionModel(SelectionModel<? super T> selectionModel) {
-    if (selectionHandler != null) {
-      selectionHandler.removeHandler();
-      selectionHandler = null;
-    }
-    this.selectionModel = selectionModel;
-    if (selectionModel != null && isAttached()) {
-      selectionHandler = selectionModel.addSelectionChangeHandler(new TableSelectionHandler());
-    }
-
-    refreshSelection();
-  }
-
-  @Override
-  protected void onLoad() {
-    // Attach a selection handler.
-    if (selectionModel != null) {
-      selectionHandler = selectionModel.addSelectionChangeHandler(new TableSelectionHandler());
-    }
-  }
-
-  @Override
-  protected void onUnload() {
-    // Detach the selection handler.
-    if (selectionHandler != null) {
-      selectionHandler.removeHandler();
-      selectionHandler = null;
-    }
-  }
-
-  private void createHeaders(List<Header<?>> headers,
-      TableSectionElement section) {
-    StringBuilder sb = new StringBuilder();
-    sb.append("<tr>");
-    for (Header<?> header : headers) {
-      sb.append("<th>");
-      if (header != null) {
-        header.render(sb);
-      }
-      sb.append("</th>");
-    }
-    sb.append("</tr>");
-
-    section.setInnerHTML(sb.toString());
-  }
-
-  private void createHeadersAndFooters() {
-    createHeaders(headers, thead);
-    createHeaders(footers, tfoot);
-  }
-
-  private void createRows() {
-    int numCols = columns.size();
-
-    // TODO - only delete as needed
-    int numRows = tbody.getRows().getLength();
-    while (numRows-- > 0) {
-      tbody.deleteRow(0);
-    }
-
-    for (int r = 0; r < pageSize; ++r) {
-      TableRowElement row = tbody.insertRow(0);
-      row.setClassName("pagingTableListView "
-          + ((r & 0x1) == 0 ? "evenRow" : "oddRow"));
-
-      // TODO: use cloneNode() to make this even faster.
-      for (int c = 0; c < numCols; ++c) {
-        row.insertCell(c);
-      }
-    }
-
-    // Make room for the data cache
-    dataValues.ensureCapacity(pageSize);
-    dataSelected.ensureCapacity(pageSize);
-    while (dataValues.size() < pageSize) {
-      dataValues.add(null);
-      dataSelected.add(Boolean.FALSE);
-    }
-  }
-
-  private TableCellElement findNearestParentCell(Node node) {
-    while ((node != null) && (node != table)) {
-      if (Element.is(node)) {
-        Element elem = Element.as(node);
-
-        // TODO: We need is() implementations in all Element subclasses.
-        // This would allow us to use TableCellElement.is() -- much cleaner.
-        String tagName = elem.getTagName();
-        if ("td".equalsIgnoreCase(tagName) || "th".equalsIgnoreCase(tagName)) {
-          return elem.cast();
-        }
-      }
-      node = node.getParentNode();
-    }
-    return null;
-  }
-
-  private native int getClientHeight(Element element) /*-{
-    return element.clientHeight;
-  }-*/;
-
-  private void updatePager() {
-    // Inform the pager about a change in page start, page size, or data size
-    if (pager != null) {
-      pager.onRangeOrSizeChanged(this);
-    }
-  }
-  
-  private void updateRowVisibility() {
-    int visible = Math.min(pageSize, size - pageStart);
-
-    for (int r = 0; r < pageSize; ++r) {
-      Style rowStyle = tbody.getRows().getItem(r).getStyle();
-      if (r < visible) {
-        rowStyle.clearDisplay();
-      } else {
-        rowStyle.setDisplay(Display.NONE);
-      }
-    }
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
deleted file mode 100644
index d83fd87..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.list.client;
-
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.bikeshed.list.client.impl.SimpleCellListImpl;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.List;
-
-/**
- * A single column list of cells.
- * 
- * @param <T> the data type of list items
- */
-public class SimpleCellList<T> extends Widget implements ListView<T> {
-
-  /**
-   * Style name applied to even rows.
-   * TODO(jlabanca): These style names only apply to SideBySideTreeView.
-   */
-  private static final String STYLENNAME_EVEN = "gwt-sstree-evenRow";
-
-  /**
-   * Style name applied to odd rows.
-   * TODO(jlabanca): These style names only apply to SideBySideTreeView.
-   */
-  private static final String STYLENNAME_ODD = "gwt-sstree-oddRow";
-
-  /**
-   * Style name applied to selected rows.
-   * TODO(jlabanca): These style names only apply to SideBySideTreeView.
-   */
-  private static final String STYLENNAME_SELECTED = "gwt-sstree-selectedItem";
-
-  private final Cell<T, Void> cell;
-  private final SimpleCellListImpl<T> impl;
-  private ValueUpdater<T, Void> valueUpdater;
-
-  public SimpleCellList(Cell<T, Void> cell, int maxSize, int increment) {
-    this.cell = cell;
-
-    // Create the DOM hierarchy.
-    Element childContainer = Document.get().createDivElement();
-
-    Element showMoreElem = Document.get().createPushButtonElement();
-    showMoreElem.setInnerText("Show more");
-
-    Element showFewerElem = Document.get().createPushButtonElement();
-    showFewerElem.setInnerText("Show fewer");
-
-    Element emptyMessageElem = Document.get().createDivElement();
-    emptyMessageElem.setInnerHTML("<i>no data</i>");
-
-    // TODO: find some way for cells to communicate what they're interested in.
-    DivElement outerDiv = Document.get().createDivElement();
-    outerDiv.appendChild(childContainer);
-    outerDiv.appendChild(emptyMessageElem);
-    outerDiv.appendChild(showFewerElem);
-    outerDiv.appendChild(showMoreElem);
-    setElement(outerDiv);
-    sinkEvents(Event.ONCLICK | Event.ONCHANGE | Event.MOUSEEVENTS);
-
-    // Create the implementation.
-    impl = new SimpleCellListImpl<T>(this, cell, maxSize, increment,
-        childContainer, emptyMessageElem, showMoreElem, showFewerElem) {
-
-      @Override
-      protected void emitHtml(StringBuilder sb, List<T> values, int start,
-          Cell<T, Void> cell, SelectionModel<? super T> selectionModel) {
-        int length = values.size();
-        int end = start + length;
-        for (int i = start; i < end; i++) {
-          T value = values.get(i - start);
-          boolean isSelected = selectionModel == null ? false
-              : selectionModel.isSelected(value);
-          sb.append("<div __idx='").append(i).append("'");
-          sb.append(" class='");
-          sb.append(i % 2 == 0 ? STYLENNAME_EVEN : STYLENNAME_ODD);
-          if (isSelected) {
-            sb.append(" ").append(STYLENNAME_SELECTED);
-          }
-          sb.append("'>");
-          cell.render(value, null, sb);
-          sb.append("</div>");
-        }
-      }
-
-      @Override
-      protected void setSelected(Element elem, boolean selected) {
-        setStyleName(elem, STYLENNAME_SELECTED, selected);
-      }
-    };
-  }
-
-  public Range getRange() {
-    return impl.getRange();
-  }
-
-  @Override
-  public void onBrowserEvent(Event event) {
-    super.onBrowserEvent(event);
-
-    Element target = event.getEventTarget().cast();
-    int type = event.getTypeInt();
-    if (type == Event.ONCLICK) {
-      // Open the node when the open image is clicked.
-      Element showFewerElem = impl.getShowFewerElem();
-      Element showMoreElem = impl.getShowMoreElem();
-      if (showFewerElem != null && showFewerElem.isOrHasChild(target)) {
-        impl.showFewer();
-        return;
-      } else if (showMoreElem != null && showMoreElem.isOrHasChild(target)) {
-        impl.showMore();
-        return;
-      }
-    }
-
-    // Forward the event to the cell.
-    String idxString = "";
-    while ((target != null)
-        && ((idxString = target.getAttribute("__idx")).length() == 0)) {
-      target = target.getParentElement();
-    }
-    if (idxString.length() > 0) {
-      int idx = Integer.parseInt(idxString);
-      T value = impl.getValue(idx);
-      cell.onBrowserEvent(target, value, null, event, valueUpdater);
-      if (!cell.consumesEvents() && type == Event.ONMOUSEDOWN) {
-        SelectionModel<? super T> selectionModel = impl.getSelectionModel();
-        if (selectionModel != null) {
-          selectionModel.setSelected(value, true);
-        }
-      }
-    }
-  }
-
-  public void setData(int start, int length, List<T> values) {
-    impl.setData(values, start);
-  }
-
-  public void setDataSize(int size, boolean isExact) {
-    impl.setDataSize(size);
-  }
-
-  public void setDelegate(Delegate<T> delegate) {
-    impl.setDelegate(delegate);
-  }
-
-  public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
-    impl.setSelectionModel(selectionModel);
-  }
-
-  /**
-   * Set the value updater to use when cells modify items.
-   * 
-   * @param valueUpdater the {@link ValueUpdater}
-   */
-  public void setValueUpdater(ValueUpdater<T, Void> valueUpdater) {
-    this.valueUpdater = valueUpdater;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleColumn.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleColumn.java
deleted file mode 100644
index 82150f2..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleColumn.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.list.client;
-
-import com.google.gwt.bikeshed.cells.client.Cell;
-
-/**
- * A column that does not make use of view data.
- *
- * @param <T> the row type
- * @param <C> the column type
- */
-public abstract class SimpleColumn<T, C> extends Column<T, C, Void> {
-
-  public SimpleColumn(Cell<C, Void> cell) {
-    super(cell);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimplePager.css b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimplePager.css
new file mode 100644
index 0000000..82bf184
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimplePager.css
@@ -0,0 +1,14 @@
+.pageDetails {
+  padding: 4px 8px;
+  text-align: center;
+}
+
+.button {
+  padding: 4px;
+  cursor: pointer;
+  cursor: hand;
+}
+
+.disabledButton {
+  cursor: default;   
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimplePager.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimplePager.java
new file mode 100644
index 0000000..9e06cfb
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimplePager.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.view.client.PagingListView;
+
+/**
+ * A pager for controlling a {@link PagingListView} that only supports simple
+ * page navigation.
+ * 
+ * @param <T> the type of the PagingListView being controlled
+ */
+public class SimplePager<T> extends AbstractPager<T> {
+
+  /**
+   * The location of the text relative to the paging buttons.
+   */
+  public static enum TextLocation {
+    LEFT, RIGHT, CENTER;
+  }
+
+  /**
+   * A ClientBundle that provides images for this widget.
+   */
+  public static interface Resources extends ClientBundle {
+
+    /**
+     * The image used to go to the first page.
+     */
+    ImageResource simplePagerFirstPage();
+
+    /**
+     * The disabled first page image.
+     */
+    ImageResource simplePagerFirstPageDisabled();
+
+    /**
+     * The image used to go to the last page.
+     */
+    ImageResource simplePagerLastPage();
+
+    /**
+     * The disabled last page image.
+     */
+    ImageResource simplePagerLastPageDisabled();
+
+    /**
+     * The image used to go to the next page.
+     */
+    ImageResource simplePagerNextPage();
+
+    /**
+     * The disabled next page image.
+     */
+    ImageResource simplePagerNextPageDisabled();
+
+    /**
+     * The image used to go to the previous page.
+     */
+    ImageResource simplePagerPreviousPage();
+
+    /**
+     * The disabled previous page image.
+     */
+    ImageResource simplePagerPreviousPageDisabled();
+
+    /**
+     * The styles used in this widget.
+     */
+    @Source("SimplePager.css")
+    Style simplePagerStyle();
+  }
+
+  /**
+   * Styles used by this widget.
+   */
+  public static interface Style extends CssResource {
+
+    /**
+     * Applied to buttons.
+     */
+    String button();
+
+    /**
+     * Applied to disabled buttons.
+     */
+    String disabledButton();
+
+    /**
+     * Applied to the details text.
+     */
+    String pageDetails();
+  }
+
+  private static Resources DEFAULT_RESOURCES;
+
+  private static Resources getDefaultResources() {
+    if (DEFAULT_RESOURCES == null) {
+      DEFAULT_RESOURCES = GWT.create(Resources.class);
+    }
+    return DEFAULT_RESOURCES;
+  }
+
+  private final Image firstPage;
+  private final Label label = new Label();
+  private final Image lastPage;
+
+  /**
+   * Set to true when the next and last buttons are disabled.
+   */
+  private boolean nextDisabled;
+
+  private final Image nextPage;
+
+  /**
+   * Set to true when the prev and first buttons are disabled.
+   */
+  private boolean prevDisabled;
+
+  private final Image prevPage;
+
+  /**
+   * The {@link Resources} used by this widget.
+   */
+  private final Resources resources;
+
+  /**
+   * The {@link Style} used by this widget.
+   */
+  private final Style style;
+
+  /**
+   * Construct a {@link SimplePager}.
+   * 
+   * @param view the {@link PagingListView} to page
+   */
+  public SimplePager(PagingListView<T> view) {
+    this(view, TextLocation.CENTER);
+  }
+
+  /**
+   * Construct a {@link SimplePager} with the specified text location.
+   * 
+   * @param view the {@link PagingListView} to page
+   * @param location the location of the text relative to the buttons
+   */
+  public SimplePager(PagingListView<T> view, TextLocation location) {
+    this(view, location, getDefaultResources());
+  }
+
+  /**
+   * Construct a {@link SimplePager} with the specified resources.
+   * 
+   * @param view the {@link PagingListView} to page
+   * @param location the location of the text relative to the buttons
+   * @param resources the {@link Resources} to use
+   */
+  public SimplePager(PagingListView<T> view, TextLocation location,
+      Resources resources) {
+    super(view);
+    this.resources = resources;
+    this.style = resources.simplePagerStyle();
+    this.style.ensureInjected();
+
+    // Create the buttons.
+    firstPage = new Image(resources.simplePagerFirstPage());
+    lastPage = new Image(resources.simplePagerLastPage());
+    nextPage = new Image(resources.simplePagerNextPage());
+    prevPage = new Image(resources.simplePagerPreviousPage());
+
+    // Add handlers.
+    firstPage.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        firstPage();
+      }
+    });
+    lastPage.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        lastPage();
+      }
+    });
+    nextPage.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        nextPage();
+      }
+    });
+    prevPage.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        previousPage();
+      }
+    });
+
+    // Construct the widget.
+    HorizontalPanel layout = new HorizontalPanel();
+    layout.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
+    initWidget(layout);
+    if (location == TextLocation.RIGHT) {
+      layout.add(label);
+    }
+    layout.add(firstPage);
+    layout.add(prevPage);
+    if (location == TextLocation.CENTER) {
+      layout.add(label);
+    }
+    layout.add(nextPage);
+    layout.add(lastPage);
+    if (location == TextLocation.LEFT) {
+      layout.add(label);
+    }
+
+    // Add style names to the cells.
+    firstPage.getElement().getParentElement().addClassName(style.button());
+    prevPage.getElement().getParentElement().addClassName(style.button());
+    label.getElement().getParentElement().addClassName(style.pageDetails());
+    nextPage.getElement().getParentElement().addClassName(style.button());
+    lastPage.getElement().getParentElement().addClassName(style.button());
+  }
+
+  @Override
+  public void onRangeOrSizeChanged(PagingListView<T> listView) {
+    super.onRangeOrSizeChanged(listView);
+    label.setText(createText());
+
+    // Update the prev and first buttons.
+    boolean hasPrev = hasPreviousPage();
+    if (hasPrev && prevDisabled) {
+      prevDisabled = false;
+      firstPage.setResource(resources.simplePagerFirstPage());
+      prevPage.setResource(resources.simplePagerPreviousPage());
+      firstPage.getElement().getParentElement().removeClassName(
+          style.disabledButton());
+      prevPage.getElement().getParentElement().removeClassName(
+          style.disabledButton());
+    } else if (!hasPrev && !prevDisabled) {
+      prevDisabled = true;
+      firstPage.setResource(resources.simplePagerFirstPageDisabled());
+      prevPage.setResource(resources.simplePagerPreviousPageDisabled());
+      firstPage.getElement().getParentElement().addClassName(
+          style.disabledButton());
+      prevPage.getElement().getParentElement().addClassName(
+          style.disabledButton());
+    }
+
+    // Update the next and last buttons.
+    if (isRangeLimited()) {
+      boolean hasNext = hasNextPage();
+      if (hasNext && nextDisabled) {
+        nextDisabled = false;
+        nextPage.setResource(resources.simplePagerNextPage());
+        lastPage.setResource(resources.simplePagerLastPage());
+        nextPage.getElement().getParentElement().removeClassName(
+            style.disabledButton());
+        lastPage.getElement().getParentElement().removeClassName(
+            style.disabledButton());
+      } else if (!hasNext && !nextDisabled) {
+        nextDisabled = true;
+        nextPage.setResource(resources.simplePagerNextPageDisabled());
+        lastPage.setResource(resources.simplePagerLastPageDisabled());
+        nextPage.getElement().getParentElement().addClassName(
+            style.disabledButton());
+        lastPage.getElement().getParentElement().addClassName(
+            style.disabledButton());
+      }
+    }
+  }
+
+  /**
+   * Get the text to display in the pager that reflects the state of the pager.
+   * 
+   * @return the text
+   */
+  protected String createText() {
+    // Default text is 1 based.
+    PagingListView<T> view = getPagingListView();
+    int pageStart = view.getPageStart() + 1;
+    int pageSize = view.getPageSize();
+    int dataSize = view.getDataSize();
+    int endIndex = Math.min(dataSize, pageStart + pageSize - 1);
+    endIndex = Math.max(pageStart, endIndex);
+    return pageStart + "-" + endIndex + " of " + dataSize;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextColumn.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextColumn.java
index 4d8dd87..a799fec 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextColumn.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextColumn.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.bikeshed.list.client;
 
-import com.google.gwt.bikeshed.cells.client.TextCell;
+import com.google.gwt.cell.client.TextCell;
 
 /**
  * A column that displays its contents with a {@link TextCell} and does not make
@@ -23,9 +23,9 @@
  *
  * @param <T> the row type
  */
-public abstract class TextColumn<T> extends Column<T, String, Void> {
+public abstract class TextColumn<T> extends Column<T, String> {
 
   public TextColumn() {
-    super(TextCell.getInstance());
+    super(new TextCell());
   }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
index 0890c3b..222129d 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.bikeshed.list.client;
 
-import com.google.gwt.bikeshed.cells.client.TextCell;
+import com.google.gwt.cell.client.TextCell;
 
 /**
  * A Header containing String data rendered by a TextCell.
@@ -25,7 +25,7 @@
   private String text;
 
   public TextHeader(String text) {
-    super(TextCell.getInstance());
+    super(new TextCell());
     this.text = text;
   }
 
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/cellListSelectedBackground.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/cellListSelectedBackground.png
new file mode 100644
index 0000000..108ea3f
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/cellListSelectedBackground.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/cellTableHeaderBackground.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/cellTableHeaderBackground.png
new file mode 100644
index 0000000..dab5a96
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/cellTableHeaderBackground.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/CellListImpl.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/CellListImpl.java
new file mode 100644
index 0000000..1394532
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/CellListImpl.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.client.impl;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.AbstractListViewAdapter.DefaultRange;
+import com.google.gwt.view.client.ListView.Delegate;
+import com.google.gwt.view.client.PagingListView.Pager;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeHandler;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Implementation of {@link com.google.gwt.bikeshed.list.client.CellList}. This
+ * class is subject to change or deletion. Do not rely on this class.
+ * 
+ * @param <T> the data type of items in the list
+ */
+public abstract class CellListImpl<T> {
+
+  /**
+   * The Element that holds the rendered child items.
+   */
+  private final Element childContainer;
+
+  /**
+   * The local cache of data in the view. The 0th index in the list corresponds
+   * to the data at pageStart.
+   */
+  private final List<T> data = new ArrayList<T>();
+
+  private int dataSize;
+
+  /**
+   * A boolean indicating whether or not the data size has ever been set. If the
+   * data size has never been set, then we will always pass it along to the
+   * view.
+   */
+  private boolean dataSizeInitialized;
+
+  private Delegate<T> delegate;
+  private final PagingListView<T> listView;
+  private Pager<T> pager;
+
+  /**
+   * The number of elements to show on the page.
+   */
+  private int pageSize;
+
+  /**
+   * The start index of the current page.
+   */
+  private int pageStart = 0;
+
+  /**
+   * Set to true when the page start changes, and we need to do a full refresh.
+   */
+  private boolean pageStartChanged;
+
+  /**
+   * Indicates whether or not a redraw is scheduled.
+   */
+  private boolean redrawScheduled;
+
+  /**
+   * The command used to refresh or redraw the page. If both are scheduled, the
+   * refresh will take priority.
+   */
+  private final Scheduler.ScheduledCommand refreshCommand = new Scheduler.ScheduledCommand() {
+    public void execute() {
+      // We clear the variables before making the refresh/redraw call so another
+      // refresh/redraw can be scheduled synchronously.
+      boolean wasRefreshScheduled = refreshScheduled;
+      boolean wasRedrawScheduled = redrawScheduled;
+      refreshScheduled = false;
+      redrawScheduled = false;
+      if (wasRefreshScheduled && delegate != null) {
+        // Refresh takes priority over redraw.
+        delegate.onRangeChanged(listView);
+      } else if (wasRedrawScheduled) {
+        setData(data, pageStart);
+      }
+    }
+  };
+
+  /**
+   * Indicates whether or not a refresh is scheduled.
+   */
+  private boolean refreshScheduled;
+
+  private HandlerRegistration selectionHandler;
+
+  /**
+   * A local cache of the currently selected rows. We cannot track selected keys
+   * instead because we might end up in an inconsistent state where we render a
+   * subset of a list with duplicate values, styling a value in the subset but
+   * not styling the duplicate value outside of the subset.
+   */
+  private final Set<Integer> selectedRows = new HashSet<Integer>();
+
+  private SelectionModel<? super T> selectionModel;
+
+  /**
+   * The temporary element use to convert HTML to DOM.
+   */
+  private final Element tmpElem;
+
+  public CellListImpl(PagingListView<T> listView, int pageSize,
+      Element childContainer) {
+    this.childContainer = childContainer;
+    this.listView = listView;
+    this.pageSize = pageSize;
+    tmpElem = Document.get().createDivElement();
+  }
+
+  /**
+   * Get the list of data within the current range. The data may not be
+   * complete.
+   * 
+   * @return the list of data
+   */
+  public List<T> getData() {
+    return data;
+  }
+
+  /**
+   * Get the overall data size.
+   * 
+   * @return the data size
+   */
+  public int getDataSize() {
+    return dataSize;
+  }
+
+  /**
+   * Get the number of items that are within the current page and data range.
+   * 
+   * @return the number of displayed items
+   */
+  public int getDisplayedItemCount() {
+    return Math.min(pageSize, dataSize - pageStart);
+  }
+
+  /**
+   * @return the page size
+   */
+  public int getPageSize() {
+    return pageSize;
+  }
+
+  /**
+   * @return the start index of the current page (inclusive)
+   */
+  public int getPageStart() {
+    return pageStart;
+  }
+
+  /**
+   * @return the range of data being displayed
+   */
+  public Range getRange() {
+    return new DefaultRange(pageStart, pageSize);
+  }
+
+  public SelectionModel<? super T> getSelectionModel() {
+    return selectionModel;
+  }
+
+  /**
+   * Redraw the list with the current data.
+   */
+  public void redraw() {
+    scheduleRefresh(true);
+  }
+
+  /**
+   * Request data from the delegate.
+   */
+  public void refresh() {
+    scheduleRefresh(false);
+  }
+
+  /**
+   * Set the data in the list.
+   * 
+   * @param values the new data
+   * @param valuesStart the start index of the values
+   */
+  public void setData(List<T> values, int valuesStart) {
+    int valuesLength = values.size();
+    int valuesEnd = valuesStart + valuesLength;
+
+    // Calculate the bounded start (inclusive) and end index (exclusive).
+    int pageEnd = pageStart + pageSize;
+    int boundedStart = Math.max(valuesStart, pageStart);
+    int boundedEnd = Math.min(valuesEnd, pageEnd);
+    if (boundedStart >= boundedEnd) {
+      // The data is out of range for the current page.
+      return;
+    }
+
+    // The data size must be at least as large as the data.
+    if (valuesEnd > dataSize) {
+      dataSize = valuesEnd;
+      onSizeChanged();
+    }
+
+    // Create placeholders up to the specified index.
+    int lastCacheIndex = pageStart + data.size();
+    while (lastCacheIndex < boundedStart) {
+      data.add(null);
+      lastCacheIndex++;
+    }
+
+    // Insert the new values into the data array.
+    for (int i = boundedStart; i < boundedEnd; i++) {
+      T value = values.get(i - valuesStart);
+      int dataIndex = i - pageStart;
+      if (dataIndex < data.size()) {
+        data.set(dataIndex, value);
+      } else {
+        data.add(value);
+      }
+
+      // Update our local cache of selected rows.
+      if (selectionModel != null) {
+        if (value != null && selectionModel.isSelected(value)) {
+          selectedRows.add(i);
+        } else {
+          selectedRows.remove(i);
+        }
+      }
+    }
+
+    // Construct a run of elements within the range of the data and the page.
+    boundedStart = pageStartChanged ? pageStart : boundedStart;
+    List<T> boundedValues = data.subList(boundedStart - pageStart, boundedEnd
+        - pageStart);
+    int boundedSize = boundedValues.size();
+    StringBuilder sb = new StringBuilder();
+    emitHtml(sb, boundedValues, boundedStart, selectionModel);
+
+    // Replace the DOM elements with the new rendered cells.
+    int childCount = childContainer.getChildCount();
+    if (boundedStart == pageStart
+        && (boundedSize >= childCount || boundedSize >= getDisplayedItemCount())) {
+      childContainer.setInnerHTML(sb.toString());
+    } else {
+      Element container = convertToElements(sb.toString());
+      Element toReplace = null;
+      int realStart = boundedStart - pageStart;
+      if (realStart < childCount) {
+        toReplace = childContainer.getChild(realStart).cast();
+      }
+      for (int i = boundedStart; i < boundedEnd; i++) {
+        if (toReplace == null) {
+          // The child will be removed from tmpElem, so always use index 0.
+          childContainer.appendChild(container.getChild(0));
+        } else {
+          Element nextSibling = toReplace.getNextSiblingElement();
+          childContainer.replaceChild(container.getChild(0), toReplace);
+          toReplace = nextSibling;
+        }
+      }
+    }
+
+    // Reset the pageStartChanged boolean.
+    pageStartChanged = false;
+  }
+
+  /**
+   * Set the overall size of the list.
+   * 
+   * @param size the overall size
+   */
+  public void setDataSize(int size) {
+    if (dataSizeInitialized && size == this.dataSize) {
+      return;
+    }
+    dataSizeInitialized = true;
+    this.dataSize = size;
+    updateDataAndView();
+    onSizeChanged();
+  }
+
+  public void setDelegate(Delegate<T> delegate) {
+    this.delegate = delegate;
+  }
+
+  public void setPager(PagingListView.Pager<T> pager) {
+    this.pager = pager;
+  }
+
+  /**
+   * Set the number of items to show on each page.
+   * 
+   * @param pageSize the page size
+   */
+  public void setPageSize(int pageSize) {
+    if (pageSize == this.pageSize) {
+      return;
+    }
+    this.pageSize = pageSize;
+    updateDataAndView();
+    onSizeChanged();
+    refresh();
+  }
+
+  /**
+   * Set the start index of the range.
+   * 
+   * @param pageStart the start index
+   */
+  public void setPageStart(int pageStart) {
+    if (pageStart == this.pageStart) {
+      return;
+    } else if (pageStart > this.pageStart) {
+      if (data.size() > pageStart - this.pageStart) {
+        // Remove the data we no longer need.
+        for (int i = this.pageStart; i < pageStart; i++) {
+          data.remove(0);
+        }
+      } else {
+        // We have no overlapping data, so just clear it.
+        data.clear();
+      }
+    } else {
+      if ((data.size() > 0) && (this.pageStart - pageStart < pageSize)) {
+        // Insert null data at the beginning.
+        for (int i = pageStart; i < this.pageStart; i++) {
+          data.add(0, null);
+        }
+      } else {
+        // We have no overlapping data, so just clear it.
+        data.clear();
+      }
+    }
+
+    // Update the start index.
+    this.pageStart = pageStart;
+    this.pageStartChanged = true;
+    updateDataAndView();
+    onSizeChanged();
+
+    // Refresh the view with the data that is currently available.
+    setData(data, pageStart);
+
+    // Send a request for new data in the range.
+    refresh();
+  }
+
+  /**
+   * Set the {@link SelectionModel}, optionally triggering an update.
+   * 
+   * @param selectionModel the new {@link SelectionModel}
+   * @param updateSelection true to update selection
+   */
+  public void setSelectionModel(final SelectionModel<? super T> selectionModel,
+      boolean updateSelection) {
+    // Remove the old selection model.
+    if (selectionHandler != null) {
+      selectionHandler.removeHandler();
+      selectionHandler = null;
+    }
+
+    // Set the new selection model.
+    this.selectionModel = selectionModel;
+    if (selectionModel != null) {
+      selectionHandler = selectionModel.addSelectionChangeHandler(new SelectionChangeHandler() {
+        public void onSelectionChange(SelectionChangeEvent event) {
+          updateSelection();
+        }
+      });
+    }
+
+    // Update the current selection state based on the new model.
+    if (updateSelection) {
+      updateSelection();
+    }
+  }
+
+  /**
+   * Convert the specified HTML into DOM elements and return the parent of the
+   * DOM elements.
+   * 
+   * @param html the HTML to convert
+   * @return the parent element
+   */
+  protected Element convertToElements(String html) {
+    tmpElem.setInnerHTML(html);
+    return tmpElem;
+  }
+
+  /**
+   * Check whether or not the cells in the list depend on the selection state.
+   * 
+   * @return true if cells depend on selection, false if not
+   */
+  protected abstract boolean dependsOnSelection();
+
+  /**
+   * Construct the HTML that represents the list of items.
+   * 
+   * @param sb the {@link StringBuilder} to build into
+   * @param values the values to render
+   * @param start the start index
+   * @param selectionModel the {@link SelectionModel}
+   */
+  protected abstract void emitHtml(StringBuilder sb, List<T> values, int start,
+      SelectionModel<? super T> selectionModel);
+
+  /**
+   * Called when pageStart, pageSize, or data size changes.
+   */
+  protected void onSizeChanged() {
+    // Inform the pager about a change in page start, page size, or data size
+    if (pager != null) {
+      pager.onRangeOrSizeChanged(listView);
+    }
+  }
+
+  /**
+   * Remove the last element from the list.
+   */
+  protected void removeLastItem() {
+    childContainer.getLastChild().removeFromParent();
+  }
+
+  /**
+   * Mark an element as selected or unselected. This is called when a cells
+   * selection state changes, but the cell does not depend on selection.
+   * 
+   * @param elem the element to modify
+   * @param selected true if selected, false if not
+   */
+  protected abstract void setSelected(Element elem, boolean selected);
+
+  /**
+   * Update the table based on the current selection.
+   */
+  protected void updateSelection() {
+    // Determine if our selection states are stale.
+    boolean dependsOnSelection = dependsOnSelection();
+    boolean refreshRequired = false;
+    Element cellElem = childContainer.getFirstChildElement();
+    int row = pageStart;
+    for (T value : data) {
+      boolean selected = selectionModel == null ? false
+          : selectionModel.isSelected(value);
+      if (selected != selectedRows.contains(row)) {
+        refreshRequired = true;
+        if (selected) {
+          selectedRows.add(row);
+        } else {
+          selectedRows.remove(row);
+        }
+        if (!dependsOnSelection) {
+          // The cell doesn't depend on selection, so we only need to update the
+          // style.
+          setSelected(cellElem, selected);
+        }
+      }
+      cellElem = cellElem.getNextSiblingElement();
+      row++;
+    }
+
+    // Refresh the entire list if needed.
+    if (refreshRequired && dependsOnSelection) {
+      setData(data, pageStart);
+    }
+  }
+
+  /**
+   * Schedule a redraw or refresh.
+   * 
+   * @param redrawOnly if true, only schedule a redraw
+   */
+  private void scheduleRefresh(boolean redrawOnly) {
+    if (!refreshScheduled && !redrawScheduled) {
+      Scheduler.get().scheduleDeferred(refreshCommand);
+    }
+    if (redrawOnly) {
+      redrawScheduled = true;
+    } else {
+      refreshScheduled = true;
+    }
+  }
+
+  /**
+   * Ensure that the data and the view are in a consistent state.
+   */
+  private void updateDataAndView() {
+    // Update the data size.
+    int expectedLastIndex = Math.max(0,
+        Math.min(pageSize, dataSize - pageStart));
+    int lastIndex = data.size() - 1;
+    while (lastIndex >= expectedLastIndex) {
+      data.remove(lastIndex);
+      selectedRows.remove(lastIndex + pageStart);
+      lastIndex--;
+    }
+
+    // Update the DOM.
+    int expectedChildCount = data.size();
+    int childCount = childContainer.getChildCount();
+    while (childCount > expectedChildCount) {
+      removeLastItem();
+      childCount--;
+    }
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/SimpleCellListImpl.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/SimpleCellListImpl.java
deleted file mode 100644
index 726aecc..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/SimpleCellListImpl.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.list.client.impl;
-
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.client.ListView.Delegate;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter.DefaultRange;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeHandler;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.event.shared.HandlerRegistration;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Implementation of {@link SimpleCellListImpl}. This class is subject to change
- * or deletion. Do not rely on this class.
- * 
- * @param <T> the data type of items in the list
- */
-public abstract class SimpleCellListImpl<T> {
-
-  private final Cell<T, Void> cell;
-  private final Element childContainer;
-  private final List<T> data = new ArrayList<T>();
-  private final Set<Object> selectedKeys = new HashSet<Object>();
-  private Delegate<T> delegate;
-  private final Element emptyMessageElem;
-  private final int increment;
-  private final int initialMaxSize;
-  private final ListView<T> listView;
-  private int maxSize;
-  private HandlerRegistration selectionHandler;
-  private SelectionModel<? super T> selectionModel;
-  private final Element showFewerElem;
-  private final Element showMoreElem;
-  private int size;
-  private final Element tmpElem;
-
-  public SimpleCellListImpl(ListView<T> listView, Cell<T, Void> cell,
-      int maxSize, int increment, Element childContainer,
-      Element emptyMessageElem, Element showMoreElem, Element showFewerElem) {
-    this.cell = cell;
-    this.childContainer = childContainer;
-    this.emptyMessageElem = emptyMessageElem;
-    this.increment = increment;
-    this.initialMaxSize = maxSize;
-    this.listView = listView;
-    this.maxSize = maxSize;
-    this.showFewerElem = showFewerElem;
-    this.showMoreElem = showMoreElem;
-    tmpElem = Document.get().createDivElement();
-
-    showOrHide(showMoreElem, false);
-    showOrHide(showFewerElem, false);
-    showOrHide(emptyMessageElem, false);
-  }
-
-  public Range getRange() {
-    return new DefaultRange(0, maxSize);
-  }
-
-  public SelectionModel<? super T> getSelectionModel() {
-    return selectionModel;
-  }
-
-  public Element getShowFewerElem() {
-    return showFewerElem;
-  }
-
-  public Element getShowMoreElem() {
-    return showMoreElem;
-  }
-
-  public T getValue(int i) {
-    return data.get(i);
-  }
-
-  /**
-   * Set the data in the list.
-   * 
-   * @param values the new data
-   * @param start the start index
-   */
-  public void setData(List<T> values, int start) {
-    int oldSize = size;
-    int len = values.size();
-    int end = start + len;
-
-    // The size must be at least as large as the data.
-    if (end > oldSize) {
-      size = end;
-      sizeChanged();
-    }
-
-    // Create placeholders up to the specified index.
-    while (data.size() < start) {
-      data.add(null);
-    }
-
-    // Insert the new values into the data array.
-    for (int i = start; i < end; i++) {
-      T value = values.get(i - start);
-      if (i < data.size()) {
-        data.set(i, value);
-      } else {
-        data.add(value);
-
-        // Update our local cache of selected values. We only need to consider
-        // new values at this point. If any existing value changes its selection
-        // state, we'll find out from the selection model.
-        if (selectionModel != null && selectionModel.isSelected(value)) {
-          selectedKeys.add(getKey(value));
-        }
-      }
-    }
-
-    // Construct a run of element from start (inclusive) to start + len
-    // (exclusive)
-    StringBuilder sb = new StringBuilder();
-    emitHtml(sb, values, start, cell, selectionModel);
-
-    // Replace the DOM elements with the new rendered cells.
-    if (oldSize == 0 || (start == 0 && len >= oldSize)) {
-      childContainer.setInnerHTML(sb.toString());
-    } else {
-      makeElements();
-      tmpElem.setInnerHTML(sb.toString());
-      Element toReplace = childContainer.getChild(start).cast();
-      for (int i = start; i < end; i++) {
-        // The child will be removed from tmpElem, so always use index 0.
-        Element nextSibling = toReplace.getNextSiblingElement();
-        childContainer.replaceChild(tmpElem.getChild(0), toReplace);
-        toReplace = nextSibling;
-      }
-    }
-  }
-
-  /**
-   * Set the overall size of the list.
-   * 
-   * @param size the overall size
-   */
-  public void setDataSize(int size) {
-    this.size = size;
-    int toRemove = data.size() - size;
-    for (int i = 0; i < toRemove; i++) {
-      removeLastItem();
-    }
-    sizeChanged();
-  }
-
-  public void setDelegate(Delegate<T> delegate) {
-    this.delegate = delegate;
-  }
-
-  public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
-    // Remove the old selection model.
-    if (selectionHandler != null) {
-      selectionHandler.removeHandler();
-      selectionHandler = null;
-    }
-
-    // Set the new selection model.
-    this.selectionModel = selectionModel;
-    if (selectionModel != null) {
-      selectionHandler = selectionModel.addSelectionChangeHandler(new SelectionChangeHandler() {
-        public void onSelectionChange(SelectionChangeEvent event) {
-          // Determine if our selection states are stale.
-          boolean dependsOnSelection = cell.dependsOnSelection();
-          boolean refreshRequired = false;
-          Element cellElem = childContainer.getFirstChildElement();
-          for (T value : data) {
-            boolean selected = selectionModel.isSelected(value);
-            Object key = getKey(value);
-            if (selected != selectedKeys.contains(key)) {
-              refreshRequired = true;
-              if (selected) {
-                selectedKeys.add(key);
-              } else {
-                selectedKeys.remove(key);
-              }
-              if (!dependsOnSelection) {
-                // The cell doesn't depend on Selection, so we only need to
-                // update the style.
-                setSelected(cellElem, selected);
-              }
-            }
-            cellElem = cellElem.getNextSiblingElement();
-          }
-
-          // Refresh the entire list if needed.
-          if (refreshRequired && dependsOnSelection) {
-            setData(data, 0);
-          }
-        }
-      });
-    }
-  }
-
-  /**
-   * Show fewer items.
-   */
-  public void showFewer() {
-    this.maxSize = Math.max(initialMaxSize, maxSize - increment);
-    sizeChanged();
-    if (delegate != null) {
-      delegate.onRangeChanged(listView);
-    }
-  }
-
-  /**
-   * Show more items.
-   */
-  public void showMore() {
-    this.maxSize += increment;
-    sizeChanged();
-    if (delegate != null) {
-      delegate.onRangeChanged(listView);
-    }
-  }
-
-  /**
-   * Construct the HTML that represents the list of items.
-   * 
-   * @param sb the {@link StringBuilder} to build into
-   * @param values the values to render
-   * @param start the start index
-   * @param cell the cell to use as a renderer
-   * @param selectionModel the {@link SelectionModel}
-   */
-  protected abstract void emitHtml(StringBuilder sb, List<T> values, int start,
-      Cell<T, Void> cell, SelectionModel<? super T> selectionModel);
-
-  /**
-   * Remove the last element from the list.
-   */
-  protected void removeLastItem() {
-    data.remove(data.size() - 1);
-    childContainer.getLastChild().removeFromParent();
-  }
-
-  /**
-   * Mark an element as selected or unselected. This is called when a cells
-   * selection state changes, but the cell does not depend on selection.
-   * 
-   * @param elem the element to modify
-   * @param selected true if selected, false if not
-   */
-  protected abstract void setSelected(Element elem, boolean selected);
-
-  /**
-   * Get the key for a given item.
-   * 
-   * @param value the item
-   * @return the key, or null if there is no selection model
-   */
-  private Object getKey(T value) {
-    return selectionModel == null ? null
-        : selectionModel.getKeyProvider().getKey(value);
-  }
-
-  /**
-   * Create placeholder elements that will be replaced with data. This is used s
-   * when replacing a subset of the list.
-   */
-  private void makeElements() {
-    int childCount = childContainer.getChildCount();
-    int actualSize = Math.min(data.size(), maxSize);
-    if (actualSize > childCount) {
-      // Create new elements with a "loading..." message
-      StringBuilder sb = new StringBuilder();
-      int newElements = actualSize - childCount;
-      for (int i = 0; i < newElements; i++) {
-        // TODO(jlabanca): Make this I18N friendly.
-        sb.append("<div __idx='" + (childCount + i)
-            + "'><i>loading...</i></div>");
-      }
-
-      if (childCount == 0) {
-        childContainer.setInnerHTML(sb.toString());
-      } else {
-        tmpElem.setInnerHTML(sb.toString());
-        for (int i = 0; i < newElements; i++) {
-          childContainer.appendChild(tmpElem.getChild(0));
-        }
-      }
-    } else if (actualSize < childCount) {
-      // Remove excess elements
-      while (actualSize < childCount) {
-        removeLastItem();
-        childCount--;
-      }
-    }
-  }
-
-  /**
-   * Show or hide an element.
-   * 
-   * @param element the element
-   * @param show true to show, false to hide
-   */
-  private void showOrHide(Element element, boolean show) {
-    if (show) {
-      element.getStyle().clearDisplay();
-    } else {
-      element.getStyle().setDisplay(Display.NONE);
-    }
-  }
-
-  /**
-   * Called when the size of the list changes.
-   */
-  private void sizeChanged() {
-    showOrHide(showMoreElem, size > maxSize);
-    showOrHide(showFewerElem, maxSize > initialMaxSize);
-    showOrHide(emptyMessageElem, size == 0);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerFirstPage.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerFirstPage.png
new file mode 100644
index 0000000..13a2ba2
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerFirstPage.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerFirstPageDisabled.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerFirstPageDisabled.png
new file mode 100644
index 0000000..645d305
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerFirstPageDisabled.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerLastPage.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerLastPage.png
new file mode 100644
index 0000000..db4f865
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerLastPage.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerLastPageDisabled.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerLastPageDisabled.png
new file mode 100644
index 0000000..72cea52
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerLastPageDisabled.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerNextPage.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerNextPage.png
new file mode 100644
index 0000000..a0879b6
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerNextPage.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerNextPageDisabled.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerNextPageDisabled.png
new file mode 100644
index 0000000..9f40e91
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerNextPageDisabled.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerPreviousPage.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerPreviousPage.png
new file mode 100644
index 0000000..d4f15a1
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerPreviousPage.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerPreviousPageDisabled.png b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerPreviousPageDisabled.png
new file mode 100644
index 0000000..aca6318
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/simplePagerPreviousPageDisabled.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/Tree.gwt.xml b/bikeshed/src/com/google/gwt/bikeshed/tree/Tree.gwt.xml
index 61d9190..8b19a59 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/Tree.gwt.xml
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/Tree.gwt.xml
@@ -15,7 +15,7 @@
 -->
 <module>
   <inherits name='com.google.gwt.user.User'/>
-  <inherits name='com.google.gwt.bikeshed.cells.Cells'/>
+  <inherits name='com.google.gwt.cell.Cell'/>
   <inherits name='com.google.gwt.bikeshed.list.List'/>
   <source path="shared" />
   <source path="client" />
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowser.css b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowser.css
new file mode 100644
index 0000000..6c44ae6
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowser.css
@@ -0,0 +1,27 @@
+.column {
+  
+}
+
+.firstColumn {
+  
+}
+
+.item {
+  padding: 8px;
+}
+
+@sprite .openItem {
+  gwt-image: 'cellBrowserOpenBackground';
+  background-color: #7b7b7b;
+  color: white;
+  height: auto;
+  overflow: auto;
+}
+
+@sprite .selectedItem {
+  gwt-image: 'cellBrowserSelectedBackground';
+  background-color: #628cd5;
+  color: white;
+  height: auto;
+  overflow: auto;
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowser.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowser.java
new file mode 100644
index 0000000..6ec1b5d
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowser.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.tree.client;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.bikeshed.list.client.CellList;
+import com.google.gwt.bikeshed.list.client.PageSizePager;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HasAnimation;
+import com.google.gwt.user.client.ui.ProvidesResize;
+import com.google.gwt.user.client.ui.RequiresResize;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.TreeViewModel;
+import com.google.gwt.view.client.PagingListView.Pager;
+import com.google.gwt.view.client.TreeViewModel.NodeInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A "browsable" view of a tree in which only a single node per level may be
+ * open at one time.
+ */
+public class CellBrowser extends Composite implements ProvidesResize,
+    RequiresResize, HasAnimation {
+
+  /**
+   * A ClientBundle that provides images for this widget.
+   */
+  public static interface Resources extends ClientBundle {
+
+    /**
+     * An image indicating a closed branch.
+     */
+    ImageResource cellBrowserClosed();
+
+    /**
+     * An image indicating an open branch.
+     */
+    ImageResource cellBrowserOpen();
+
+    /**
+     * The background used for open items.
+     */
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource cellBrowserOpenBackground();
+
+    /**
+     * The background used for selected items.
+     */
+    @Source("../../list/client/cellListSelectedBackground.png")
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource cellBrowserSelectedBackground();
+
+    /**
+     * The styles used in this widget.
+     */
+    @Source("CellBrowser.css")
+    Style cellBrowserStyle();
+  }
+
+  /**
+   * Styles used by this widget.
+   */
+  public static interface Style extends CssResource {
+
+    /**
+     * Applied to all columns.
+     */
+    String column();
+
+    /**
+     * Applied to the first column.
+     */
+    String firstColumn();
+
+    /**
+     * Applied to all list items.
+     */
+    String item();
+
+    /***
+     * Applied to open items.
+     */
+    String openItem();
+
+    /***
+     * Applied to selected items.
+     */
+    String selectedItem();
+  }
+
+  /**
+   * We override the Resources in {@link CellList} so that the styles in
+   * {@link CellList} don't conflict with the styles in {@link CellBrowser}.
+   */
+  static interface CellListResources extends CellList.Resources {
+    @Source("CellBrowserOverride.css")
+    CellList.Style cellListStyle();
+  }
+
+  /**
+   * A wrapper around a cell that adds an open button.
+   * 
+   * @param <C> the data type of the cell
+   */
+  private class CellDecorator<C> extends AbstractCell<C> {
+
+    /**
+     * The cell used to render the inner contents.
+     */
+    private final Cell<C> cell;
+
+    /**
+     * The level of this list view.
+     */
+    private final int level;
+
+    /**
+     * The key of the currently open item.
+     */
+    private Object openKey;
+
+    /**
+     * The key provider for the node.
+     */
+    private final ProvidesKey<C> providesKey;
+
+    /**
+     * The selection model for the node.
+     */
+    private final SelectionModel<? super C> selectionModel;
+
+    /**
+     * Construct a new {@link CellDecorator}.
+     * 
+     * @param nodeInfo the {@link NodeInfo} associated with the cell
+     * @param level the level of items rendered by this decorator
+     */
+    public CellDecorator(NodeInfo<C> nodeInfo, int level) {
+      this.cell = nodeInfo.getCell();
+      this.level = level;
+      this.providesKey = nodeInfo.getProvidesKey();
+      this.selectionModel = nodeInfo.getSelectionModel();
+    }
+
+    @Override
+    public boolean consumesEvents() {
+      return cell.consumesEvents();
+    }
+
+    @Override
+    public boolean dependsOnSelection() {
+      return cell.dependsOnSelection();
+    }
+
+    @Override
+    public Object onBrowserEvent(Element parent, C value, Object viewData,
+        NativeEvent event, ValueUpdater<C> valueUpdater) {
+
+      // Fire the event to the inner cell.
+      viewData = cell.onBrowserEvent(getCellParent(parent), value, viewData,
+          event, valueUpdater);
+
+      // Open child nodes.
+      if (Event.getTypeInt(event.getType()) == Event.ONMOUSEDOWN) {
+        trimToLevel(level);
+
+        // Remove style from currently open item.
+        Element curOpenItem = Document.get().getElementById(getOpenId());
+        if (curOpenItem != null) {
+          setElementOpenState(curOpenItem.getParentElement(), false);
+        }
+        openKey = null;
+
+        // Save the key of the new open item and update the Element.
+        if (!viewModel.isLeaf(value)) {
+          NodeInfo<?> nodeInfo = viewModel.getNodeInfo(value);
+          if (nodeInfo != null) {
+            openKey = providesKey.getKey(value);
+            setElementOpenState(parent, true);
+            appendTreeNode(nodeInfo);
+          }
+        }
+      }
+
+      return viewData;
+    }
+
+    @Override
+    public void render(C value, Object viewData, StringBuilder sb) {
+      boolean isOpen = (openKey == null) ? false
+          : openKey.equals(providesKey.getKey(value));
+      boolean isSelected = (selectionModel == null) ? false
+          : selectionModel.isSelected(value);
+      sb.append("<div style='position:relative;padding-right:");
+      sb.append(imageWidth);
+      sb.append("px;'");
+      sb.append(" class='").append(style.item());
+      if (isOpen) {
+        sb.append(" ").append(style.openItem());
+      }
+      if (isSelected) {
+        sb.append(" ").append(style.selectedItem());
+      }
+      sb.append("'");
+      if (isOpen) {
+        sb.append(" id='").append(getOpenId()).append("'");
+      }
+      sb.append(">");
+      if (isOpen) {
+        sb.append(openImageHtml);
+      } else if (viewModel.isLeaf(value)) {
+        sb.append(LEAF_IMAGE);
+      } else {
+        sb.append(closedImageHtml);
+      }
+      sb.append("<div>");
+      cell.render(value, viewData, sb);
+      sb.append("</div></div>");
+    }
+
+    @Override
+    public void setValue(Element parent, C value, Object viewData) {
+      cell.setValue(getCellParent(parent), value, viewData);
+    }
+
+    /**
+     * Get the parent element of the decorated cell.
+     * 
+     * @param parent the parent of this cell
+     * @return the decorated cell's parent
+     */
+    private Element getCellParent(Element parent) {
+      return parent.getFirstChildElement().getChild(1).cast();
+    }
+
+    /**
+     * Get the image element of the decorated cell.
+     * 
+     * @param parent the parent of this cell
+     * @return the image element
+     */
+    private Element getImageElement(Element parent) {
+      return parent.getFirstChildElement().getFirstChildElement();
+    }
+
+    /**
+     * Get the ID of the open element.
+     * 
+     * @return the ID
+     */
+    private String getOpenId() {
+      return uniqueId + "-" + level;
+    }
+
+    /**
+     * Replace the image element of a cell and update the styles associated with
+     * the open state.
+     * 
+     * @param parent the parent element of the cell
+     * @param open true if open, false if closed
+     */
+    private void setElementOpenState(Element parent, boolean open) {
+      // Update the style name and ID.
+      Element wrapper = parent.getFirstChildElement();
+      if (open) {
+        wrapper.addClassName(style.openItem());
+        wrapper.setId(getOpenId());
+      } else {
+        wrapper.removeClassName(style.openItem());
+        wrapper.setId("");
+      }
+
+      // Replace the image element.
+      String html = open ? openImageHtml : closedImageHtml;
+      Element tmp = Document.get().createDivElement();
+      tmp.setInnerHTML(html);
+      Element imageElem = tmp.getFirstChildElement();
+      Element oldImg = getImageElement(parent);
+      wrapper.replaceChild(imageElem, oldImg);
+    }
+  }
+
+  /**
+   * The animation used to scroll to the newly added list view.
+   */
+  private class ScrollAnimation extends Animation {
+
+    /**
+     * The starting scroll position.
+     */
+    private int startScrollLeft;
+
+    /**
+     * The ending scroll position.
+     */
+    private int targetScrollLeft;
+
+    @Override
+    protected void onComplete() {
+      getElement().setScrollLeft(targetScrollLeft);
+    }
+
+    @Override
+    protected void onUpdate(double progress) {
+      int diff = targetScrollLeft - startScrollLeft;
+      getElement().setScrollLeft(startScrollLeft + (int) (diff * progress));
+    }
+
+    void scrollToEnd() {
+      Element elem = getElement();
+      targetScrollLeft = elem.getScrollWidth() - elem.getClientWidth();
+
+      if (isAnimationEnabled()) {
+        // Animate the scrolling.
+        startScrollLeft = elem.getScrollLeft();
+        run(250);
+      } else {
+        // Scroll instantly.
+        onComplete();
+      }
+    }
+  }
+
+  /**
+   * A node in the tree.
+   * 
+   * @param <C> the data type of the children of the node
+   */
+  private class TreeNode<C> {
+    private CellDecorator<C> cell;
+    private ListView<C> listView;
+    private NodeInfo<C> nodeInfo;
+    private Widget widget;
+
+    /**
+     * Construct a new {@link TreeNode}.
+     * 
+     * @param nodeInfo the nodeInfo for the children nodes
+     * @param listView the list view assocated with the node
+     * @param widget the widget that represents the list view
+     */
+    public TreeNode(NodeInfo<C> nodeInfo, ListView<C> listView,
+        CellDecorator<C> cell, Widget widget) {
+      this.cell = cell;
+      this.listView = listView;
+      this.nodeInfo = nodeInfo;
+      this.widget = widget;
+    }
+
+    /**
+     * Get the {@link CellDecorator} used to render the node.
+     * 
+     * @return the cell decorator
+     */
+    public CellDecorator<C> getCell() {
+      return cell;
+    }
+
+    /**
+     * Unregister the list view and remove it from the widget.
+     */
+    void cleanup() {
+      listView.setSelectionModel(null);
+      nodeInfo.unsetView();
+      getSplitLayoutPanel().remove(widget);
+    }
+  }
+
+  /**
+   * The element used in place of an image when a node has no children.
+   */
+  private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";
+
+  private static Resources DEFAULT_RESOURCES;
+
+  /**
+   * The override styles used in {@link CellList}.
+   */
+  private static CellListResources cellListResource;
+
+  /**
+   * Get the {@link CellList.Resources} overrides.
+   */
+  private static CellListResources getCellListResources() {
+    if (cellListResource == null) {
+      cellListResource = GWT.create(CellListResources.class);
+    }
+    return cellListResource;
+  }
+
+  private static Resources getDefaultResources() {
+    if (DEFAULT_RESOURCES == null) {
+      DEFAULT_RESOURCES = GWT.create(Resources.class);
+    }
+    return DEFAULT_RESOURCES;
+  }
+
+  /**
+   * The animation used for scrolling.
+   */
+  private final ScrollAnimation animation = new ScrollAnimation();
+
+  /**
+   * The default width of new columns.
+   */
+  private int defaultWidth = 200;
+
+  /**
+   * The HTML used to generate the closed image.
+   */
+  private final String closedImageHtml;
+
+  /**
+   * The unique ID assigned to this tree widget.
+   */
+  private final String uniqueId = Document.get().createUniqueId();
+
+  /**
+   * A boolean indicating whether or not animations are enabled.
+   */
+  private boolean isAnimationEnabled;
+
+  /**
+   * The minimum width of new columns.
+   */
+  private int minWidth;
+
+  /**
+   * The maximum width of the open and closed images.
+   */
+  private final int imageWidth;
+
+  /**
+   * The HTML used to generate the open image.
+   */
+  private final String openImageHtml;
+
+  /**
+   * The styles used by this widget.
+   */
+  private final Style style;
+
+  /**
+   * The element used to maintain the scrollbar when columns are removed.
+   */
+  private Element scrollLock;
+
+  /**
+   * The visible {@link TreeNode}.
+   */
+  private final List<TreeNode<?>> treeNodes = new ArrayList<TreeNode<?>>();
+
+  /**
+   * The {@link TreeViewModel} that backs the tree.
+   */
+  private final TreeViewModel viewModel;
+
+  /**
+   * Construct a new {@link CellBrowser}.
+   * 
+   * @param <T> the type of data in the root node
+   * @param viewModel the {@link TreeViewModel} that backs the tree
+   * @param rootValue the hidden root value of the tree
+   */
+  public <T> CellBrowser(TreeViewModel viewModel, T rootValue) {
+    this(viewModel, rootValue, getDefaultResources());
+  }
+
+  /**
+   * Construct a new {@link CellBrowser} with the specified {@link Resources}.
+   * 
+   * @param <T> the type of data in the root node
+   * @param viewModel the {@link TreeViewModel} that backs the tree
+   * @param rootValue the hidden root value of the tree
+   * @param resources the {@link Resources} used for images
+   */
+  public <T> CellBrowser(TreeViewModel viewModel, T rootValue,
+      Resources resources) {
+    this.viewModel = viewModel;
+    this.style = resources.cellBrowserStyle();
+    this.style.ensureInjected();
+    initWidget(new SplitLayoutPanel());
+    getElement().getStyle().setOverflow(Overflow.AUTO);
+    setStyleName("gwt-SideBySideTreeView");
+
+    // Initialize the open and close images strings.
+    ImageResource treeOpen = resources.cellBrowserOpen();
+    ImageResource treeClosed = resources.cellBrowserClosed();
+    openImageHtml = getImageHtml(treeOpen);
+    closedImageHtml = getImageHtml(treeClosed);
+    imageWidth = Math.max(treeOpen.getWidth(), treeClosed.getWidth());
+    minWidth = imageWidth + 20;
+
+    // Add a placeholder to maintain the scroll width.
+    scrollLock = Document.get().createDivElement();
+    scrollLock.getStyle().setPosition(Position.ABSOLUTE);
+    scrollLock.getStyle().setVisibility(Visibility.HIDDEN);
+    scrollLock.getStyle().setZIndex(-32767);
+    scrollLock.getStyle().setBackgroundColor("red");
+    scrollLock.getStyle().setTop(0, Unit.PX);
+    scrollLock.getStyle().setLeft(0, Unit.PX);
+    scrollLock.getStyle().setHeight(1, Unit.PX);
+    scrollLock.getStyle().setWidth(1, Unit.PX);
+    getElement().appendChild(scrollLock);
+
+    // Associate the first ListView with the rootValue.
+    appendTreeNode(viewModel.getNodeInfo(rootValue));
+
+    // Catch scroll events.
+    sinkEvents(Event.ONSCROLL);
+  }
+
+  /**
+   * Get the default width of new columns.
+   * 
+   * @return the default width in pixels
+   */
+  public int getDefaultColumnWidth() {
+    return defaultWidth;
+  }
+
+  /**
+   * Get the minimum width of columns.
+   * 
+   * @return the minimum width in pixels
+   */
+  public int getMinimumColumnWidth() {
+    return minWidth;
+  }
+
+  public boolean isAnimationEnabled() {
+    return isAnimationEnabled;
+  }
+
+  @Override
+  public void onBrowserEvent(Event event) {
+    switch (DOM.eventGetType(event)) {
+      case Event.ONSCROLL:
+        // Shorten the scroll bar is possible.
+        adjustScrollLock();
+        break;
+    }
+    super.onBrowserEvent(event);
+  }
+
+  public void onResize() {
+    getSplitLayoutPanel().onResize();
+  }
+
+  public void setAnimationEnabled(boolean enable) {
+    this.isAnimationEnabled = enable;
+  }
+
+  /**
+   * Set the default width of new columns.
+   * 
+   * @param width the default width in pixels
+   */
+  public void setDefaultColumnWidth(int width) {
+    this.defaultWidth = width;
+  }
+
+  /**
+   * Set the minimum width of columns.
+   * 
+   * @param minWidth the minimum width in pixels
+   */
+  public void setMinimumColumnWidth(int minWidth) {
+    this.minWidth = minWidth;
+  }
+
+  /**
+   * Create a Pager to control the list view. The {@link ListView} must extend
+   * {@link Widget}.
+   * 
+   * @param <C> the item type in the list view
+   * @param listView the list view to add paging too
+   * @return the {@link Pager}
+   */
+  protected <C> Pager<C> createPager(PagingListView<C> listView) {
+    return new PageSizePager<C>(listView, listView.getPageSize());
+  }
+
+  /**
+   * Create a {@link PagingListView} that will display items. The
+   * {@link PagingListView} must extend {@link Widget}.
+   * 
+   * @param <C> the item type in the list view
+   * @param nodeInfo the node info with child data
+   * @param cell the cell to use in the list view
+   * @return the {@link ListView}
+   */
+  protected <C> PagingListView<C> createPagingListView(NodeInfo<C> nodeInfo,
+      Cell<C> cell) {
+    CellList<C> pagingListView = new CellList<C>(cell, getCellListResources());
+    pagingListView.setValueUpdater(nodeInfo.getValueUpdater());
+    return pagingListView;
+  }
+
+  /**
+   * Adjust the size of the scroll lock element based on the new position of the
+   * scroll bar.
+   */
+  private void adjustScrollLock() {
+    int scrollLeft = getElement().getScrollLeft();
+    if (scrollLeft > 0) {
+      int clientWidth = getElement().getClientWidth();
+      scrollLock.getStyle().setWidth(scrollLeft + clientWidth, Unit.PX);
+    } else {
+      scrollLock.getStyle().setWidth(1.0, Unit.PX);
+    }
+  }
+
+  /**
+   * Create a new {@link TreeNode} and append it to the end of the LayoutPanel.
+   * 
+   * @param <C> the data type of the children
+   * @param nodeInfo the info about the node
+   */
+  private <C> void appendTreeNode(final NodeInfo<C> nodeInfo) {
+    // Create the list view.
+    final int level = treeNodes.size();
+    final CellDecorator<C> cell = new CellDecorator<C>(nodeInfo, level);
+    final PagingListView<C> listView = createPagingListView(nodeInfo, cell);
+    assert (listView instanceof Widget) : "createPagingListView() must return a widget";
+
+    // Create a pager and wrap the components in a scrollable container.
+    ScrollPanel scrollable = new ScrollPanel();
+    final Pager<C> pager = createPager(listView);
+    if (pager != null) {
+      assert (pager instanceof Widget) : "createPager() must return a widget";
+      FlowPanel flowPanel = new FlowPanel();
+      flowPanel.add((Widget) listView);
+      flowPanel.add((Widget) pager);
+      scrollable.setWidget(flowPanel);
+    } else {
+      scrollable.setWidget((Widget) listView);
+    }
+    scrollable.setStyleName(style.column());
+    if (level == 0) {
+      scrollable.addStyleName(style.firstColumn());
+    }
+
+    // Create a delegate list view so we can trap data changes.
+    ListView<C> listViewDelegate = new ListView<C>() {
+      public Range getRange() {
+        return listView.getRange();
+      }
+
+      public void setData(int start, int length, List<C> values) {
+        // Trim to the current level if the open node no longer exists.
+        TreeNode<?> node = treeNodes.get(level);
+        Object openKey = node.getCell().openKey;
+        if (openKey != null) {
+          boolean stillExists = false;
+          ProvidesKey<C> keyProvider = nodeInfo.getProvidesKey();
+          for (C value : values) {
+            if (openKey.equals(keyProvider.getKey(value))) {
+              stillExists = true;
+              break;
+            }
+          }
+          if (!stillExists) {
+            trimToLevel(level);
+          }
+        }
+
+        // Refresh the list.
+        listView.setData(start, length, values);
+      }
+
+      public void setDataSize(int size, boolean isExact) {
+        listView.setDataSize(size, isExact);
+      }
+
+      public void setDelegate(Delegate<C> delegate) {
+        listView.setDelegate(delegate);
+      }
+
+      public void setSelectionModel(SelectionModel<? super C> selectionModel) {
+        listView.setSelectionModel(selectionModel);
+      }
+    };
+
+    // Create a TreeNode.
+    TreeNode<C> treeNode = new TreeNode<C>(nodeInfo, listViewDelegate, cell,
+        scrollable);
+    treeNodes.add(treeNode);
+
+    // Attach the view to the selection model and node info.
+    listView.setSelectionModel(nodeInfo.getSelectionModel());
+    nodeInfo.setView(listViewDelegate);
+
+    // Add the ListView to the LayoutPanel.
+    SplitLayoutPanel splitPanel = getSplitLayoutPanel();
+    splitPanel.insertWest(scrollable, defaultWidth, null);
+    splitPanel.setWidgetMinSize(scrollable, minWidth);
+    splitPanel.forceLayout();
+
+    // Scroll to the right.
+    animation.scrollToEnd();
+  }
+
+  /**
+   * Get the HTML representation of an image.
+   * 
+   * @param res the {@link ImageResource} to render as HTML
+   * @return the rendered HTML
+   */
+  private String getImageHtml(ImageResource res) {
+    // Add the position and dimensions.
+    StringBuilder sb = new StringBuilder();
+    sb.append("<div style=\"position:absolute;right:0px;top:0px;height:100%;");
+    sb.append("width:").append(res.getWidth()).append("px;");
+
+    // Add the background, vertically centered.
+    sb.append("background:url('").append(res.getURL()).append("') ");
+    sb.append("no-repeat scroll center center transparent;");
+
+    // Close the div and return.
+    sb.append("\"></div>");
+    return sb.toString();
+  }
+
+  /**
+   * Get the {@link SplitLayoutPanel} used to lay out the views.
+   * 
+   * @return the {@link SplitLayoutPanel}
+   */
+  private SplitLayoutPanel getSplitLayoutPanel() {
+    return (SplitLayoutPanel) getWidget();
+  }
+
+  /**
+   * Reduce the number of {@link ListView}s down to the specified level.
+   * 
+   * @param level the level to trim to
+   */
+  private void trimToLevel(int level) {
+    // Add a placeholder to maintain the same scroll width.
+    adjustScrollLock();
+
+    // Remove the listViews that are no longer needed.
+    int curLevel = treeNodes.size() - 1;
+    while (curLevel > level) {
+      TreeNode<?> removed = treeNodes.remove(curLevel);
+      removed.cleanup();
+      curLevel--;
+    }
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowserOverride.css b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowserOverride.css
new file mode 100644
index 0000000..3793a83
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellBrowserOverride.css
@@ -0,0 +1,15 @@
+/**
+ * The following overrides are used by CellBrowser to disable the styles used
+ * in CellList.
+ */
+.evenItem {
+  
+}
+
+.oddItem {
+  
+}
+
+.selectedItem {
+  
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTree.css b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTree.css
new file mode 100644
index 0000000..be6f2f9
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTree.css
@@ -0,0 +1,28 @@
+.emptyMessage {
+  padding-left: 16px;
+  font-style: italic;
+}
+
+.item {
+  padding: 4px 3px;
+}
+
+.itemImage {
+  top: 2px !important;
+}
+
+.openItem {
+}
+
+@sprite .selectedItem {
+  gwt-image: 'cellTreeSelectedBackground';
+  background-color: #628cd5;
+  color: white;
+  height: auto;
+  overflow: auto;
+}
+
+.showMoreButton {
+  padding-left: 16px;
+  outline: none;
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTree.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTree.java
new file mode 100644
index 0000000..7a53fc6
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTree.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.tree.client;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HasAnimation;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.view.client.TreeViewModel;
+
+import java.util.ArrayList;
+
+/**
+ * A view of a tree.
+ */
+public class CellTree extends Composite implements HasAnimation {
+
+  /**
+   * A node animation.
+   */
+  public abstract static class NodeAnimation extends Animation {
+
+    /**
+     * The default animation delay in milliseconds.
+     */
+    private static final int DEFAULT_ANIMATION_DURATION = 450;
+
+    /**
+     * The duration of the animation.
+     */
+    private int duration = DEFAULT_ANIMATION_DURATION;
+
+    NodeAnimation() {
+    }
+
+    public int getDuration() {
+      return duration;
+    }
+
+    public void setDuration(int duration) {
+      this.duration = duration;
+    }
+
+    /**
+     * Animate a tree node into its new state.
+     * 
+     * @param node the node to animate
+     * @param isAnimationEnabled true to animate
+     */
+    abstract void animate(CellTreeNodeView<?> node, boolean isAnimationEnabled);
+  }
+
+  /**
+   * A ClientBundle that provides images for this widget.
+   */
+  public static interface Resources extends ClientBundle {
+
+    /**
+     * An image indicating a closed branch.
+     */
+    ImageResource cellTreeClosedItem();
+
+    /**
+     * An image indicating that a node is loading.
+     */
+    ImageResource cellTreeLoading();
+
+    /**
+     * An image indicating an open branch.
+     */
+    ImageResource cellTreeOpenItem();
+
+    /**
+     * The background used for selected items.
+     */
+    @Source("../../list/client/cellListSelectedBackground.png")
+    @ImageOptions(repeatStyle = RepeatStyle.Horizontal)
+    ImageResource cellTreeSelectedBackground();
+
+    /**
+     * The styles used in this widget.
+     */
+    @Source("CellTree.css")
+    Style cellTreeStyle();
+  }
+
+  /**
+   * A {@link NodeAnimation} that reveals the contents of child nodes.
+   */
+  public static class RevealAnimation extends NodeAnimation {
+
+    /**
+     * Create a new {@link RevealAnimation}.
+     * 
+     * @return the new animation
+     */
+    public static RevealAnimation create() {
+      return new RevealAnimation();
+    }
+
+    /**
+     * The container that holds the content, includind the children.
+     */
+    Element contentContainer;
+
+    /**
+     * The target height when opening, the start height when closing.
+     */
+    int height;
+
+    /**
+     * True if the node is opening, false if closing.
+     */
+    boolean opening;
+
+    /**
+     * The container that holds the child container.
+     */
+    private Element animFrame;
+
+    /**
+     * The container that holds the children.
+     */
+    private Element childContainer;
+
+    /**
+     * Not instantiable.
+     */
+    private RevealAnimation() {
+    }
+
+    @Override
+    protected void onComplete() {
+      cleanup();
+    }
+
+    @Override
+    protected void onStart() {
+      if (opening) {
+        animFrame.getStyle().setHeight(1.0, Unit.PX);
+        animFrame.getStyle().clearDisplay();
+        height = contentContainer.getScrollHeight();
+      } else {
+        height = contentContainer.getOffsetHeight();
+      }
+    }
+
+    @Override
+    protected void onUpdate(double progress) {
+      if (opening) {
+        double curHeight = progress * height;
+        animFrame.getStyle().setHeight(curHeight, Unit.PX);
+      } else {
+        double curHeight = (1.0 - progress) * height;
+        animFrame.getStyle().setHeight(curHeight, Unit.PX);
+      }
+    }
+
+    /**
+     * Animate a {@link CellTreeNodeView} into its new state.
+     * 
+     * @param node the {@link CellTreeNodeView} to animate
+     * @param isAnimationEnabled true to animate
+     */
+    @Override
+    void animate(CellTreeNodeView<?> node, boolean isAnimationEnabled) {
+      // Cancel any pending animations.
+      cancel();
+
+      // Initialize the fields.
+      this.opening = node.isOpen();
+      animFrame = node.ensureAnimationFrame();
+      contentContainer = node.ensureContentContainer();
+      childContainer = node.ensureChildContainer();
+
+      if (isAnimationEnabled) {
+        // Animated.
+        int duration = getDuration();
+        int childCount = childContainer.getChildCount();
+        if (childCount < 4) {
+          // Reduce the duration if there are less than four items or it will
+          // look really slow.
+          duration = (int) ((childCount / 4.0) * duration);
+        }
+        run(duration);
+      } else {
+        // Non animated.
+        cleanup();
+      }
+    }
+
+    /**
+     * Put the node back into a clean state and clear fields.
+     */
+    private void cleanup() {
+      if (opening) {
+        animFrame.getStyle().clearDisplay();
+      } else {
+        animFrame.getStyle().setDisplay(Display.NONE);
+        childContainer.setInnerHTML("");
+      }
+      animFrame.getStyle().clearHeight();
+      this.contentContainer = null;
+      this.childContainer = null;
+      this.animFrame = null;
+    }
+  }
+
+  /**
+   * A {@link NodeAnimation} that slides children into view.
+   */
+  public static class SlideAnimation extends RevealAnimation {
+    /**
+     * Create a new {@link RevealAnimation}.
+     * 
+     * @return the new animation
+     */
+    public static SlideAnimation create() {
+      return new SlideAnimation();
+    }
+
+    /**
+     * Not instantiable.
+     */
+    private SlideAnimation() {
+    }
+
+    @Override
+    protected void onComplete() {
+      contentContainer.getStyle().clearPosition();
+      contentContainer.getStyle().clearTop();
+      contentContainer.getStyle().clearWidth();
+      super.onComplete();
+    }
+
+    @Override
+    protected void onStart() {
+      super.onStart();
+      if (opening) {
+        contentContainer.getStyle().setTop(-height, Unit.PX);
+      } else {
+        contentContainer.getStyle().setTop(0, Unit.PX);
+      }
+      contentContainer.getStyle().setPosition(Position.RELATIVE);
+    }
+
+    @Override
+    protected void onUpdate(double progress) {
+      super.onUpdate(progress);
+      if (opening) {
+        double curTop = (1.0 - progress) * -height;
+        contentContainer.getStyle().setTop(curTop, Unit.PX);
+      } else {
+        double curTop = progress * -height;
+        contentContainer.getStyle().setTop(curTop, Unit.PX);
+      }
+    }
+  }
+
+  /**
+   * Styles used by this widget.
+   */
+  public static interface Style extends CssResource {
+
+    /**
+     * Applied to the empty message.
+     */
+    String emptyMessage();
+
+    /**
+     * Applied to tree items.
+     */
+    String item();
+
+    /**
+     * Applied to open/close icon.
+     */
+    String itemImage();
+
+    /**
+     * Applied to open tree items.
+     */
+    String openItem();
+
+    /**
+     * Applied to selected tree items.
+     */
+    String selectedItem();
+
+    /**
+     * Applied to the show more button.
+     */
+    String showMoreButton();
+  }
+
+  /**
+   * The default number of children to show under a tree node.
+   */
+  private static final int DEFAULT_LIST_SIZE = 25;
+
+  private static Resources DEFAULT_RESOURCES;
+
+  private static Resources getDefaultResources() {
+    if (DEFAULT_RESOURCES == null) {
+      DEFAULT_RESOURCES = GWT.create(Resources.class);
+    }
+    return DEFAULT_RESOURCES;
+  }
+
+  /**
+   * The animation.
+   */
+  private NodeAnimation animation;
+
+  /**
+   * The HTML used to generate the closed image.
+   */
+  private final String closedImageHtml;
+
+  /**
+   * The default number of children to display under each node.
+   */
+  private int defaultNodeSize = DEFAULT_LIST_SIZE;
+
+  /**
+   * The maximum width of the open and closed images.
+   */
+  private final int imageWidth;
+
+  /**
+   * Indicates whether or not animations are enabled.
+   */
+  private boolean isAnimationEnabled;
+
+  /**
+   * The HTML used to generate the loading image.
+   */
+  private final String loadingImageHtml;
+
+  /**
+   * The HTML used to generate the open image.
+   */
+  private final String openImageHtml;
+
+  /**
+   * The hidden root node in the tree.
+   */
+  private final CellTreeNodeView<?> rootNode;
+
+  /**
+   * The styles used by this widget.
+   */
+  private final Style style;
+
+  /**
+   * The {@link TreeViewModel} that backs the tree.
+   */
+  private final TreeViewModel viewModel;
+
+  /**
+   * Construct a new {@link CellTree}.
+   * 
+   * @param <T> the type of data in the root node
+   * @param viewModel the {@link TreeViewModel} that backs the tree
+   * @param rootValue the hidden root value of the tree
+   */
+  public <T> CellTree(TreeViewModel viewModel, T rootValue) {
+    this(viewModel, rootValue, getDefaultResources());
+  }
+
+  /**
+   * Construct a new {@link CellTree}.
+   * 
+   * @param <T> the type of data in the root node
+   * @param viewModel the {@link TreeViewModel} that backs the tree
+   * @param rootValue the hidden root value of the tree
+   * @param resources the resources used to render the tree
+   */
+  public <T> CellTree(TreeViewModel viewModel, T rootValue,
+      Resources resources) {
+    this.viewModel = viewModel;
+    this.style = resources.cellTreeStyle();
+    this.style.ensureInjected();
+    initWidget(new SimplePanel());
+    setStyleName("gwt-StandardTreeView");
+
+    // Initialize the open and close images strings.
+    ImageResource treeOpen = resources.cellTreeOpenItem();
+    ImageResource treeClosed = resources.cellTreeClosedItem();
+    ImageResource treeLoading = resources.cellTreeLoading();
+    openImageHtml = getImageHtml(treeOpen);
+    closedImageHtml = getImageHtml(treeClosed);
+    loadingImageHtml = getImageHtml(treeLoading);
+    imageWidth = Math.max(Math.max(treeOpen.getWidth(), treeClosed.getWidth()),
+        treeLoading.getWidth());
+
+    // We use one animation for the entire tree.
+    setAnimation(SlideAnimation.create());
+
+    // Add event handlers.
+    sinkEvents(Event.ONCLICK | Event.ONCHANGE | Event.MOUSEEVENTS);
+
+    // Associate a view with the item.
+    CellTreeNodeView<T> root = new CellTreeNodeView<T>(this, null, null,
+        getElement(), rootValue);
+    rootNode = root;
+    root.setOpen(true);
+  }
+
+  /**
+   * Get the animation used to open and close nodes in this tree if animations
+   * are enabled.
+   * 
+   * @return the animation
+   * @see #isAnimationEnabled()
+   */
+  public NodeAnimation getAnimation() {
+    return animation;
+  }
+
+  /**
+   * Get the default maximum number of children to display under each tree node.
+   * 
+   * @return the default node size
+   */
+  public int getDefaultNodeSize() {
+    return defaultNodeSize;
+  }
+
+  public TreeViewModel getTreeViewModel() {
+    return viewModel;
+  }
+
+  public boolean isAnimationEnabled() {
+    return isAnimationEnabled;
+  }
+
+  @Override
+  public void onBrowserEvent(Event event) {
+    super.onBrowserEvent(event);
+
+    Element target = event.getEventTarget().cast();
+
+    ArrayList<Element> chain = new ArrayList<Element>();
+    collectElementChain(chain, getElement(), target);
+
+    CellTreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode);
+    if (nodeView != null && nodeView != rootNode) {
+      if ("click".equals(event.getType())) {
+        // Open the node when the open image is clicked.
+        Element showMoreElem = nodeView.getShowMoreElement();
+        if (nodeView.getImageElement().isOrHasChild(target)) {
+          nodeView.setOpen(!nodeView.isOpen());
+          return;
+        } else if (showMoreElem != null && showMoreElem.isOrHasChild(target)) {
+          nodeView.showMore();
+          return;
+        }
+      }
+
+      // Forward the event to the cell.
+      if (nodeView.getCellParent().isOrHasChild(target)) {
+        boolean consumesEvent = nodeView.fireEventToCell(event);
+        if (!consumesEvent && "click".equals(event.getType())) {
+          nodeView.select();
+        }
+      }
+    }
+  }
+
+  /**
+   * Set the animation used to open and close nodes in this tree. You must call
+   * {@link #setAnimationEnabled(boolean)} to enable or disable animation.
+   * 
+   * @param animation a {@link NodeAnimation}
+   * @see #setAnimationEnabled(boolean)
+   */
+  public void setAnimation(NodeAnimation animation) {
+    assert animation != null : "animation cannot be null";
+    this.animation = animation;
+  }
+
+  public void setAnimationEnabled(boolean enable) {
+    this.isAnimationEnabled = enable;
+    if (!enable && animation != null) {
+      animation.cancel();
+    }
+  }
+
+  /**
+   * Set the default number of children to display beneath each child node. If
+   * more nodes are available, a button will appear at the end of the list
+   * allowing the user to show more items. Changing this value will not affect
+   * tree nodes that are already open.
+   * 
+   * @param defaultNodeSize the max
+   */
+  public void setDefaultNodeSize(int defaultNodeSize) {
+    this.defaultNodeSize = defaultNodeSize;
+  }
+
+  /**
+   * @return the HTML to render the closed image.
+   */
+  String getClosedImageHtml() {
+    return closedImageHtml;
+  }
+
+  /**
+   * Get the width required for the images.
+   * 
+   * @return the maximum width required for images.
+   */
+  int getImageWidth() {
+    return imageWidth;
+  }
+
+  /**
+   * @return the HTML to render the loading image.
+   */
+  String getLoadingImageHtml() {
+    return loadingImageHtml;
+  }
+
+  /**
+   * @return the HTML to render the open image.
+   */
+  String getOpenImageHtml() {
+    return openImageHtml;
+  }
+
+  /**
+   * @return the Style used by the tree
+   */
+  Style getStyle() {
+    return style;
+  }
+
+  /**
+   * Animate the current state of a {@link CellTreeNodeView} in this tree.
+   * 
+   * @param node the node to animate
+   */
+  void maybeAnimateTreeNode(CellTreeNodeView<?> node) {
+    if (animation != null) {
+      animation.animate(node, node.consumeAnimate() && isAnimationEnabled());
+    }
+  }
+
+  /**
+   * Collects parents going up the element tree, terminated at the tree root.
+   */
+  private void collectElementChain(ArrayList<Element> chain, Element hRoot,
+      Element hElem) {
+    if ((hElem == null) || (hElem == hRoot)) {
+      return;
+    }
+
+    collectElementChain(chain, hRoot, hElem.getParentElement());
+    chain.add(hElem);
+  }
+
+  private CellTreeNodeView<?> findItemByChain(ArrayList<Element> chain,
+      int idx, CellTreeNodeView<?> parent) {
+    if (idx == chain.size()) {
+      return parent;
+    }
+
+    Element hCurElem = chain.get(idx);
+    for (int i = 0, n = parent.getChildCount(); i < n; ++i) {
+      CellTreeNodeView<?> child = parent.getChildNode(i);
+      if (child.getElement() == hCurElem) {
+        CellTreeNodeView<?> retItem = findItemByChain(chain, idx + 1, child);
+        if (retItem == null) {
+          return child;
+        }
+        return retItem;
+      }
+    }
+
+    return findItemByChain(chain, idx + 1, parent);
+  }
+
+  /**
+   * Get the HTML representation of an image.
+   * 
+   * @param res the {@link ImageResource} to render as HTML
+   * @return the rendered HTML
+   */
+  private String getImageHtml(ImageResource res) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("<div class='").append(style.itemImage()).append("' ");
+
+    // Add the position and dimensions.
+    sb.append("style=\"position:absolute;left:0px;top:0px;");
+    sb.append("height:").append(res.getHeight()).append("px;");
+    sb.append("width:").append(res.getWidth()).append("px;");
+
+    // Add the background, vertically centered.
+    sb.append("background:url('").append(res.getURL()).append("') ");
+    sb.append("no-repeat scroll center center transparent;");
+
+    // Close the div and return.
+    sb.append("\"></div>");
+    return sb.toString();
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTreeNodeView.java
similarity index 69%
rename from bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java
rename to bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTreeNodeView.java
index 46399e6..1b35306 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/CellTreeNodeView.java
@@ -15,13 +15,9 @@
  */
 package com.google.gwt.bikeshed.tree.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.client.impl.SimpleCellListImpl;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.tree.client.TreeViewModel.NodeInfo;
+import com.google.gwt.bikeshed.list.client.impl.CellListImpl;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
@@ -29,6 +25,12 @@
 import com.google.gwt.dom.client.Style.Overflow;
 import com.google.gwt.dom.client.Style.Position;
 import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.view.client.TreeViewModel;
+import com.google.gwt.view.client.PagingListView;
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.TreeViewModel.NodeInfo;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -40,7 +42,7 @@
  * 
  * @param <T> the type that this view contains
  */
-class StandardTreeNodeView<T> extends UIObject {
+class CellTreeNodeView<T> extends UIObject {
 
   /**
    * The element used in place of an image when a node has no children.
@@ -48,11 +50,6 @@
   private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";
 
   /**
-   * Style name applied to selected rows.
-   */
-  private static final String STYLENNAME_SELECTED = "gwt-tree-selectedItem";
-
-  /**
    * Returns the element that parents the cell contents of the node.
    * 
    * @param nodeElem the element that represents the node
@@ -77,38 +74,43 @@
   }
 
   /**
-   * The {@link ListView} used to show children.
+   * The {@link com.google.gwt.view.client.ListView ListView} used to
+   * show children.
    * 
    * @param <C> the child item type
    */
-  private static class NodeListView<C> implements ListView<C> {
+  private static class NodeListView<C> implements PagingListView<C> {
 
-    private final SimpleCellListImpl<C> impl;
-    private StandardTreeNodeView<?> nodeView;
-    private Map<Object, StandardTreeNodeView<?>> savedViews;
+    private final int defaultPageSize;
+    private final CellListImpl<C> impl;
+    private CellTreeNodeView<?> nodeView;
+    private Map<Object, CellTreeNodeView<?>> savedViews;
 
     public NodeListView(final NodeInfo<C> nodeInfo,
-        final StandardTreeNodeView<?> nodeView) {
+        final CellTreeNodeView<?> nodeView, int pageSize) {
+      this.defaultPageSize = pageSize;
       this.nodeView = nodeView;
 
-      impl = new SimpleCellListImpl<C>(this, nodeInfo.getCell(), 100, 50,
-          nodeView.ensureChildContainer(), nodeView.emptyMessageElem,
-          nodeView.showMoreElem, nodeView.showFewerElem) {
+      final Cell<C> cell = nodeInfo.getCell();
+      impl = new CellListImpl<C>(this, pageSize,
+          nodeView.ensureChildContainer()) {
 
         @Override
         public void setData(List<C> values, int start) {
+          nodeView.updateImage(false);
+
           // Ensure that we have a children array.
           if (nodeView.children == null) {
-            nodeView.children = new ArrayList<StandardTreeNodeView<?>>();
+            nodeView.children = new ArrayList<CellTreeNodeView<?>>();
           }
 
           // Construct a map of former child views based on their value keys.
           int len = values.size();
           int end = start + len;
           int childCount = nodeView.getChildCount();
-          Map<Object, StandardTreeNodeView<?>> openNodes = new HashMap<Object, StandardTreeNodeView<?>>();
+          Map<Object, CellTreeNodeView<?>> openNodes = new HashMap<Object, CellTreeNodeView<?>>();
           for (int i = start; i < end && i < childCount; i++) {
-            StandardTreeNodeView<?> child = nodeView.getChildNode(i);
+            CellTreeNodeView<?> child = nodeView.getChildNode(i);
             // Ignore child nodes that are closed.
             if (child.isOpen()) {
               openNodes.put(child.getValueKey(), child);
@@ -122,12 +124,12 @@
 
           // Trim the saved views down to the children that still exists.
           ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
-          savedViews = new HashMap<Object, StandardTreeNodeView<?>>();
+          savedViews = new HashMap<Object, CellTreeNodeView<?>>();
           for (C childValue : values) {
             // Remove any child elements that correspond to prior children
             // so the call to setInnerHtml will not destroy them
             Object key = providesKey.getKey(childValue);
-            StandardTreeNodeView<?> savedView = openNodes.remove(key);
+            CellTreeNodeView<?> savedView = openNodes.remove(key);
             if (savedView != null) {
               savedView.ensureAnimationFrame().removeFromParent();
               savedViews.put(key, savedView);
@@ -141,9 +143,9 @@
           Element childElem = nodeView.ensureChildContainer().getFirstChildElement();
           for (int i = start; i < end; i++) {
             C childValue = values.get(i - start);
-            StandardTreeNodeView<C> child = nodeView.createTreeNodeView(
-                nodeInfo, childElem, childValue, null);
-            StandardTreeNodeView<?> savedChild = savedViews.remove(providesKey.getKey(childValue));
+            CellTreeNodeView<C> child = nodeView.createTreeNodeView(nodeInfo,
+                childElem, childValue, null);
+            CellTreeNodeView<?> savedChild = savedViews.remove(providesKey.getKey(childValue));
             // Copy the saved child's state into the new child
             if (savedChild != null) {
               child.animationFrame = savedChild.animationFrame;
@@ -154,7 +156,6 @@
               child.nodeInfo = savedChild.nodeInfo;
               child.nodeInfoLoaded = savedChild.nodeInfoLoaded;
               child.open = savedChild.open;
-              child.showFewerElem = savedChild.showFewerElem;
               child.showMoreElem = savedChild.showMoreElem;
 
               // Swap the node view in the child. We reuse the same NodeListView
@@ -188,28 +189,40 @@
         }
 
         @Override
+        protected boolean dependsOnSelection() {
+          return cell.dependsOnSelection();
+        }
+
+        @Override
         protected void emitHtml(StringBuilder sb, List<C> values, int start,
-            Cell<C, Void> cell, SelectionModel<? super C> selectionModel) {
+            SelectionModel<? super C> selectionModel) {
+          String selectedStyle = nodeView.tree.getStyle().selectedItem();
+          String itemStyle = nodeView.tree.getStyle().item();
+          String openStyle = nodeView.tree.getStyle().openItem();
+
           ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
           TreeViewModel model = nodeView.tree.getTreeViewModel();
           int imageWidth = nodeView.tree.getImageWidth();
           for (C value : values) {
             Object key = providesKey.getKey(value);
-            sb.append("<div style=\"position:relative;padding-left:");
-            sb.append(imageWidth);
-            sb.append("px;\">");
-            if (savedViews.get(key) != null) {
+            boolean isOpen = savedViews.containsKey(key);
+            sb.append("<div style='position:relative;padding-left:");
+            sb.append(imageWidth).append("px'>");
+            if (isOpen) {
               sb.append(nodeView.tree.getOpenImageHtml());
             } else if (model.isLeaf(value)) {
               sb.append(LEAF_IMAGE);
             } else {
               sb.append(nodeView.tree.getClosedImageHtml());
             }
-            if (selectionModel != null && selectionModel.isSelected(value)) {
-              sb.append("<div class='").append(STYLENNAME_SELECTED).append("'>");
-            } else {
-              sb.append("<div>");
+            sb.append("<div class='").append(itemStyle);
+            if (isOpen) {
+              sb.append(" ").append(openStyle);
             }
+            if (selectionModel != null && selectionModel.isSelected(value)) {
+              sb.append(" ").append(selectedStyle);
+            }
+            sb.append("'>");
             cell.render(value, null, sb);
             sb.append("</div></div>");
           }
@@ -217,23 +230,55 @@
 
         @Override
         protected void removeLastItem() {
-          StandardTreeNodeView<?> child = nodeView.children.remove(nodeView.children.size() - 1);
+          CellTreeNodeView<?> child = nodeView.children.remove(nodeView.children.size() - 1);
           child.cleanup();
           super.removeLastItem();
         }
 
         @Override
         protected void setSelected(Element elem, boolean selected) {
-          setStyleName(getCellParent(elem), STYLENNAME_SELECTED, selected);
+          setStyleName(getCellParent(elem),
+              nodeView.tree.getStyle().selectedItem(), selected);
         }
       };
+
+      // Use a pager to update buttons.
+      impl.setPager(new Pager<C>() {
+        public void onRangeOrSizeChanged(PagingListView<C> listView) {
+          // Assumes a page start of 0.
+          int dataSize = impl.getDataSize();
+          showOrHide(nodeView.showMoreElem, dataSize > impl.getPageSize());
+          if (dataSize == 0) {
+            showOrHide(nodeView.emptyMessageElem, true);
+            nodeView.updateImage(false);
+          } else {
+            showOrHide(nodeView.emptyMessageElem, false);
+          }
+        }
+      });
     }
 
     /**
      * Cleanup this node view.
      */
     public void cleanup() {
-      impl.setSelectionModel(null);
+      impl.setSelectionModel(null, false);
+    }
+
+    public int getDataSize() {
+      return impl.getDataSize();
+    }
+
+    public int getDefaultPageSize() {
+      return defaultPageSize;
+    }
+
+    public int getPageSize() {
+      return impl.getPageSize();
+    }
+
+    public int getPageStart() {
+      return impl.getPageStart();
     }
 
     public Range getRange() {
@@ -252,16 +297,28 @@
       impl.setDelegate(delegate);
     }
 
+    public void setPager(Pager<C> pager) {
+      impl.setPager(pager);
+    }
+
+    public void setPageSize(int pageSize) {
+      impl.setPageSize(pageSize);
+    }
+
+    public void setPageStart(int pageStart) {
+      impl.setPageStart(pageStart);
+    }
+
     public void setSelectionModel(final SelectionModel<? super C> selectionModel) {
-      impl.setSelectionModel(selectionModel);
+      impl.setSelectionModel(selectionModel, true);
     }
 
     /**
-     * Assign this {@link ListView} to a new {@link StandardTreeNodeView}.
+     * Assign this {@link PagingListView} to a new {@link CellTreeNodeView}.
      * 
      * @param nodeView the new node view
      */
-    private void setNodeView(StandardTreeNodeView<?> nodeView) {
+    private void setNodeView(CellTreeNodeView<?> nodeView) {
       this.nodeView.listView = null;
       this.nodeView = nodeView;
       nodeView.listView = this;
@@ -288,7 +345,7 @@
   /**
    * A list of child views.
    */
-  private List<StandardTreeNodeView<?>> children;
+  private List<CellTreeNodeView<?>> children;
 
   /**
    * A reference to the element that contains all content. Parent of the
@@ -322,9 +379,9 @@
   private boolean open;
 
   /**
-   * The parent {@link StandardTreeNodeView}.
+   * The parent {@link CellTreeNodeView}.
    */
-  private final StandardTreeNodeView<?> parentNode;
+  private final CellTreeNodeView<?> parentNode;
 
   /**
    * The {@link NodeInfo} of the parent node.
@@ -332,19 +389,14 @@
   private final NodeInfo<T> parentNodeInfo;
 
   /**
-   * The element used to display less children.
-   */
-  private Element showFewerElem;
-
-  /**
    * The element used to display more children.
    */
-  private Element showMoreElem;
+  private AnchorElement showMoreElem;
 
   /**
-   * The {@link TreeView} that this node belongs to.
+   * The {@link CellTree} that this node belongs to.
    */
-  private final StandardTreeView tree;
+  private final CellTree tree;
 
   /**
    * This node's value.
@@ -352,17 +404,16 @@
   private T value;
 
   /**
-   * Construct a {@link StandardTreeNodeView}.
+   * Construct a {@link CellTreeNodeView}.
    * 
-   * @param tree the parent {@link StandardTreeNodeView}
-   * @param parent the parent {@link StandardTreeNodeView}
+   * @param tree the parent {@link CellTreeNodeView}
+   * @param parent the parent {@link CellTreeNodeView}
    * @param parentNodeInfo the {@link NodeInfo} of the parent
-   * @param elem the outer element of this {@link StandardTreeNodeView}
+   * @param elem the outer element of this {@link CellTreeNodeView}
    * @param value the value of this node
    */
-  StandardTreeNodeView(final StandardTreeView tree,
-      final StandardTreeNodeView<?> parent, NodeInfo<T> parentNodeInfo,
-      Element elem, T value) {
+  CellTreeNodeView(final CellTree tree, final CellTreeNodeView<?> parent,
+      NodeInfo<T> parentNodeInfo, Element elem, T value) {
     this.tree = tree;
     this.parentNode = parent;
     this.parentNodeInfo = parentNodeInfo;
@@ -374,7 +425,7 @@
     return children == null ? 0 : children.size();
   }
 
-  public StandardTreeNodeView<?> getChildNode(int childIndex) {
+  public CellTreeNodeView<?> getChildNode(int childIndex) {
     return children.get(childIndex);
   }
 
@@ -419,20 +470,24 @@
       // If we don't have any nodeInfo, we must be a leaf node.
       if (nodeInfo != null) {
         // Add a loading message.
-        ensureChildContainer().setInnerHTML(tree.getLoadingHtml());
-        showOrHide(showFewerElem, false);
+        ensureChildContainer();
         showOrHide(showMoreElem, false);
         showOrHide(emptyMessageElem, false);
+        if (!isRootNode()) {
+          setStyleName(getCellParent(), tree.getStyle().openItem(), true);
+        }
         ensureAnimationFrame().getStyle().setProperty("display", "");
+        updateImage(true);
         onOpen(nodeInfo);
       }
     } else {
+      if (!isRootNode()) {
+        setStyleName(getCellParent(), tree.getStyle().openItem(), false);
+      }
       cleanup();
       tree.maybeAnimateTreeNode(this);
+      updateImage(false);
     }
-
-    // Update the image.
-    updateImage();
   }
 
   /**
@@ -448,7 +503,7 @@
 
     // Recursively kill children.
     if (children != null) {
-      for (StandardTreeNodeView<?> child : children) {
+      for (CellTreeNodeView<?> child : children) {
         child.cleanup();
       }
       children = null;
@@ -472,14 +527,13 @@
    * @param viewData view data associated with the node
    * @return a TreeNodeView of suitable type
    */
-  protected <C> StandardTreeNodeView<C> createTreeNodeView(
-      NodeInfo<C> nodeInfo, Element childElem, C childValue, Void viewData) {
-    return new StandardTreeNodeView<C>(tree, this, nodeInfo, childElem,
-        childValue);
+  protected <C> CellTreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo,
+      Element childElem, C childValue, Object viewData) {
+    return new CellTreeNodeView<C>(tree, this, nodeInfo, childElem, childValue);
   }
 
   /**
-   * Fire an event to the {@link com.google.gwt.bikeshed.cells.client.Cell}.
+   * Fire an event to the {@link com.google.gwt.cell.client.AbstractCell}.
    * 
    * @param event the native event
    * @return true if the cell consumes the event, false if not
@@ -487,7 +541,7 @@
   protected boolean fireEventToCell(NativeEvent event) {
     if (parentNodeInfo != null) {
       Element cellParent = getCellParent();
-      Cell<T, Void> parentCell = parentNodeInfo.getCell();
+      Cell<T> parentCell = parentNodeInfo.getCell();
       parentCell.onBrowserEvent(cellParent, value, null, event,
           parentNodeInfo.getValueUpdater());
       return parentCell.consumesEvents();
@@ -527,35 +581,14 @@
    * @param <C> the child data type of the node
    */
   protected <C> void onOpen(final NodeInfo<C> nodeInfo) {
-    NodeListView<C> view = new NodeListView<C>(nodeInfo, this);
+    NodeListView<C> view = new NodeListView<C>(nodeInfo, this,
+        tree.getDefaultNodeSize());
     listView = view;
     view.setSelectionModel(nodeInfo.getSelectionModel());
     nodeInfo.setView(view);
   }
 
   /**
-   * Update the image based on the current state.
-   */
-  protected void updateImage() {
-    // Early out if this is a root node.
-    if (parentNode == null) {
-      return;
-    }
-
-    // Replace the image element with a new one.
-    String html = open ? tree.getOpenImageHtml() : tree.getClosedImageHtml();
-    if (nodeInfoLoaded && nodeInfo == null) {
-      html = LEAF_IMAGE;
-    }
-    Element tmp = Document.get().createDivElement();
-    tmp.setInnerHTML(html);
-    Element imageElem = tmp.getFirstChildElement();
-
-    Element oldImg = getImageElement();
-    oldImg.getParentElement().replaceChild(imageElem, oldImg);
-  }
-
-  /**
    * Ensure that the animation frame exists and return it.
    * 
    * @return the animation frame
@@ -594,37 +627,72 @@
       contentContainer = Document.get().createDivElement();
       ensureAnimationFrame().appendChild(contentContainer);
 
+      // TODO(jlabanca): I18N no data string.
       emptyMessageElem = Document.get().createDivElement();
-      emptyMessageElem.setInnerHTML("<i>no data</i>");
+      emptyMessageElem.setInnerHTML("no data");
+      setStyleName(emptyMessageElem, tree.getStyle().emptyMessage(), true);
       showOrHide(emptyMessageElem, false);
       contentContainer.appendChild(emptyMessageElem);
 
-      showMoreElem = Document.get().createPushButtonElement();
+      showMoreElem = Document.get().createAnchorElement();
+      showMoreElem.setHref("javascript:;");
       showMoreElem.setInnerText("Show more");
+      setStyleName(showMoreElem, tree.getStyle().showMoreButton(), true);
       showOrHide(showMoreElem, false);
       contentContainer.appendChild(showMoreElem);
-
-      showFewerElem = Document.get().createPushButtonElement();
-      showFewerElem.setInnerText("Show fewer");
-      showOrHide(showFewerElem, false);
-      contentContainer.appendChild(showFewerElem);
     }
     return contentContainer;
   }
 
-  Element getShowFewerElement() {
-    return showFewerElem;
-  }
-
   Element getShowMoreElement() {
     return showMoreElem;
   }
 
   void showFewer() {
-    listView.impl.showFewer();
+    int defaultPageSize = listView.getDefaultPageSize();
+    int maxSize = Math.max(defaultPageSize, listView.impl.getPageSize()
+        - defaultPageSize);
+    listView.impl.setPageSize(maxSize);
   }
 
   void showMore() {
-    listView.impl.showMore();
+    listView.impl.setPageSize(listView.impl.getPageSize()
+        + listView.getDefaultPageSize());
+  }
+
+  /**
+   * Check if this node is a root node.
+   * 
+   * @return true if a root node
+   */
+  private boolean isRootNode() {
+    return parentNode == null;
+  }
+
+  /**
+   * Update the image based on the current state.
+   * 
+   * @param isLoading true if still loading data
+   */
+  private void updateImage(boolean isLoading) {
+    // Early out if this is a root node.
+    if (isRootNode()) {
+      return;
+    }
+
+    // Replace the image element with a new one.
+    String html = tree.getClosedImageHtml();
+    if (open) {
+      html = isLoading ? tree.getLoadingImageHtml() : tree.getOpenImageHtml();
+    }
+    if (nodeInfoLoaded && nodeInfo == null) {
+      html = LEAF_IMAGE;
+    }
+    Element tmp = Document.get().createDivElement();
+    tmp.setInnerHTML(html);
+    Element imageElem = tmp.getFirstChildElement();
+
+    Element oldImg = getImageElement();
+    oldImg.getParentElement().replaceChild(imageElem, oldImg);
   }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
deleted file mode 100644
index 500c007..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
+++ /dev/null
@@ -1,629 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.tree.client;
-
-import com.google.gwt.animation.client.Animation;
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.client.SimpleCellList;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.tree.client.TreeViewModel.NodeInfo;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.Style.Visibility;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
-import com.google.gwt.user.client.ui.HasAnimation;
-import com.google.gwt.user.client.ui.ProvidesResize;
-import com.google.gwt.user.client.ui.RequiresResize;
-import com.google.gwt.user.client.ui.ScrollPanel;
-import com.google.gwt.user.client.ui.SplitLayoutPanel;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A view of a tree.
- */
-public class SideBySideTreeView extends TreeView implements ProvidesResize,
-    RequiresResize, HasAnimation {
-
-  /**
-   * The element used in place of an image when a node has no children.
-   */
-  private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";
-
-  /**
-   * The style name assigned to each column.
-   */
-  private static final String STYLENAME_COLUMN = "gwt-sstree-column";
-
-  /**
-   * The style name assigned to each column.
-   */
-  private static final String STYLENAME_OPEN = "gwt-sstree-openItem";
-
-  /**
-   * The prefix of the ID assigned to open cells.
-   */
-  private static final String ID_PREFIX_OPEN = "__gwt-sstree-open-";
-
-  /**
-   * The animation used to scroll to the newly added list view.
-   */
-  private class ScrollAnimation extends Animation {
-
-    /**
-     * The starting scroll position.
-     */
-    private int startScrollLeft;
-
-    /**
-     * The ending scroll position.
-     */
-    private int targetScrollLeft;
-
-    @Override
-    protected void onComplete() {
-      getElement().setScrollLeft(targetScrollLeft);
-    }
-
-    @Override
-    protected void onUpdate(double progress) {
-      int diff = targetScrollLeft - startScrollLeft;
-      getElement().setScrollLeft(startScrollLeft + (int) (diff * progress));
-    }
-
-    void scrollToEnd() {
-      Element elem = getElement();
-      targetScrollLeft = elem.getScrollWidth() - elem.getClientWidth();
-
-      if (isAnimationEnabled()) {
-        // Animate the scrolling.
-        startScrollLeft = elem.getScrollLeft();
-        run(250);
-      } else {
-        // Scroll instantly.
-        onComplete();
-      }
-    }
-  }
-
-  /**
-   * A wrapper around a cell that adds an open button.
-   * 
-   * @param <C> the data type of the cell
-   */
-  private class CellDecorator<C> extends Cell<C, Void> {
-
-    /**
-     * The cell used to render the inner contents.
-     */
-    private final Cell<C, Void> cell;
-
-    /**
-     * The level of this list view.
-     */
-    private final int level;
-
-    /**
-     * The key of the currently open item.
-     */
-    private Object openKey;
-
-    /**
-     * The key provider for the node.
-     */
-    private final ProvidesKey<C> providesKey;
-
-    /**
-     * Construct a new {@link CellDecorator}.
-     * 
-     * @param nodeInfo the {@link NodeInfo} associated with the cell
-     * @param level the level of items rendered by this decorator
-     */
-    public CellDecorator(NodeInfo<C> nodeInfo, int level) {
-      this.cell = nodeInfo.getCell();
-      this.level = level;
-      this.providesKey = nodeInfo.getProvidesKey();
-    }
-
-    @Override
-    public boolean consumesEvents() {
-      return cell.consumesEvents();
-    }
-
-    @Override
-    public boolean dependsOnSelection() {
-      return cell.dependsOnSelection();
-    }
-
-    public Object getOpenKey() {
-      return openKey;
-    }
-
-    @Override
-    public Void onBrowserEvent(Element parent, C value, Void viewData,
-        NativeEvent event, ValueUpdater<C, Void> valueUpdater) {
-      Element target = event.getEventTarget().cast();
-      if (getImageElement(parent).isOrHasChild(target)) {
-        if (Event.getTypeInt(event.getType()) == Event.ONMOUSEDOWN) {
-          trimToLevel(level);
-
-          // Remove style from currently open item.
-          Element curOpenItem = Document.get().getElementById(getOpenId());
-          if (curOpenItem != null) {
-            replaceImageElement(curOpenItem.getParentElement(), false);
-          }
-
-          // Save the key of the new open item and update the Element.
-          openKey = providesKey.getKey(value);
-          replaceImageElement(parent, true);
-
-          // Add a tree node for the next level.
-          appendTreeNode(getTreeViewModel().getNodeInfo(value));
-        }
-        return viewData;
-      } else {
-        return cell.onBrowserEvent(getCellParent(parent), value, viewData,
-            event, valueUpdater);
-      }
-    }
-
-    @Override
-    public void render(C value, Void viewData, StringBuilder sb) {
-      boolean isOpen = (openKey == null) ? false
-          : openKey.equals(providesKey.getKey(value));
-      int imageWidth = getImageWidth();
-      sb.append("<div style='position:relative;padding-right:");
-      sb.append(imageWidth);
-      sb.append("px;'");
-      if (isOpen) {
-        sb.append(" class='").append(STYLENAME_OPEN).append("'");
-        sb.append(" id='").append(getOpenId()).append("'");
-      }
-      sb.append(">");
-      if (isOpen) {
-        sb.append(getOpenImageHtml());
-      } else if (getTreeViewModel().isLeaf(value)) {
-        sb.append(LEAF_IMAGE);
-      } else {
-        sb.append(getClosedImageHtml());
-      }
-      sb.append("<div>");
-      cell.render(value, viewData, sb);
-      sb.append("</div></div>");
-    }
-
-    @Override
-    public void setValue(Element parent, C value, Void viewData) {
-      cell.setValue(getCellParent(parent), value, viewData);
-    }
-
-    /**
-     * Get the parent element of the decorated cell.
-     * 
-     * @param parent the parent of this cell
-     * @return the decorated cell's parent
-     */
-    private Element getCellParent(Element parent) {
-      return parent.getFirstChildElement().getChild(1).cast();
-    }
-
-    /**
-     * Get the image element of the decorated cell.
-     * 
-     * @param parent the parent of this cell
-     * @return the image element
-     */
-    private Element getImageElement(Element parent) {
-      return parent.getFirstChildElement().getFirstChildElement();
-    }
-
-    /**
-     * Get the ID of the open element.
-     * 
-     * @return the ID
-     */
-    private String getOpenId() {
-      return ID_PREFIX_OPEN + level + "-" + uniqueId;
-    }
-
-    /**
-     * Replace the image element of a cell.
-     * 
-     * @param parent the parent element of the cell
-     * @param open true if open, false if closed
-     */
-    private void replaceImageElement(Element parent, boolean open) {
-      // Update the style name and ID.
-      Element wrapper = parent.getFirstChildElement();
-      if (open) {
-        wrapper.addClassName(STYLENAME_OPEN);
-        wrapper.setId(getOpenId());
-      } else {
-        wrapper.removeClassName(STYLENAME_OPEN);
-        wrapper.setId("");
-      }
-
-      // Replace the image element.
-      String html = open ? getOpenImageHtml() : getClosedImageHtml();
-      Element tmp = Document.get().createDivElement();
-      tmp.setInnerHTML(html);
-      Element imageElem = tmp.getFirstChildElement();
-      Element oldImg = getImageElement(parent);
-      wrapper.replaceChild(imageElem, oldImg);
-    }
-  }
-
-  /**
-   * A node in the tree.
-   * 
-   * @param <C> the data type of the children of the node
-   */
-  private class TreeNode<C> {
-    private CellDecorator<C> cell;
-    private ListView<C> listView;
-    private NodeInfo<C> nodeInfo;
-    private Widget widget;
-
-    /**
-     * Construct a new {@link TreeNode}.
-     * 
-     * @param nodeInfo the nodeInfo for the children nodes
-     * @param listView the list view assocated with the node
-     * @param widget the widget that represents the list view
-     */
-    public TreeNode(NodeInfo<C> nodeInfo, ListView<C> listView,
-        CellDecorator<C> cell, Widget widget) {
-      this.cell = cell;
-      this.listView = listView;
-      this.nodeInfo = nodeInfo;
-      this.widget = widget;
-    }
-
-    /**
-     * Get the {@link CellDecorator} used to render the node.
-     * 
-     * @return the cell decorator
-     */
-    public CellDecorator<C> getCell() {
-      return cell;
-    }
-
-    /**
-     * Get the widget that represents this {@link TreeNode}.
-     * 
-     * @return the widget
-     */
-    public Widget getWidget() {
-      return widget;
-    }
-
-    /**
-     * Unregister the list view and remove it from the widget.
-     */
-    void cleanup() {
-      listView.setSelectionModel(null);
-      nodeInfo.unsetView();
-      getSplitLayoutPanel().remove(widget);
-    }
-  }
-
-  /**
-   * The counter used to assigned unique IDs.
-   */
-  private static int NEXT_ID = 0;
-
-  /**
-   * The animation used for scrolling.
-   */
-  private final ScrollAnimation animation = new ScrollAnimation();
-
-  /**
-   * The default width of new columns.
-   */
-  private int defaultWidth = 200;
-
-  /**
-   * The HTML used to generate the closed image.
-   */
-  private String closedImageHtml;
-
-  /**
-   * The unique ID assigned to this tree view.
-   */
-  private final int uniqueId = NEXT_ID++;
-
-  /**
-   * A boolean indicating whether or not animations are enabled.
-   */
-  private boolean isAnimationEnabled;
-
-  /**
-   * The minimum width of new columns.
-   */
-  private int minWidth = getImageWidth() + 20;
-
-  /**
-   * The HTML used to generate the open image.
-   */
-  private String openImageHtml;
-
-  /**
-   * The element used to maintain the scrollbar when columns are removed.
-   */
-  private Element scrollLock;
-
-  /**
-   * The visible {@link TreeNode}.
-   */
-  private List<TreeNode<?>> treeNodes = new ArrayList<TreeNode<?>>();
-
-  /**
-   * Construct a new {@link TreeView}.
-   * 
-   * @param <T> the type of data in the root node
-   * @param viewModel the {@link TreeViewModel} that backs the tree
-   * @param rootValue the hidden root value of the tree
-   */
-  public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue) {
-    super(viewModel, new SplitLayoutPanel());
-    getElement().getStyle().setOverflow(Overflow.AUTO);
-    setStyleName("gwt-SideBySideTreeView");
-
-    // Add a placeholder to maintain the scroll width.
-    scrollLock = Document.get().createDivElement();
-    scrollLock.getStyle().setPosition(Position.ABSOLUTE);
-    scrollLock.getStyle().setVisibility(Visibility.HIDDEN);
-    scrollLock.getStyle().setZIndex(-32767);
-    scrollLock.getStyle().setBackgroundColor("red");
-    scrollLock.getStyle().setTop(0, Unit.PX);
-    scrollLock.getStyle().setLeft(0, Unit.PX);
-    scrollLock.getStyle().setHeight(1, Unit.PX);
-    scrollLock.getStyle().setWidth(1, Unit.PX);
-    getElement().appendChild(scrollLock);
-
-    // Associate the first ListView with the rootValue.
-    appendTreeNode(viewModel.getNodeInfo(rootValue));
-
-    // Catch scroll events.
-    sinkEvents(Event.ONSCROLL);
-  }
-
-  /**
-   * Get the default width of new columns.
-   * 
-   * @return the default width in pixels
-   */
-  public int getDefaultColumnWidth() {
-    return defaultWidth;
-  }
-
-  /**
-   * Get the minimum width of columns.
-   * 
-   * @return the minimum width in pixels
-   */
-  public int getMinimumColumnWidth() {
-    return minWidth;
-  }
-
-  public boolean isAnimationEnabled() {
-    return isAnimationEnabled;
-  }
-
-  @Override
-  public void onBrowserEvent(Event event) {
-    switch (DOM.eventGetType(event)) {
-      case Event.ONSCROLL:
-        // Shorten the scroll bar is possible.
-        adjustScrollLock();
-        break;
-    }
-    super.onBrowserEvent(event);
-  }
-
-  public void onResize() {
-    getSplitLayoutPanel().onResize();
-  }
-
-  public void setAnimationEnabled(boolean enable) {
-    this.isAnimationEnabled = enable;
-  }
-
-  /**
-   * Set the default width of new columns.
-   * 
-   * @param width the default width in pixels
-   */
-  public void setDefaultColumnWidth(int width) {
-    this.defaultWidth = width;
-  }
-
-  /**
-   * Set the minimum width of columns.
-   * 
-   * @param minWidth the minimum width in pixels
-   */
-  public void setMinimumColumnWidth(int minWidth) {
-    this.minWidth = minWidth;
-  }
-
-  /**
-   * Create a {@link ListView} that will display items. The {@link ListView}
-   * must extend {@link com.google.gwt.user.client.ui.Widget}.
-   * 
-   * @param <C> the item type in the list view
-   * @param nodeInfo the node info with child data
-   * @param cell the cell to use in the list view
-   * @return the {@link ListView}
-   */
-  protected <C> ListView<C> createListView(NodeInfo<C> nodeInfo,
-      Cell<C, Void> cell) {
-    SimpleCellList<C> listView = new SimpleCellList<C>(cell, 100, 100);
-    listView.setValueUpdater(nodeInfo.getValueUpdater());
-    return listView;
-  }
-
-  /**
-   * Adjust the size of the scroll lock element based on the new position of the
-   * scroll bar.
-   */
-  private void adjustScrollLock() {
-    int scrollLeft = getElement().getScrollLeft();
-    if (scrollLeft > 0) {
-      int clientWidth = getElement().getClientWidth();
-      scrollLock.getStyle().setWidth(scrollLeft + clientWidth, Unit.PX);
-    } else {
-      scrollLock.getStyle().setWidth(1.0, Unit.PX);
-    }
-  }
-
-  /**
-   * Create a new {@link TreeNode} and append it to the end of the LayoutPanel.
-   * 
-   * @param <C> the data type of the children
-   * @param nodeInfo the info about the node
-   */
-  private <C> void appendTreeNode(final NodeInfo<C> nodeInfo) {
-    // Create the list view and its scrollable container.
-    final int level = treeNodes.size();
-    CellDecorator<C> cell = new CellDecorator<C>(nodeInfo, level);
-    final ListView<C> listView = createListView(nodeInfo, cell);
-    ScrollPanel scrollable = new ScrollPanel((Widget) listView);
-    scrollable.setStyleName(STYLENAME_COLUMN);
-
-    // Create a delegate list view so we can trap data changes.
-    ListView<C> listViewDelegate = new ListView<C>() {
-      public Range getRange() {
-        return listView.getRange();
-      }
-
-      public void setData(int start, int length, List<C> values) {
-        // Trim to the current level if the open node no longer exists.
-        TreeNode<?> node = treeNodes.get(level);
-        Object openKey = node.getCell().openKey;
-        if (openKey != null) {
-          boolean stillExists = false;
-          ProvidesKey<C> keyProvider = nodeInfo.getProvidesKey();
-          for (C value : values) {
-            if (openKey.equals(keyProvider.getKey(value))) {
-              stillExists = true;
-              break;
-            }
-          }
-          if (!stillExists) {
-            trimToLevel(level);
-          }
-        }
-
-        // Refresh the list.
-        listView.setData(start, length, values);
-      }
-
-      public void setDataSize(int size, boolean isExact) {
-        listView.setDataSize(size, isExact);
-      }
-
-      public void setDelegate(Delegate<C> delegate) {
-        listView.setDelegate(delegate);
-      }
-
-      public void setSelectionModel(SelectionModel<? super C> selectionModel) {
-        listView.setSelectionModel(selectionModel);
-      }
-    };
-
-    // Create a TreeNode.
-    TreeNode<C> treeNode = new TreeNode<C>(nodeInfo, listViewDelegate, cell,
-        scrollable);
-    treeNodes.add(treeNode);
-
-    // Attach the view to the selection model and node info.
-    listView.setSelectionModel(nodeInfo.getSelectionModel());
-    nodeInfo.setView(listViewDelegate);
-
-    // Add the ListView to the LayoutPanel.
-    SplitLayoutPanel splitPanel = getSplitLayoutPanel();
-    splitPanel.insertWest(scrollable, defaultWidth, null);
-    splitPanel.setWidgetMinSize(scrollable, minWidth);
-    splitPanel.forceLayout();
-
-    // Scroll to the right.
-    animation.scrollToEnd();
-  }
-
-  /**
-   * @return the HTML to render the closed image.
-   */
-  private String getClosedImageHtml() {
-    if (closedImageHtml == null) {
-      AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeClosed());
-      closedImageHtml = proto.getHTML().replace("style='",
-          "style='position:absolute;right:0px;top:0px;");
-    }
-    return closedImageHtml;
-  }
-
-  /**
-   * @return the HTML to render the open image.
-   */
-  private String getOpenImageHtml() {
-    if (openImageHtml == null) {
-      AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeOpen());
-      openImageHtml = proto.getHTML().replace("style='",
-          "style='position:absolute;right:0px;top:0px;");
-    }
-    return openImageHtml;
-  }
-
-  /**
-   * Get the {@link SplitLayoutPanel} used to lay out the views.
-   * 
-   * @return the {@link SplitLayoutPanel}
-   */
-  private SplitLayoutPanel getSplitLayoutPanel() {
-    return (SplitLayoutPanel) getWidget();
-  }
-
-  /**
-   * Reduce the number of {@link ListView} down to the specified level.
-   * 
-   * @param level the level to trim to
-   */
-  private void trimToLevel(int level) {
-    // Add a placeholder to maintain the same scroll width.
-    adjustScrollLock();
-
-    // Remove the listViews that are no longer needed.
-    int curLevel = treeNodes.size() - 1;
-    while (curLevel > level) {
-      TreeNode<?> removed = treeNodes.remove(curLevel);
-      removed.cleanup();
-      curLevel--;
-    }
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
deleted file mode 100644
index 1be9854..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.tree.client;
-
-import com.google.gwt.animation.client.Animation;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
-import com.google.gwt.user.client.ui.HasAnimation;
-
-import java.util.ArrayList;
-
-/**
- * A view of a tree.
- */
-public class StandardTreeView extends TreeView implements HasAnimation {
-
-  /**
-   * A node animation.
-   */
-  public abstract static class NodeAnimation extends Animation {
-
-    /**
-     * The default animation delay in milliseconds.
-     */
-    private static final int DEFAULT_ANIMATION_DURATION = 450;
-
-    /**
-     * The duration of the animation.
-     */
-    private int duration = DEFAULT_ANIMATION_DURATION;
-
-    NodeAnimation() {
-    }
-
-    /**
-     * Animate a tree node into its new state.
-     * 
-     * @param node the node to animate
-     * @param isAnimationEnabled true to animate
-     */
-    abstract void animate(StandardTreeNodeView<?> node,
-        boolean isAnimationEnabled);
-
-    public int getDuration() {
-      return duration;
-    }
-
-    public void setDuration(int duration) {
-      this.duration = duration;
-    }
-  }
-
-  /**
-   * A {@link NodeAnimation} that reveals the contents of child nodes.
-   */
-  public static class RevealAnimation extends NodeAnimation {
-
-    /**
-     * Create a new {@link RevealAnimation}.
-     * 
-     * @return the new animation
-     */
-    public static RevealAnimation create() {
-      return new RevealAnimation();
-    }
-
-    /**
-     * The container that holds the content, includind the children.
-     */
-    Element contentContainer;
-
-    /**
-     * The target height when opening, the start height when closing.
-     */
-    int height;
-
-    /**
-     * True if the node is opening, false if closing.
-     */
-    boolean opening;
-
-    /**
-     * The container that holds the child container.
-     */
-    private Element animFrame;
-
-    /**
-     * The container that holds the children.
-     */
-    private Element childContainer;
-
-    /**
-     * Not instantiable.
-     */
-    private RevealAnimation() {
-    }
-
-    /**
-     * Animate a {@link StandardTreeNodeView} into its new state.
-     * 
-     * @param node the {@link StandardTreeNodeView} to animate
-     * @param isAnimationEnabled true to animate
-     */
-    @Override
-    void animate(StandardTreeNodeView<?> node, boolean isAnimationEnabled) {
-      // Cancel any pending animations.
-      cancel();
-
-      // Initialize the fields.
-      this.opening = node.isOpen();
-      animFrame = node.ensureAnimationFrame();
-      contentContainer = node.ensureContentContainer();
-      childContainer = node.ensureChildContainer();
-
-      if (isAnimationEnabled) {
-        // Animated.
-        int duration = getDuration();
-        int childCount = childContainer.getChildCount();
-        if (childCount < 4) {
-          // Reduce the duration if there are less than four items or it will
-          // look really slow.
-          duration = (int) ((childCount / 4.0) * duration);
-        }
-        run(duration);
-      } else {
-        // Non animated.
-        cleanup();
-      }
-    }
-
-    @Override
-    protected void onComplete() {
-      cleanup();
-    }
-
-    @Override
-    protected void onStart() {
-      if (opening) {
-        animFrame.getStyle().setHeight(1.0, Unit.PX);
-        animFrame.getStyle().clearDisplay();
-        height = contentContainer.getScrollHeight();
-      } else {
-        height = contentContainer.getOffsetHeight();
-      }
-    }
-
-    @Override
-    protected void onUpdate(double progress) {
-      if (opening) {
-        double curHeight = progress * height;
-        animFrame.getStyle().setHeight(curHeight, Unit.PX);
-      } else {
-        double curHeight = (1.0 - progress) * height;
-        animFrame.getStyle().setHeight(curHeight, Unit.PX);
-      }
-    }
-
-    /**
-     * Put the node back into a clean state and clear fields.
-     */
-    private void cleanup() {
-      if (opening) {
-        animFrame.getStyle().clearDisplay();
-      } else {
-        animFrame.getStyle().setDisplay(Display.NONE);
-        childContainer.setInnerHTML("");
-      }
-      animFrame.getStyle().clearHeight();
-      this.contentContainer = null;
-      this.childContainer = null;
-      this.animFrame = null;
-    }
-  }
-
-  /**
-   * A {@link NodeAnimation} that slides children into view.
-   */
-  public static class SlideAnimation extends RevealAnimation {
-    /**
-     * Create a new {@link RevealAnimation}.
-     * 
-     * @return the new animation
-     */
-    public static SlideAnimation create() {
-      return new SlideAnimation();
-    }
-
-    /**
-     * Not instantiable.
-     */
-    private SlideAnimation() {
-    }
-
-    @Override
-    protected void onComplete() {
-      contentContainer.getStyle().clearPosition();
-      contentContainer.getStyle().clearTop();
-      contentContainer.getStyle().clearWidth();
-      super.onComplete();
-    }
-
-    @Override
-    protected void onStart() {
-      super.onStart();
-      if (opening) {
-        contentContainer.getStyle().setTop(-height, Unit.PX);
-      } else {
-        contentContainer.getStyle().setTop(0, Unit.PX);
-      }
-      contentContainer.getStyle().setPosition(Position.RELATIVE);
-    }
-
-    @Override
-    protected void onUpdate(double progress) {
-      super.onUpdate(progress);
-      if (opening) {
-        double curTop = (1.0 - progress) * -height;
-        contentContainer.getStyle().setTop(curTop, Unit.PX);
-      } else {
-        double curTop = progress * -height;
-        contentContainer.getStyle().setTop(curTop, Unit.PX);
-      }
-    }
-  }
-
-  /**
-   * The animation.
-   */
-  private NodeAnimation animation;
-
-  /**
-   * The HTML used to generate the closed image.
-   */
-  private String closedImageHtml;
-
-  /**
-   * Indicates whether or not animations are enabled.
-   */
-  private boolean isAnimationEnabled;
-
-  /**
-   * The message displayed while child nodes are loading.
-   */
-  // TODO(jlabanca): I18N loading HTML, or remove the text.
-  private String loadingHtml = "Loading...";
-
-  /**
-   * The HTML used to generate the open image.
-   */
-  private String openImageHtml;
-
-  /**
-   * The hidden root node in the tree.
-   */
-  private StandardTreeNodeView<?> rootNode;
-
-  /**
-   * Construct a new {@link TreeView}.
-   * 
-   * @param <T> the type of data in the root node
-   * @param viewModel the {@link TreeViewModel} that backs the tree
-   * @param rootValue the hidden root value of the tree
-   */
-  public <T> StandardTreeView(TreeViewModel viewModel, T rootValue) {
-    super(viewModel);
-    setStyleName("gwt-StandardTreeView");
-
-    // We use one animation for the entire tree.
-    setAnimation(SlideAnimation.create());
-
-    // Add event handlers.
-    sinkEvents(Event.ONCLICK | Event.ONCHANGE | Event.MOUSEEVENTS);
-
-    // Associate a view with the item.
-    StandardTreeNodeView<T> root = new StandardTreeNodeView<T>(this, null,
-        null, getElement(), rootValue);
-    rootNode = root;
-    root.setOpen(true);
-  }
-
-  /**
-   * Get the animation used to open and close nodes in this tree if animations
-   * are enabled.
-   * 
-   * @return the animation
-   * @see #isAnimationEnabled()
-   */
-  public NodeAnimation getAnimation() {
-    return animation;
-  }
-
-  /**
-   * Get the HTML string that is displayed while nodes wait for their children
-   * to load.
-   * 
-   * @return the loading HTML string
-   */
-  public String getLoadingHtml() {
-    return loadingHtml;
-  }
-
-  public boolean isAnimationEnabled() {
-    return isAnimationEnabled;
-  }
-
-  @Override
-  public void onBrowserEvent(Event event) {
-    super.onBrowserEvent(event);
-
-    Element target = event.getEventTarget().cast();
-
-    ArrayList<Element> chain = new ArrayList<Element>();
-    collectElementChain(chain, getElement(), target);
-
-    StandardTreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode);
-    if (nodeView != null && nodeView != rootNode) {
-      if ("click".equals(event.getType())) {
-        // Open the node when the open image is clicked.
-        Element showFewerElem = nodeView.getShowFewerElement();
-        Element showMoreElem = nodeView.getShowMoreElement();
-        if (nodeView.getImageElement().isOrHasChild(target)) {
-          nodeView.setOpen(!nodeView.isOpen());
-          return;
-        } else if (showFewerElem != null && showFewerElem.isOrHasChild(target)) {
-          nodeView.showFewer();
-          return;
-        } else if (showMoreElem != null && showMoreElem.isOrHasChild(target)) {
-          nodeView.showMore();
-          return;
-        }
-      }
-
-      // Forward the event to the cell.
-      if (nodeView.getCellParent().isOrHasChild(target)) {
-        boolean consumesEvent = nodeView.fireEventToCell(event);
-        if (!consumesEvent && "click".equals(event.getType())) {
-          nodeView.select();
-        }
-      }
-    }
-  }
-
-  /**
-   * Set the animation used to open and close nodes in this tree. You must call
-   * {@link #setAnimationEnabled(boolean)} to enable or disable animation.
-   * 
-   * @param animation a {@link NodeAnimation}
-   * @see #setAnimationEnabled(boolean)
-   */
-  public void setAnimation(NodeAnimation animation) {
-    assert animation != null : "animation cannot be null";
-    this.animation = animation;
-  }
-
-  public void setAnimationEnabled(boolean enable) {
-    this.isAnimationEnabled = enable;
-    if (!enable && animation != null) {
-      animation.cancel();
-    }
-  }
-
-  /**
-   * Set the HTML string that will be displayed when a node is waiting for its
-   * child nodes to load.
-   * 
-   * @param loadingHtml the HTML string
-   */
-  public void setLoadingHtml(String loadingHtml) {
-    this.loadingHtml = loadingHtml;
-  }
-
-  /**
-   * @return the HTML to render the closed image.
-   */
-  String getClosedImageHtml() {
-    if (closedImageHtml == null) {
-      AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeClosed());
-      closedImageHtml = proto.getHTML().replace("style='",
-          "style='position:absolute;left:0px;top:0px;");
-    }
-    return closedImageHtml;
-  }
-
-  /**
-   * @return the HTML to render the open image.
-   */
-  String getOpenImageHtml() {
-    if (openImageHtml == null) {
-      AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeOpen());
-      openImageHtml = proto.getHTML().replace("style='",
-          "style='position:absolute;left:0px;top:0px;");
-    }
-    return openImageHtml;
-  }
-
-  /**
-   * Animate the current state of a {@link StandardTreeNodeView} in this tree.
-   * 
-   * @param node the node to animate
-   */
-  void maybeAnimateTreeNode(StandardTreeNodeView<?> node) {
-    if (animation != null) {
-      animation.animate(node, node.consumeAnimate() && isAnimationEnabled());
-    }
-  }
-
-  /**
-   * Collects parents going up the element tree, terminated at the tree root.
-   */
-  private void collectElementChain(ArrayList<Element> chain, Element hRoot,
-      Element hElem) {
-    if ((hElem == null) || (hElem == hRoot)) {
-      return;
-    }
-
-    collectElementChain(chain, hRoot, hElem.getParentElement());
-    chain.add(hElem);
-  }
-
-  private StandardTreeNodeView<?> findItemByChain(ArrayList<Element> chain,
-      int idx, StandardTreeNodeView<?> parent) {
-    if (idx == chain.size()) {
-      return parent;
-    }
-
-    Element hCurElem = chain.get(idx);
-    for (int i = 0, n = parent.getChildCount(); i < n; ++i) {
-      StandardTreeNodeView<?> child = parent.getChildNode(i);
-      if (child.getElement() == hCurElem) {
-        StandardTreeNodeView<?> retItem = findItemByChain(chain, idx + 1, child);
-        if (retItem == null) {
-          return child;
-        }
-        return retItem;
-      }
-    }
-
-    return findItemByChain(chain, idx + 1, parent);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
deleted file mode 100644
index 089c4a6..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.bikeshed.tree.client;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.ImageResource;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.Widget;
-
-/**
- * A view of a tree.
- */
-public abstract class TreeView extends Composite {
-
-  /**
-   * A ClientBundle that provides images for this widget.
-   */
-  public static interface Resources extends ClientBundle {
-
-    /**
-     * An image indicating a closed branch.
-     */
-    ImageResource treeClosed();
-
-    /**
-     * An image indicating an open branch.
-     */
-    ImageResource treeOpen();
-  }
-
-  private static final Resources DEFAULT_RESOURCES = GWT.create(Resources.class);
-
-  /**
-   * The {@link Resources} used in the tree.
-   */
-  private Resources resources = DEFAULT_RESOURCES;
-
-  /**
-   * The {@link TreeViewModel} that backs the tree.
-   */
-  private TreeViewModel viewModel;
-
-  /**
-   * Construct a new {@link TreeView}.
-   * 
-   * @param viewModel the {@link TreeViewModel} that backs the tree
-   */
-  // TODO(jlabanca): Should we nuke this class?
-  public TreeView(TreeViewModel viewModel) {
-    this(viewModel, new SimplePanel());
-  }
-
-  protected TreeView(TreeViewModel viewModel, Widget widget) {
-    this.viewModel = viewModel;
-    initWidget(widget);
-  }
-
-  public TreeViewModel getTreeViewModel() {
-    return viewModel;
-  }
-
-  /**
-   * Get the width required for the images.
-   * 
-   * @return the maximum width required for images.
-   */
-  protected int getImageWidth() {
-    return Math.max(DEFAULT_RESOURCES.treeClosed().getWidth(),
-        DEFAULT_RESOURCES.treeOpen().getWidth());
-  }
-
-  /**
-   * Get the {@link Resources} used in the tree.
-   * 
-   * @return the resources
-   */
-  protected Resources getResources() {
-    return resources;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserClosed.gif b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserClosed.gif
new file mode 100644
index 0000000..6e9a180
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserClosed.gif
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserOpen.gif b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserOpen.gif
new file mode 100644
index 0000000..b891d9d
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserOpen.gif
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserOpenBackground.png b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserOpenBackground.png
new file mode 100644
index 0000000..1cbce6f
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellBrowserOpenBackground.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/treeClosed.gif b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellTreeClosedItem.gif
similarity index 100%
rename from bikeshed/src/com/google/gwt/bikeshed/tree/client/treeClosed.gif
rename to bikeshed/src/com/google/gwt/bikeshed/tree/client/cellTreeClosedItem.gif
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellTreeLoading.gif b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellTreeLoading.gif
new file mode 100644
index 0000000..c5fedc3
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellTreeLoading.gif
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/treeOpen.gif b/bikeshed/src/com/google/gwt/bikeshed/tree/client/cellTreeOpenItem.gif
similarity index 100%
rename from bikeshed/src/com/google/gwt/bikeshed/tree/client/treeOpen.gif
rename to bikeshed/src/com/google/gwt/bikeshed/tree/client/cellTreeOpenItem.gif
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/collections/CollectionFactory.java b/bikeshed/src/com/google/gwt/collections/CollectionFactory.java
index 815751a..2825bf6 100644
--- a/bikeshed/src/com/google/gwt/collections/CollectionFactory.java
+++ b/bikeshed/src/com/google/gwt/collections/CollectionFactory.java
@@ -29,5 +29,9 @@
     r.setSize(size, fillValue);
     return r;
   }
+  
+  public static <V> MutableStringMap<V> createMutableStringMap() {
+    return new MutableStringMap<V>();
+  }
 
 }
diff --git a/bikeshed/src/com/google/gwt/collections/Map.java b/bikeshed/src/com/google/gwt/collections/Map.java
new file mode 100644
index 0000000..7d28e32
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/collections/Map.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.collections;
+
+/**
+ * A dictionary of values indexed by a set of keys.
+ * 
+ * Concrete implementations of this class may provide differing behavior in
+ * terms of acceptable types and values allowed for its mappings. In particular,
+ * the result of calling {@code containsKey} or {@code get} with {@code null}
+ * keys depends on the concrete {@code Map} implementation being accessed. A
+ * recommended implementation strategy when an unsupported key is received is to
+ * consider such mapping as never present; therefore guaranteeing for an
+ * unsupported key {@code k}, {@code get(k) == null} and {@code containsKey(k)
+ * == false}.
+ * 
+ * @param <K> the type used to access values stored in the Map
+ * @param <V> the type of values stored in the Map
+ */
+public abstract class Map<K, V> {
+
+  Map() {
+  }
+
+  /**
+   * Determines if a key is in the set of keys contained in the map.
+   * 
+   * @param key to use for testing membership
+   * @return <code>true</code> if the key is contained in the map
+   */
+  public abstract boolean containsKey(K key);
+
+  /**
+   * Get a value indexed by a key.
+   * 
+   * Notice that if the Map contains {@code null} values, a returned {@code
+   * null} value does not guarantee that there is no such mapping. Use {@code
+   * containsKey(K)} to determine key membership.
+   * 
+   * @param key index to use for retrieval.
+   * @return value associated to the key or <code>null</code> otherwise.
+   */
+  public abstract V get(K key);
+
+  /**
+   * @return <code>true</code> if the map contains no entries.
+   */
+  public abstract boolean isEmpty();
+
+}
diff --git a/bikeshed/src/com/google/gwt/collections/MutableMap.java b/bikeshed/src/com/google/gwt/collections/MutableMap.java
new file mode 100644
index 0000000..77e5477
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/collections/MutableMap.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.collections;
+
+/**
+ * A Map whose contents may be modified.
+ * 
+ * The result of calling {@code put} or {@code remove} with {@code null} keys
+ * and values depends on the concrete {@code MutableMap} implementation being
+ * accessed. In particular, the result of calling {@code remove} or {@code put}
+ * with {@code null} keys (and values) depends on the concrete {@code
+ * MutableMap} implementation being accessed. A recommended implementation
+ * strategy when an unsupported key is received by {@code remove} is to consider
+ * such mapping as never present and return.
+ * 
+ * @param <K> the type used to access values stored in the Map
+ * @param <V> the type of value stored in the Map
+ */
+public abstract class MutableMap<K, V> extends Map<K, V> {
+
+  /**
+   * Removes all entries from this map.
+   */
+  public abstract void clear();
+
+  /**
+   * Put the value in the map at the given key. Note: Does not return the old
+   * value.
+   * 
+   * @param key index to the value.
+   * @param value value to be stored
+   */
+  public abstract void put(K key, V value);
+
+  /**
+   * Deletes a key-value entry if the key is a member of the key set.
+   * 
+   * @param key index to the key-value
+   */
+  public abstract void remove(K key);
+
+}
diff --git a/bikeshed/src/com/google/gwt/collections/MutableStringMap.java b/bikeshed/src/com/google/gwt/collections/MutableStringMap.java
new file mode 100644
index 0000000..97ab587
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/collections/MutableStringMap.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.collections;
+
+import java.util.HashMap;
+
+/**
+ * A {@link MutableMap} whose keys are supported by Strings. Standard byte code
+ * implementation backed by {@code HashMap}.
+ * 
+ * This class may contain mappings from {@code null} keys. More specifically a
+ * key is equal to a stored key k if {@code key == null? k == null :
+ * key.equals(k)}. This class may also store {@code null} values.
+ * 
+ * @param <V> the type of value stored in the {@link MutableMap}
+ */
+public class MutableStringMap<V> extends MutableMap<String, V> {
+
+  java.util.Map<String, V> entries;
+
+  @Override
+  @ConstantTime
+  public void clear() {
+    entries = null;
+  }
+
+  @Override
+  @ConstantTime
+  public boolean containsKey(String key) {
+    return entries != null && entries.containsKey(key);
+  }
+
+  @Override
+  @ConstantTime
+  public V get(String key) {
+    return isEmpty() ? null : entries.get(key);
+  }
+
+  @Override
+  @ConstantTime
+  public boolean isEmpty() {
+    return entries == null;
+  }
+
+  @Override
+  @ConstantTime
+  public void put(String key, V value) {
+    if (entries == null) {
+      entries = new HashMap<String, V>();
+    }
+    entries.put(key, value);
+  }
+
+  @Override
+  @ConstantTime
+  public void remove(String key) {
+    entries.remove(key);
+    if (entries.isEmpty()) {
+      entries = null;
+    }
+  }
+
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/gen/ClientRequestObject.java b/bikeshed/src/com/google/gwt/requestfactory/client/gen/ClientRequestObject.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/requestfactory/client/gen/ClientRequestObject.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractDoubleRequest.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractDoubleRequest.java
new file mode 100644
index 0000000..59891ba
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractDoubleRequest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.client.impl;
+
+/**
+ * Abstract implementation of
+ * {@link com.google.gwt.requestfactory.shared.RequestFactory.RequestObject
+ * RequestFactory.RequestObject} for requests that return Double.
+ */
+public abstract class AbstractDoubleRequest extends
+    AbstractRequest<Double, AbstractDoubleRequest> {
+
+  public AbstractDoubleRequest(RequestFactoryJsonImpl requestFactory) {
+    super(requestFactory);
+  }
+
+  public void handleResponseText(String responseText) {
+    receiver.onSuccess(Double.valueOf(responseText));
+  }
+
+  @Override
+  protected AbstractDoubleRequest getThis() {
+    return this;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractIntegerRequest.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractIntegerRequest.java
new file mode 100644
index 0000000..ef499d3
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractIntegerRequest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.client.impl;
+
+/**
+ * Abstract implementation of
+ * {@link com.google.gwt.requestfactory.shared.RequestFactory.RequestObject
+ * RequestFactory.RequestObject} for requests that return Integer.
+ */
+public abstract class AbstractIntegerRequest extends
+    AbstractRequest<Integer, AbstractIntegerRequest> {
+
+  public AbstractIntegerRequest(RequestFactoryJsonImpl requestFactory) {
+    super(requestFactory);
+  }
+
+  public void handleResponseText(String responseText) {
+    receiver.onSuccess(Integer.valueOf(responseText));
+  }
+
+  @Override
+  protected AbstractIntegerRequest getThis() {
+    return this;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java
new file mode 100644
index 0000000..92e6d67
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractJsonListRequest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.client.impl;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.valuestore.shared.impl.RecordJsoImpl;
+import com.google.gwt.valuestore.shared.impl.RecordSchema;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstract implementation of
+ * {@link com.google.gwt.requestfactory.shared.RequestFactory.RequestObject
+ * RequestFactory.RequestObject} for requests that return lists of
+ * {@link Record}.
+ * 
+ * @param <T> the type of entities returned
+ * @param <R> this request type
+ */
+public abstract class //
+AbstractJsonListRequest<T extends Record, R extends AbstractJsonListRequest<T, R>> //
+    extends AbstractRequest<List<T>, R> implements RecordListRequest<T> {
+  protected final RecordSchema<? extends T> schema;
+
+  public AbstractJsonListRequest(RecordSchema<? extends T> schema,
+      RequestFactoryJsonImpl requestService) {
+    super(requestService);
+    this.schema = schema;
+  }
+
+  public void handleResponseText(String text) {
+    JsArray<RecordJsoImpl> valueJsos = RecordJsoImpl.arrayFromJson(text);
+    List<T> valueList = new ArrayList<T>(valueJsos.length());
+    for (int i = 0; i < valueJsos.length(); i++) {
+      RecordJsoImpl jso = valueJsos.get(i);
+      jso.setSchema(schema);
+      valueList.add(schema.create(jso));
+    }
+
+    requestFactory.getValueStore().setRecords(valueJsos);
+    receiver.onSuccess(valueList);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java
new file mode 100644
index 0000000..01b5d1c
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractJsonObjectRequest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.client.impl;
+
+import com.google.gwt.requestfactory.shared.RecordRequest;
+import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.valuestore.shared.impl.RecordJsoImpl;
+import com.google.gwt.valuestore.shared.impl.RecordSchema;
+
+/**
+ * Abstract implementation of
+ * {@link com.google.gwt.requestfactory.shared.RequestFactory.RequestObject
+ * RequestFactory.RequestObject} for requests that return single instances of
+ * {@link Record}.
+ * 
+ * @param <T> the type of entities returned
+ * @param <R> this request type
+ */
+public abstract class //
+AbstractJsonObjectRequest<T extends Record, R extends AbstractJsonObjectRequest<T, R>> //
+    extends AbstractRequest<T, R> implements RecordRequest<T> {
+  protected final RecordSchema<? extends T> schema;
+
+  public AbstractJsonObjectRequest(RecordSchema<? extends T> schema,
+      RequestFactoryJsonImpl requestService) {
+    super(requestService);
+    this.schema = schema;
+  }
+
+  public void handleResponseText(String text) {
+    RecordJsoImpl jso = RecordJsoImpl.fromJson(text);
+    jso.setSchema(schema);
+
+    requestFactory.getValueStore().setRecord(jso);
+    receiver.onSuccess(schema.create(jso));
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractListJsonRequestObject.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractListJsonRequestObject.java
deleted file mode 100644
index 38c9c97..0000000
--- a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractListJsonRequestObject.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.requestfactory.client.impl;
-
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.requestfactory.shared.EntityListRequest;
-import com.google.gwt.requestfactory.shared.RequestFactory;
-import com.google.gwt.user.client.ui.TakesValueList;
-import com.google.gwt.valuestore.shared.Property;
-import com.google.gwt.valuestore.shared.Record;
-import com.google.gwt.valuestore.shared.impl.RecordJsoImpl;
-import com.google.gwt.valuestore.shared.impl.RecordSchema;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Abstract implementation of {@link RequestFactory.RequestObject} for methods
- * returning lists of entities (as opposed to lists of primitives or enums).
- *
- * @param <T> the type of entities returned
- * @param <R> this request type
- */
-public abstract class AbstractListJsonRequestObject<T extends Record, R extends AbstractListJsonRequestObject<T, R>>
-    implements RequestFactory.RequestObject, EntityListRequest<T> {
-
-  private final RecordSchema<? extends T> schema;
-  private final RequestFactoryJsonImpl requestFactory;
-  private final Set<Property<?>> properties = new HashSet<Property<?>>();
-
-  private TakesValueList<T> target;
-
-  public AbstractListJsonRequestObject(RecordSchema<? extends T> schema,
-      RequestFactoryJsonImpl requestService) {
-    this.requestFactory = requestService;
-    this.schema = schema;
-  }
-
-  public void fire() {
-    requestFactory.fire(this);
-  }
-
-  public R forProperties(Collection<Property<?>> properties) {
-    this.properties.addAll(properties);
-    return getThis();
-  }
-
-  public R forProperty(Property<?> property) {
-    this.properties.add(property);
-    return getThis();
-  }
-
-  /**
-   * @return the properties
-   */
-  public Set<Property<?>> getProperties() {
-    return Collections.unmodifiableSet(properties);
-  }
-
-  public void handleResponseText(String text) {
-    JsArray<RecordJsoImpl> valueJsos = RecordJsoImpl.arrayFromJson(text);
-    List<T> valueList = new ArrayList<T>(valueJsos.length());
-    for (int i = 0; i < valueJsos.length(); i++) {
-      RecordJsoImpl jso = valueJsos.get(i);
-      jso.setSchema(schema);
-      valueList.add(schema.create(jso));
-    }
-
-    requestFactory.getValueStore().setRecords(valueJsos);
-    target.setValueList(valueList);
-  }
-
-  public R to(TakesValueList<T> target) {
-    this.target = target;
-    return getThis();
-  }
-
-  /**
-   * Subclasses must override to return {@code this}, to allow builder-style
-   * methods to do the same.
-   */
-  protected abstract R getThis();
-}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractLongRequest.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractLongRequest.java
new file mode 100644
index 0000000..a3d7271
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractLongRequest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.client.impl;
+
+/**
+ * Abstract implementation of
+ * {@link com.google.gwt.requestfactory.shared.RequestFactory.RequestObject
+ * RequestFactory.RequestObject} for requests that return Long.
+ */
+public abstract class AbstractLongRequest extends
+    AbstractRequest<Long, AbstractLongRequest> {
+
+  public AbstractLongRequest(RequestFactoryJsonImpl requestFactory) {
+    super(requestFactory);
+  }
+
+  public void handleResponseText(String responseText) {
+    receiver.onSuccess(Long.valueOf(responseText));
+  }
+
+  @Override
+  protected AbstractLongRequest getThis() {
+    return this;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
new file mode 100644
index 0000000..60ce0f3
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.client.impl;
+
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.valuestore.shared.Property;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Abstract implementation of {@link RequestFactory.RequestObject}.
+ * 
+ * @param <T> return type
+ * @param <R> type of this request object
+ */
+public abstract class AbstractRequest<T, R extends AbstractRequest<T, R>>
+    implements RequestFactory.RequestObject<T> {
+
+  protected final RequestFactoryJsonImpl requestFactory;
+  protected Receiver<T> receiver;
+
+  private final Set<Property<?>> properties = new HashSet<Property<?>>();
+
+  public AbstractRequest(RequestFactoryJsonImpl requestFactory) {
+    this.requestFactory = requestFactory;
+  }
+
+  public void fire() {
+    assert null != receiver : "to(Receiver) was not called";
+    requestFactory.fire(this);
+  }
+
+  public R forProperties(Collection<Property<?>> properties) {
+    this.properties.addAll(properties);
+    return getThis();
+  }
+
+  public R forProperty(Property<?> property) {
+    this.properties.add(property);
+    return getThis();
+  }
+
+  /**
+   * @return the properties
+   */
+  public Set<Property<?>> getProperties() {
+    return Collections.unmodifiableSet(properties);
+  }
+
+  public R to(Receiver<T> target) {
+    this.receiver = target;
+    return getThis();
+  }
+
+  /**
+   * Subclasses must override to return {@code this}, to allow builder-style
+   * methods to do the same.
+   */
+  protected abstract R getThis();
+
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/ClientRequestObject.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/ClientRequestHelper.java
similarity index 93%
rename from bikeshed/src/com/google/gwt/requestfactory/client/impl/ClientRequestObject.java
rename to bikeshed/src/com/google/gwt/requestfactory/client/impl/ClientRequestHelper.java
index 73ae76e..aea8ed3 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/client/impl/ClientRequestObject.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/ClientRequestHelper.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -23,7 +23,7 @@
  * A convenience class to convert a Map<String, String> to a JSON string on the
  * client side.
  */
-public class ClientRequestObject {
+public class ClientRequestHelper {
 
   private static class MyJSO extends JavaScriptObject {
     static native MyJSO create() /*-{
@@ -39,7 +39,7 @@
     }-*/;
 
     private native String toJsonString()/*-{
-      return JSON.stringify(this);
+      return $wnd.JSON.stringify(this);
     }-*/;
   }
 
diff --git a/bikeshed/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java b/bikeshed/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
index 745e978..5663a32 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
@@ -21,13 +21,17 @@
 import com.google.gwt.http.client.RequestCallback;
 import com.google.gwt.http.client.RequestException;
 import com.google.gwt.http.client.Response;
+import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.SyncRequest;
+import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.requestfactory.shared.impl.RequestDataManager;
 import com.google.gwt.valuestore.client.DeltaValueStoreJsonImpl;
 import com.google.gwt.valuestore.client.ValueStoreJsonImpl;
 import com.google.gwt.valuestore.shared.DeltaValueStore;
 
+import java.util.Set;
+
 /**
  * Base implementation of RequestFactory.
  */
@@ -42,7 +46,7 @@
     this.valueStore = new ValueStoreJsonImpl(handlerManager);
   }
 
-  public void fire(final RequestObject requestObject) {
+  public void fire(final RequestObject<?> requestObject) {
     RequestBuilder builder = new RequestBuilder(RequestBuilder.POST,
         RequestFactory.URL);
     builder.setRequestData(requestObject.getRequestData());
@@ -82,13 +86,14 @@
 
     return new SyncRequest() {
 
+      Receiver<Set<SyncResult>> receiver = null;
       public void fire() {
 
         RequestBuilder builder = new RequestBuilder(RequestBuilder.POST,
             "/expenses/data");
 
-        builder.setRequestData(ClientRequestObject.getRequestString(RequestDataManager.getRequestMap(
-            RequestFactory.UPDATE_STRING, null, jsonDeltas.toJson())));
+        builder.setRequestData(ClientRequestHelper.getRequestString(RequestDataManager.getRequestMap(
+            RequestFactory.SYNC, null, jsonDeltas.toJson())));
         builder.setCallback(new RequestCallback() {
 
           public void onError(Request request, Throwable exception) {
@@ -98,8 +103,7 @@
           public void onResponseReceived(Request request, Response response) {
             if (200 == response.getStatusCode()) {
               // parse the return value.
-
-              jsonDeltas.commit(response.getText());
+              receiver.onSuccess(jsonDeltas.commit(response.getText()));
             } else {
               // shell.error.setInnerText(SERVER_ERROR + " ("
               // + response.getStatusText() + ")");
@@ -114,6 +118,11 @@
           // ")");
         }
       }
+
+      public SyncRequest to(Receiver<Set<SyncResult>> receiver) {
+        this.receiver = receiver;
+        return this;
+      }
     };
   }
 }
diff --git a/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index 38974a1..e7adf26 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -27,13 +27,18 @@
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.requestfactory.client.impl.AbstractListJsonRequestObject;
-import com.google.gwt.requestfactory.client.impl.ClientRequestObject;
+import com.google.gwt.requestfactory.client.impl.AbstractDoubleRequest;
+import com.google.gwt.requestfactory.client.impl.AbstractIntegerRequest;
+import com.google.gwt.requestfactory.client.impl.AbstractJsonListRequest;
+import com.google.gwt.requestfactory.client.impl.AbstractJsonObjectRequest;
+import com.google.gwt.requestfactory.client.impl.AbstractLongRequest;
+import com.google.gwt.requestfactory.client.impl.ClientRequestHelper;
 import com.google.gwt.requestfactory.client.impl.RequestFactoryJsonImpl;
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.requestfactory.shared.RecordRequest;
 import com.google.gwt.requestfactory.shared.ServerOperation;
 import com.google.gwt.requestfactory.shared.RequestFactory.WriteOperation;
 import com.google.gwt.requestfactory.shared.impl.RequestDataManager;
-import com.google.gwt.sample.expenses.gwt.request.ServerType;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.PrintWriterManager;
 import com.google.gwt.user.rebind.SourceWriter;
@@ -57,6 +62,8 @@
  */
 public class RequestFactoryGenerator extends Generator {
 
+  private final Set<JClassType> generatedRecordTypes = new HashSet<JClassType>();
+
   @Override
   public String generate(TreeLogger logger, GeneratorContext generatorContext,
       String interfaceName) throws UnableToCompleteException {
@@ -96,17 +103,32 @@
     return packageName + "." + implName;
   }
 
+  private String asInnerImplClass(String className, JClassType outerClassName) {
+    className = outerClassName.getQualifiedSourceName() + "Impl." + className;
+    return className;
+  }
+
   private String capitalize(String name) {
     return name.substring(0, 1).toUpperCase() + name.substring(1);
   }
 
-  private String ensureRecordType(TreeLogger logger,
+  private void ensureRecordType(TreeLogger logger,
       GeneratorContext generatorContext, PrintWriterManager printWriters,
-      TypeOracle typeOracle, String packageName, JClassType publicRecordType)
+      String packageName, JClassType publicRecordType)
       throws UnableToCompleteException {
-    String recordImplTypeName = publicRecordType.getName() + "Impl";
+    TypeOracle typeOracle = generatorContext.getTypeOracle();
 
+    if (!publicRecordType.isAssignableTo(typeOracle.findType(Record.class.getName()))) {
+      return;
+    }
+
+    if (generatedRecordTypes.contains(publicRecordType)) {
+      return;
+    }
+
+    String recordImplTypeName = publicRecordType.getName() + "Impl";
     PrintWriter pw = printWriters.tryToMakePrintWriterFor(recordImplTypeName);
+
     if (pw != null) {
       logger = logger.branch(TreeLogger.DEBUG, "Generating "
           + publicRecordType.getName());
@@ -123,6 +145,9 @@
         throw new UnableToCompleteException();
       }
 
+      f.addImport(AbstractJsonListRequest.class.getName());
+      f.addImport(AbstractJsonObjectRequest.class.getName());
+      f.addImport(RequestFactoryJsonImpl.class.getName());
       f.addImport(Property.class.getName());
       f.addImport(Record.class.getName());
       f.addImport(RecordImpl.class.getName());
@@ -139,75 +164,14 @@
 
       SourceWriter sw = f.createSourceWriter(generatorContext, pw);
       sw.println();
-      sw.println(String.format(
-          "public static class MySchema extends RecordSchema<%s> {",
-          recordImplTypeName));
 
-      sw.indent();
-      sw.println("private final Set<Property<?>> allProperties;");
-      sw.println("{");
-
-      sw.indent();
-      sw.println("Set<Property<?>> set = new HashSet<Property<?>>();");
-      sw.println("set.addAll(super.allProperties());");
-
-      JClassType propertyType;
-      try {
-        propertyType = typeOracle.getType(Property.class.getName());
-      } catch (NotFoundException e) {
-        throw new RuntimeException(e);
-      }
-
-      for (JField field : publicRecordType.getFields()) {
-        if (propertyType.getErasedType() == field.getType().getErasedType()) {
-          sw.println(String.format("set.add(%s);", field.getName()));
-        }
-      }
-
-      sw.println("allProperties = Collections.unmodifiableSet(set);");
-      sw.outdent();
-      sw.println("}");
+      JClassType propertyType = printSchema(typeOracle, publicRecordType,
+          recordImplTypeName, eventType, sw, logger);
 
       sw.println();
-      sw.println("public Set<Property<?>> allProperties() {");
-      sw.indent();
-      sw.println("return allProperties;");
-      sw.outdent();
-      sw.println("}");
-
-      sw.println();
-      sw.println("@Override");
-      sw.println(String.format("public %s create(RecordJsoImpl jso) {",
-          recordImplTypeName));
-      sw.indent();
-      sw.println(String.format("return new %s(jso);", recordImplTypeName));
-      sw.outdent();
-      sw.println("}");
-
-      sw.println();
-      sw.println("@Override");
-      sw.println(String.format("public %s createChangeEvent(Record record, WriteOperation writeOperation) {",
-          eventType.getName()));
-      sw.indent();
-      sw.println(String.format("return new %s((%s) record, writeOperation);",
-          eventType.getName(), publicRecordType.getName()));
-      sw.outdent();
-      sw.println("}");
-
-      sw.println();
-      sw.println("public String getToken() {");
-      sw.indent();
-      ServerType serverType = publicRecordType.getAnnotation(ServerType.class);
-      String token = serverType.token();
-      if ("[UNASSIGNED]".equals(token)) {
-        token = publicRecordType.getName();
-      }
-      sw.println("return \"" + token + "\";");
-      sw.outdent();
-      sw.println("}");
-
-      sw.outdent();
-      sw.println("}");
+      String simpleImplName = publicRecordType.getSimpleSourceName() + "Impl";
+      printRequestImplClass(sw, publicRecordType, simpleImplName, true);
+      printRequestImplClass(sw, publicRecordType, simpleImplName, false);
 
       sw.println();
       sw.println(String.format(
@@ -246,7 +210,7 @@
       sw.println("}");
     }
 
-    return recordImplTypeName;
+    generatedRecordTypes.add(publicRecordType);
   }
 
   private void generateOnce(TreeLogger logger,
@@ -324,24 +288,14 @@
 
     ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
         packageName, implName);
-    f.addImport(ClientRequestObject.class.getName());
-    f.addImport(AbstractListJsonRequestObject.class.getName());
+    f.addImport(ClientRequestHelper.class.getName());
     f.addImport(RequestDataManager.class.getName());
 
-    JClassType returnType = getReturnType(logger, interfaceType);
-    String returnImplTypeName =
-
-    ensureRecordType(logger, generatorContext, printWriters,
-        generatorContext.getTypeOracle(), returnType.getPackage().getName(),
-        returnType);
-
     f.addImplementedInterface(interfaceType.getName());
 
     SourceWriter sw = f.createSourceWriter(generatorContext, out);
     sw.println();
 
-    printRequestImplClass(sw, returnType, returnImplTypeName);
-
     sw.println("private final " + mainType.getName() + "Impl factory;");
     sw.println();
     // constructor for the class.
@@ -355,19 +309,45 @@
 
     // write each method.
     for (JMethod method : interfaceType.getMethods()) {
+      JClassType returnType = method.getReturnType().isParameterized().getTypeArgs()[0];
+
+      ensureRecordType(logger, generatorContext, printWriters,
+          returnType.getPackage().getName(), returnType);
+
       ServerOperation annotation = method.getAnnotation(ServerOperation.class);
       if (annotation == null) {
         logger.log(TreeLogger.ERROR, "no annotation on the service method "
             + method);
         throw new UnableToCompleteException();
       }
+
+      JClassType requestType = method.getReturnType().isClassOrInterface();
+      String requestClassName = null;
+
+      TypeOracle typeOracle = generatorContext.getTypeOracle();
+      if (isRecordListRequest(typeOracle, requestType)) {
+        requestClassName = asInnerImplClass("ListRequestImpl", returnType);
+      } else if (isRecordRequest(typeOracle, requestType)) {
+        requestClassName = asInnerImplClass("ObjectRequestImpl", returnType);
+      } else if (isLongRequest(typeOracle, requestType)) {
+        requestClassName = AbstractLongRequest.class.getName();
+      } else if (isIntegerRequest(typeOracle, requestType)) {
+        requestClassName = AbstractIntegerRequest.class.getName();
+      } else if (isDoubleRequest(typeOracle, requestType)) {
+        requestClassName = AbstractDoubleRequest.class.getName();
+      } else {
+        logger.log(TreeLogger.ERROR, "Return type " + requestType
+            + " is not yet supported");
+        throw new UnableToCompleteException();
+      }
+
       sw.println(getMethodDeclaration(method) + " {");
       sw.indent();
-      sw.println("return new RequestImpl() {");
+      sw.println("return new " + requestClassName + "(factory) {");
       sw.indent();
       sw.println("public String getRequestData() {");
       sw.indent();
-      sw.println("return " + ClientRequestObject.class.getSimpleName()
+      sw.println("return " + ClientRequestHelper.class.getSimpleName()
           + ".getRequestString(" + RequestDataManager.class.getSimpleName()
           + ".getRequestMap(\"" + annotation.value() + "\", "
           + getParametersAsString(method) + ", null));");
@@ -429,6 +409,9 @@
         sb.append(", ");
       }
       sb.append(parameter.getName());
+      // TODO No. This defeats the entire purpose of PropertyReference. It's
+      // supposed
+      // to be dereferenced server side, not client side.
       if ("com.google.gwt.valuestore.shared.PropertyReference".equals(parameter.getType().getQualifiedBinaryName())) {
         sb.append(".get()");
       }
@@ -436,48 +419,53 @@
     return "new Object[] {" + sb.toString() + "}";
   }
 
-  /**
-   * Inspect all the get methods that are returning a List of "domain type".
-   * Return the domain type.
-   * <p>
-   * TODO: Lift the restriction that there be just one return type.
-   */
-  private JClassType getReturnType(TreeLogger logger, JClassType interfaceType)
-      throws UnableToCompleteException {
-    Set<JClassType> returnTypes = new LinkedHashSet<JClassType>();
-    for (JMethod method : interfaceType.getMethods()) {
-      JType returnType = method.getReturnType();
-      if (returnType instanceof JParameterizedType) {
-        for (JClassType typeArg : ((JParameterizedType) returnType).getTypeArgs()) {
-          returnTypes.add(typeArg);
-        }
-      }
-    }
-    if (returnTypes.size() != 1) {
-      logger.log(TreeLogger.ERROR, "Methods return objects of different types");
-      throw new UnableToCompleteException();
-    }
-    return returnTypes.toArray(new JClassType[0])[0];
+  private boolean isDoubleRequest(TypeOracle typeOracle, JClassType requestType) {
+    return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Double.class.getName()));
+  }
+
+  private boolean isIntegerRequest(TypeOracle typeOracle, JClassType requestType) {
+    return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Integer.class.getName()));
+  }
+
+  private boolean isLongRequest(TypeOracle typeOracle, JClassType requestType) {
+    return requestType.isParameterized().getTypeArgs()[0].isAssignableTo(typeOracle.findType(Long.class.getName()));
+  }
+
+  private boolean isRecordListRequest(TypeOracle typeOracle,
+      JClassType requestType) {
+    return requestType.isAssignableTo(typeOracle.findType(RecordListRequest.class.getName()));
+  }
+
+  private boolean isRecordRequest(TypeOracle typeOracle, JClassType requestType) {
+    return requestType.isAssignableTo(typeOracle.findType(RecordRequest.class.getName()));
   }
 
   /**
-   * Prints the RequestImpl class.
+   * Prints a ListRequestImpl or ObjectRequestImpl class.
+   * 
+   * @param list
    */
   private void printRequestImplClass(SourceWriter sw, JClassType returnType,
-      String returnImplTypeName) {
-    sw.println("private abstract class RequestImpl extends "
-        + AbstractListJsonRequestObject.class.getSimpleName() + "<"
-        + returnType.getName() + ", RequestImpl> {");
+      String returnImplTypeName, boolean list) {
+
+    String name = list ? "ListRequestImpl" : "ObjectRequestImpl";
+    Class<?> superClass = list ? AbstractJsonListRequest.class
+        : AbstractJsonObjectRequest.class;
+
+    sw.println("public static abstract class " + name + " extends "
+        + superClass.getSimpleName() + "<" + returnType.getName() + ", " + name
+        + "> {");
     sw.println();
     sw.indent();
-    sw.println("RequestImpl() {");
+    sw.println(String.format("%s(%s factory) {", name,
+        RequestFactoryJsonImpl.class.getSimpleName()));
     sw.indent();
     sw.println("super(" + returnImplTypeName + ".SCHEMA, factory);");
     sw.outdent();
     sw.println("}");
     sw.println();
     sw.println("@Override");
-    sw.println("protected RequestImpl getThis() {");
+    sw.println("protected " + name + " getThis() {");
     sw.indent();
     sw.println("return this;");
     sw.outdent();
@@ -486,4 +474,99 @@
     sw.println("}");
     sw.println();
   }
+
+  /**
+   * @param typeOracle
+   * @param publicRecordType
+   * @param recordImplTypeName
+   * @param eventType
+   * @param sw
+   * @return
+   * @throws UnableToCompleteException
+   */
+  private JClassType printSchema(TypeOracle typeOracle,
+      JClassType publicRecordType, String recordImplTypeName,
+      JClassType eventType, SourceWriter sw, TreeLogger logger) throws UnableToCompleteException {
+    sw.println(String.format(
+        "public static class MySchema extends RecordSchema<%s> {",
+        recordImplTypeName));
+
+    sw.indent();
+    sw.println("private final Set<Property<?>> allProperties;");
+    sw.println("{");
+
+    sw.indent();
+    sw.println("Set<Property<?>> set = new HashSet<Property<?>>();");
+    sw.println("set.addAll(super.allProperties());");
+
+    JClassType propertyType;
+    try {
+      propertyType = typeOracle.getType(Property.class.getName());
+    } catch (NotFoundException e) {
+      throw new RuntimeException(e);
+    }
+
+    for (JField field : publicRecordType.getFields()) {
+      if (propertyType.getErasedType() == field.getType().getErasedType()) {
+        sw.println(String.format("set.add(%s);", field.getName()));
+      }
+    }
+
+    sw.println("allProperties = Collections.unmodifiableSet(set);");
+    sw.outdent();
+    sw.println("}");
+
+    sw.println();
+    sw.println("public Set<Property<?>> allProperties() {");
+    sw.indent();
+    sw.println("return allProperties;");
+    sw.outdent();
+    sw.println("}");
+
+    sw.println();
+    sw.println("@Override");
+    sw.println(String.format("public %s create(RecordJsoImpl jso) {",
+        recordImplTypeName));
+    sw.indent();
+    sw.println(String.format("return new %s(jso);", recordImplTypeName));
+    sw.outdent();
+    sw.println("}");
+
+    sw.println();
+    sw.println("@Override");
+    sw.println(String.format(
+        "public %s createChangeEvent(Record record, WriteOperation writeOperation) {",
+        eventType.getName()));
+    sw.indent();
+    sw.println(String.format("return new %s((%s) record, writeOperation);",
+        eventType.getName(), publicRecordType.getName()));
+    sw.outdent();
+    sw.println("}");
+
+    sw.println();
+    sw.println("public String getToken() {");
+    sw.indent();
+    String fieldName = "TOKEN";
+    validateTokenField(publicRecordType, fieldName, typeOracle, logger);
+    sw.println("return " + publicRecordType.getName() + "." + fieldName
+        + "; // special field");
+    sw.outdent();
+    sw.println("}");
+
+    sw.outdent();
+    sw.println("}");
+    return propertyType;
+  }
+
+  private void validateTokenField(JClassType publicRecordType,
+      String fieldName, TypeOracle typeOracle, TreeLogger logger)
+      throws UnableToCompleteException {
+    JField field = publicRecordType.getField(fieldName);
+    if (field == null
+        || field.getType() != typeOracle.findType("java.lang.String")) {
+      logger.log(TreeLogger.ERROR, "The field " + fieldName
+          + " of type String must be defined on " + publicRecordType.getName());
+      throw new UnableToCompleteException();
+    }
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index 92d999a..2b9a454 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -16,11 +16,11 @@
 package com.google.gwt.requestfactory.server;
 
 import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.ServerType;
 import com.google.gwt.requestfactory.shared.RequestFactory.Config;
 import com.google.gwt.requestfactory.shared.RequestFactory.RequestDefinition;
 import com.google.gwt.requestfactory.shared.RequestFactory.WriteOperation;
 import com.google.gwt.requestfactory.shared.impl.RequestDataManager;
-import com.google.gwt.sample.expenses.gwt.request.ServerType;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
@@ -36,6 +36,8 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -46,6 +48,10 @@
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
 
 /**
  * Handles GWT RequestFactory JSON requests. Configured via servlet context
@@ -54,7 +60,7 @@
  * com.google.gwt.requestfactory.shared.RequestFactory.Config.
  * <p>
  * e.g.
- *
+ * 
  * <pre>  &lt;context-param>
     &lt;param-name>servlet.serverOperation&lt;/param-name>
     &lt;param-value>com.myco.myapp.MyAppServerSideOperations&lt;/param-value>
@@ -79,21 +85,23 @@
     }
   }
 
-  private static final String SERVER_OPERATION_CONTEXT_PARAM = "servlet.serverOperation";
-  // TODO: Remove this hack
-  private static final Set<String> PROPERTY_SET = new HashSet<String>();
+  private static final Set<String> BLACK_LIST = initBlackList();
 
-  static {
-    for (String str : new String[] {
-        "id", "version", "displayName", "userName", "purpose", "created"}) {
-      PROPERTY_SET.add(str);
+  private static final String SERVER_OPERATION_CONTEXT_PARAM = "servlet.serverOperation";
+
+  private static Set<String> initBlackList() {
+    Set<String> blackList = new HashSet<String>();
+    for (String str : new String[] {"password"}) {
+      blackList.add(str);
     }
+    return Collections.unmodifiableSet(blackList);
   }
 
   private Config config = null;
 
   protected Map<String, EntityRecordPair> tokenToEntityRecord;
 
+  @SuppressWarnings("unchecked")
   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
       throws IOException {
@@ -107,7 +115,7 @@
       PrintWriter writer = response.getWriter();
       JSONObject topLevelJsonObject = new JSONObject(getContent(request));
       String operationName = topLevelJsonObject.getString(RequestDataManager.OPERATION_TOKEN);
-      if (operationName.equals(RequestFactory.UPDATE_STRING)) {
+      if (operationName.equals(RequestFactory.SYNC)) {
         sync(topLevelJsonObject.getString(RequestDataManager.CONTENT_TOKEN),
             writer);
       } else {
@@ -122,14 +130,27 @@
         Object args[] = RequestDataManager.getObjectsFromParameterMap(
             getParameterMap(topLevelJsonObject),
             domainMethod.getParameterTypes());
-        Object resultList = domainMethod.invoke(null, args);
-        if (!(resultList instanceof List<?>)) {
-          throw new IllegalArgumentException("return value not a list "
-              + resultList);
+        Object result = domainMethod.invoke(null, args);
+
+        if ((result instanceof List<?>) != operation.isReturnTypeList()) {
+          throw new IllegalArgumentException(String.format(
+              "Type mismatch, expected %s%s, but %s returns %s",
+              operation.isReturnTypeList() ? "list of " : "",
+              operation.getReturnType(), domainMethod,
+              domainMethod.getReturnType()));
         }
-        JSONArray jsonArray = getJsonArray((List<?>) resultList,
-            operation.getReturnType());
-        writer.print(jsonArray.toString());
+
+        if (result instanceof List<?>) {
+          JSONArray jsonArray = getJsonArray((List<?>) result,
+              (Class<? extends Record>) operation.getReturnType());
+          writer.print(jsonArray.toString());
+        } else if (result instanceof Number) {
+          writer.print(result.toString());
+        } else {
+          JSONObject jsonObject = getJsonObject(result,
+              (Class<? extends Record>) operation.getReturnType());
+          writer.print("(" + jsonObject.toString() + ")");
+        }
       }
       writer.flush();
       // TODO: clean exception handling code below.
@@ -181,38 +202,46 @@
     Class<?> entity = tokenToEntityRecord.get(recordToken).entity;
     Class<? extends Record> record = tokenToEntityRecord.get(recordToken).record;
     Map<String, Class<?>> propertiesInRecord = getPropertiesFromRecord(record);
-    validateKeys(recordObject, propertiesInRecord);
+    validateKeys(recordObject, propertiesInRecord.keySet());
+    updatePropertyTypes(propertiesInRecord, entity);
 
     // get entityInstance
     Object entityInstance = getEntityInstance(writeOperation, entity,
-        recordObject.getString("id"), propertiesInRecord.get("id"));
+        recordObject.get("id"), propertiesInRecord.get("id"));
 
     // persist
+    Set<ConstraintViolation<Object>> violations = null;
     if (writeOperation == WriteOperation.DELETE) {
       entity.getMethod("remove").invoke(entityInstance);
     } else {
       Iterator<?> keys = recordObject.keys();
       while (keys.hasNext()) {
         String key = (String) keys.next();
-        Object value = recordObject.getString(key);
         Class<?> propertyType = propertiesInRecord.get(key);
-        // TODO: hack to work around the GAE integer bug.
-        if ("version".equals(key)) {
-          propertyType = Long.class;
-          value = new Long(value.toString());
-        }
         if (writeOperation == WriteOperation.CREATE && ("id".equals(key))) {
           // ignored. id is assigned by default.
         } else {
+          Object propertyValue = getPropertyValueFromRequest(recordObject, key,
+              propertyType);
+          propertyValue = getSwizzledObject(propertyValue, propertyType);
           entity.getMethod(getMethodNameFromPropertyName(key, "set"),
-              propertyType).invoke(entityInstance, value);
+              propertyType).invoke(entityInstance, propertyValue);
         }
       }
-      entity.getMethod("persist").invoke(entityInstance);
+
+      // validations check..
+      ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
+      Validator validator = validatorFactory.getValidator();
+
+      violations = validator.validate(entityInstance);
+      if (violations.isEmpty()) {
+        entity.getMethod("persist").invoke(entityInstance);
+      }
     }
 
     // return data back.
-    return getReturnRecord(writeOperation, entity, entityInstance, recordObject);
+    return getReturnRecord(writeOperation, entityInstance, recordObject,
+        violations);
   }
 
   private Collection<Property<?>> allProperties(Class<? extends Record> clazz) {
@@ -251,9 +280,18 @@
             tokenToEntityRecord = new HashMap<String, EntityRecordPair>();
             for (Class<? extends Record> recordClass : config.recordTypes()) {
               ServerType serverType = recordClass.getAnnotation(ServerType.class);
-              String token = serverType.token();
-              if ("[UNASSIGNED]".equals(token)) {
-                token = recordClass.getSimpleName();
+              String token = (String) recordClass.getField("TOKEN").get(null);
+              if (token == null) {
+                throw new IllegalStateException("TOKEN field on "
+                    + recordClass.getName() + " can not be null");
+              }
+              EntityRecordPair previousValue = tokenToEntityRecord.get(token);
+              if (previousValue != null) {
+                throw new IllegalStateException(
+                    "TOKEN fields have to be unique. TOKEN fields for both "
+                        + recordClass.getName() + " and "
+                        + previousValue.record.getName()
+                        + " have the same value, value = " + token);
               }
               tokenToEntityRecord.put(token, new EntityRecordPair(
                   serverType.type(), recordClass));
@@ -270,6 +308,8 @@
           failConfig(e);
         } catch (ClassCastException e) {
           failConfig(e);
+        } catch (NoSuchFieldException e) {
+          failConfig(e);
         }
       }
     }
@@ -304,22 +344,21 @@
   }
 
   private Object getEntityInstance(WriteOperation writeOperation,
-      Class<?> entity, String idValue, Class<?> idType)
+      Class<?> entity, Object idValue, Class<?> idType)
       throws SecurityException, InstantiationException, IllegalAccessException,
       InvocationTargetException, NoSuchMethodException {
 
     if (writeOperation == WriteOperation.CREATE) {
       return entity.getConstructor().newInstance();
     }
-
     // TODO: check "version" validity.
     return entity.getMethod("find" + entity.getSimpleName(), idType).invoke(
-        null, idValue);
+        null, getSwizzledObject(idValue, idType));
   }
 
   /**
    * Converts the returnValue of a 'get' method to a JSONArray.
-   *
+   * 
    * @param resultObject object returned by a 'get' method, must be of type
    *          List<?>
    * @return the JSONArray
@@ -334,24 +373,30 @@
     }
 
     for (Object entityElement : resultList) {
-      JSONObject jsonObject = new JSONObject();
-      for (Property<?> p : allProperties(entityKeyClass)) {
-
-        if (requestedProperty(p)) {
-          String propertyName = p.getName();
-          jsonObject.put(propertyName, getPropertyValue(entityElement,
-              propertyName));
-        }
-      }
-      jsonArray.put(jsonObject);
+      jsonArray.put(getJsonObject(entityElement, entityKeyClass));
     }
     return jsonArray;
   }
 
+  private JSONObject getJsonObject(Object entityElement,
+      Class<? extends Record> entityKeyClass) throws JSONException,
+      NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+    JSONObject jsonObject = new JSONObject();
+    for (Property<?> p : allProperties(entityKeyClass)) {
+
+      if (requestedProperty(p)) {
+        String propertyName = p.getName();
+        jsonObject.put(propertyName, getPropertyValueFromDataStore(
+            entityElement, propertyName));
+      }
+    }
+    return jsonObject;
+  }
+
   /**
    * Returns methodName corresponding to the propertyName that can be invoked on
    * an entity.
-   *
+   * 
    * Example: "userName" returns prefix + "UserName". "version" returns prefix +
    * "Version"
    */
@@ -412,13 +457,12 @@
   }
 
   /**
-   * @param entityElement
-   * @param property
-   * @return
+   * Returns the propertyValue in the right type, from the DataStore. The value
+   * is sent into the response.
    */
-  private Object getPropertyValue(Object entityElement, String propertyName)
-      throws SecurityException, NoSuchMethodException, IllegalAccessException,
-      InvocationTargetException {
+  private Object getPropertyValueFromDataStore(Object entityElement,
+      String propertyName) throws SecurityException, NoSuchMethodException,
+      IllegalAccessException, InvocationTargetException {
     String methodName = getMethodNameFromPropertyName(propertyName, "get");
     Method method = entityElement.getClass().getMethod(methodName);
     Object returnValue = method.invoke(entityElement);
@@ -435,15 +479,47 @@
     return returnValue;
   }
 
+  /**
+   * Returns the property value, in the specified type, from the request object.
+   * The value is put in the DataStore.
+   */
+  private Object getPropertyValueFromRequest(JSONObject recordObject,
+      String key, Class<?> propertyType) throws JSONException {
+    if (propertyType == java.lang.Integer.class) {
+      return new Integer(recordObject.getInt(key));
+    }
+    /*
+     * 1. decode String to long. 2. decode Double to Date.
+     */
+    if (propertyType == java.lang.Long.class) {
+      return Long.valueOf(recordObject.getString(key));
+    }
+    if (propertyType == java.util.Date.class) {
+      return new Date((long) recordObject.getDouble(key));
+    }
+    return recordObject.get(key);
+  }
+
   private JSONObject getReturnRecord(WriteOperation writeOperation,
-      Class<?> entity, Object entityInstance, JSONObject recordObject)
-      throws SecurityException, JSONException, IllegalAccessException,
-      InvocationTargetException, NoSuchMethodException {
+      Object entityInstance, JSONObject recordObject,
+      Set<ConstraintViolation<Object>> violations) throws SecurityException,
+      JSONException, IllegalAccessException, InvocationTargetException,
+      NoSuchMethodException {
 
     JSONObject returnObject = new JSONObject();
-    returnObject.put("id", entity.getMethod("getId").invoke(entityInstance));
-    returnObject.put("version", entity.getMethod("getVersion").invoke(
-        entityInstance));
+    if (writeOperation != WriteOperation.CREATE || violations == null) {
+      // currently sending back only two properties.
+      for (String propertyName : new String[] {"id", "version"}) {
+        if ("version".equals(propertyName) && violations != null) {
+          continue;
+        }
+        returnObject.put(propertyName, getPropertyValueFromDataStore(
+            entityInstance, propertyName));
+      }
+    }
+    if (violations != null) {
+      returnObject.put("violations", getViolationsAsJson(violations));
+    }
     if (writeOperation == WriteOperation.CREATE) {
       returnObject.put("futureId", recordObject.getString("id"));
     }
@@ -451,13 +527,40 @@
   }
 
   /**
-   * returns true if the property has been requested. TODO: fix this hack.
-   *
+   * Swizzle an idValue received from the client to the type expected by the
+   * server. Return the object of the new type.
+   */
+  private Object getSwizzledObject(Object idValue, Class<?> idType) {
+    if (idValue.getClass() == idType) {
+      return idValue;
+    }
+    // swizzle from String to Long
+    if (idValue.getClass() == String.class && idType == Long.class) {
+      return new Long((String) idValue);
+    }
+    throw new IllegalArgumentException("id is of type: " + idValue.getClass()
+        + ",  expected type: " + idType);
+  }
+
+  private JSONObject getViolationsAsJson(
+      Set<ConstraintViolation<Object>> violations) throws JSONException {
+    JSONObject violationsAsJson = new JSONObject();
+    for (ConstraintViolation<Object> violation : violations) {
+      violationsAsJson.put(violation.getPropertyPath().toString(),
+          violation.getMessage());
+    }
+    return violationsAsJson;
+  }
+
+  /**
+   * returns true if the property has been requested. TODO: use the properties
+   * that should be coming with the request.
+   * 
    * @param p the field of entity ref
    * @return has the property value been requested
    */
   private boolean requestedProperty(Property<?> p) {
-    return PROPERTY_SET.contains(p.getName());
+    return !BLACK_LIST.contains(p.getName());
   }
 
   private void sync(String content, PrintWriter writer)
@@ -482,18 +585,16 @@
         }
         for (int i = 0; i < length; i++) {
           JSONObject recordWithSchema = reportArray.getJSONObject(i);
-          // iterator has just one element.
           Iterator<?> iterator = recordWithSchema.keys();
-          iterator.hasNext();
           String recordToken = (String) iterator.next();
-          JSONObject recordObject = recordWithSchema.getJSONObject(recordToken);
-          JSONObject returnObject = updateRecordInDataStore(recordToken,
-              recordObject, writeOperation);
-          returnArray.put(returnObject);
           if (iterator.hasNext()) {
             throw new IllegalArgumentException(
                 "There cannot be more than one record token");
           }
+          JSONObject recordObject = recordWithSchema.getJSONObject(recordToken);
+          JSONObject returnObject = updateRecordInDataStore(recordToken,
+              recordObject, writeOperation);
+          returnArray.put(returnObject);
         }
         returnJsonObject.put(writeOperation.name(), returnArray);
       }
@@ -503,12 +604,25 @@
     }
   }
 
+  /**
+   * Update propertiesInRecord based on the types of entity.
+   */
+  private void updatePropertyTypes(Map<String, Class<?>> propertiesInRecord,
+      Class<?> entity) {
+    for (Field field : entity.getDeclaredFields()) {
+      Class<?> fieldType = propertiesInRecord.get(field.getName());
+      if (fieldType != null) {
+        propertiesInRecord.put(field.getName(), field.getType());
+      }
+    }
+  }
+
   private void validateKeys(JSONObject recordObject,
-      Map<String, Class<?>> declaredProperties) {
+      Set<String> declaredProperties) {
     Iterator<?> keys = recordObject.keys();
     while (keys.hasNext()) {
       String key = (String) keys.next();
-      if (declaredProperties.get(key) == null) {
+      if (!declaredProperties.contains(key)) {
         throw new IllegalArgumentException("key " + key
             + " is not permitted to be set");
       }
diff --git a/bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java b/bikeshed/src/com/google/gwt/requestfactory/shared/Receiver.java
similarity index 75%
rename from bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java
rename to bikeshed/src/com/google/gwt/requestfactory/shared/Receiver.java
index 46883ee..b6b3971 100644
--- a/bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/Receiver.java
@@ -13,15 +13,13 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.user.client.ui;
-
-import java.util.List;
+package com.google.gwt.requestfactory.shared;
 
 /**
- * Implemented by objects that display a list of values.
+ * Implemented by objects that display values.
  *
  * @param <V> value type
  */
-public interface TakesValueList<V> {
-  void setValueList(List<V> newValues);
+public interface Receiver<V> {
+  void onSuccess(V response);
 }
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/EntityListRequest.java b/bikeshed/src/com/google/gwt/requestfactory/shared/RecordListRequest.java
similarity index 69%
rename from bikeshed/src/com/google/gwt/requestfactory/shared/EntityListRequest.java
rename to bikeshed/src/com/google/gwt/requestfactory/shared/RecordListRequest.java
index 54bbac2..f354eaf 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/EntityListRequest.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/RecordListRequest.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,26 +15,24 @@
  */
 package com.google.gwt.requestfactory.shared;
 
-import com.google.gwt.user.client.ui.TakesValueList;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
 import java.util.Collection;
+import java.util.List;
 
 /**
  * Implemented by RequestObjects for service methods that return lists of
- * entities.
- * <p>
- * TODO Really should be for lists of anything.
- *
+ * records.
+ * 
  * @param <R> The type held by the returned list
  */
-public interface EntityListRequest<R extends Record> extends
-    RequestFactory.RequestObject {
+public interface RecordListRequest<R extends Record> extends
+    RequestFactory.RequestObject<List<R>> {
 
-  EntityListRequest<R> forProperties(Collection<Property<?>> properties);
+  RecordListRequest<R> forProperties(Collection<Property<?>> properties);
 
-  EntityListRequest<R> forProperty(Property<?> property);
-
-  EntityListRequest<R> to(TakesValueList<R> target);
+  RecordListRequest<R> forProperty(Property<?> property);
+  
+  RecordListRequest<R> to(Receiver<List<R>> target);
 }
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/RecordRequest.java b/bikeshed/src/com/google/gwt/requestfactory/shared/RecordRequest.java
new file mode 100644
index 0000000..0aa6475
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/RecordRequest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.requestfactory.shared;
+
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.util.Collection;
+
+/**
+ * A Request that returns specific properties for one or more {@link Record}
+ * instances.
+ * 
+ * @param <R> return type
+ */
+public interface RecordRequest<R extends Record> extends
+    RequestFactory.RequestObject<R> {
+
+  RecordRequest<R> forProperties(Collection<Property<?>> properties);
+
+  RecordRequest<R> forProperty(Property<?> property);
+
+  RecordRequest<R> to(Receiver<R> target);
+}
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
index 6872cfb..da950ea 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -28,6 +28,8 @@
  */
 public interface RequestFactory {
 
+  // TODO all these inner interfaces are clutter, move them to their own files
+
   /**
    * Implemented by the configuration class used by
    * {@link com.google.gwt.requestfactory.server.RequestFactoryServlet
@@ -35,11 +37,12 @@
    */
   interface Config {
     Map<String, RequestDefinition> requestDefinitions();
+
     Set<Class<? extends Record>> recordTypes();
   }
 
   /**
-   * Implemented by enums that defines the mapping between request objects and
+   * Implemented by enums that define the mapping between request objects and
    * service methods.
    */
   interface RequestDefinition {
@@ -62,7 +65,13 @@
     /**
      * Returns the return type of the method to be invoked on the server.
      */
-    Class<? extends Record> getReturnType();
+    Class<?> getReturnType();
+
+    /**
+     * Returns true if the request returns Lists of {@link #getReturnType},
+     * false for single instances.
+     */
+    boolean isReturnTypeList();
 
     /**
      * Returns the name.
@@ -73,24 +82,24 @@
   /**
    * Implemented by the request objects created by this factory.
    */
-  interface RequestObject {
+  interface RequestObject<T> {
     void fire();
 
     String getRequestData();
 
     void handleResponseText(String responseText);
+
+    RequestObject<T> to(Receiver<T> receiver);
   }
 
+  // TODO: this must be configurable
   String URL = "/expenses/data";
 
-  /*
-   * eventually, this will become an enum of update operations.
-   */
-  String UPDATE_STRING = "SYNC";
+  String SYNC = "SYNC";
 
   ValueStore getValueStore();
 
-  void init(HandlerManager handlerManager);
+  void init(HandlerManager eventBus);
 
   SyncRequest syncRequest(DeltaValueStore deltaValueStore);
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ServerType.java b/bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java
similarity index 69%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ServerType.java
rename to bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java
index 920027d..e0f449c 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ServerType.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/ServerType.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.request;
+package com.google.gwt.requestfactory.shared;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -21,16 +21,12 @@
 import java.lang.annotation.Target;
 
 /**
- * Annotation on Record classes specifying 'type' and 'token'. 'type' represents
- * the server-side counterpart of the Record. 'token' is an optional String that
- * is sent in sync requests to the server. server and their type. If 'token' is
- * absent, the record name is used instead.
+ * Annotation on Record classes specifying 'type'. 'type' represents the
+ * server-side counterpart of the Record.
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.TYPE)
 public @interface ServerType {
 
-  String token() default "[UNASSIGNED]";
-
   Class<?> type();
 }
diff --git a/bikeshed/src/com/google/gwt/requestfactory/shared/SyncRequest.java b/bikeshed/src/com/google/gwt/requestfactory/shared/SyncRequest.java
index cd79676..d757b22 100644
--- a/bikeshed/src/com/google/gwt/requestfactory/shared/SyncRequest.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/SyncRequest.java
@@ -15,9 +15,13 @@
  */
 package com.google.gwt.requestfactory.shared;
 
+import java.util.Set;
+
 /**
  * Request to commit CRUD operations accumulated in a DeltaValueStore.
  */
 public interface SyncRequest {
   void fire();
+
+  SyncRequest to(Receiver<Set<SyncResult>> receiver);
 }
diff --git a/bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java b/bikeshed/src/com/google/gwt/requestfactory/shared/SyncResult.java
similarity index 67%
copy from bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java
copy to bikeshed/src/com/google/gwt/requestfactory/shared/SyncResult.java
index 46883ee..9af5329 100644
--- a/bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java
+++ b/bikeshed/src/com/google/gwt/requestfactory/shared/SyncResult.java
@@ -13,15 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.user.client.ui;
+package com.google.gwt.requestfactory.shared;
 
-import java.util.List;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.util.Map;
 
 /**
- * Implemented by objects that display a list of values.
- *
- * @param <V> value type
+ * Result per record of a SyncRequest.
  */
-public interface TakesValueList<V> {
-  void setValueList(List<V> newValues);
+public interface SyncResult {
+  boolean hasViolations();
+  
+  Record getRecord();
+  
+  Map<String, String> getViolations();
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTableRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTableRecipe.java
index 7196040..485eb47 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTableRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTableRecipe.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,8 +15,24 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.bikeshed.list.client.IdentityColumn;
+import com.google.gwt.bikeshed.list.client.CellTable;
+import com.google.gwt.cell.client.ButtonCell;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.SelectionModel;
+
+import java.util.List;
 
 /**
  * Basic table recipe.
@@ -29,7 +45,93 @@
 
   @Override
   protected Widget createWidget() {
-    // TODO
-    return new Label("Basic table demo...");
+    final ListViewAdapter<String> adapter = new ListViewAdapter<String>();
+    final CellTable<String> table = new CellTable<String>();
+    adapter.addView(table);
+
+    // Add a selection model.
+    final SelectionModel<String> selectionModel = new MultiSelectionModel<String>();
+    table.setSelectionModel(selectionModel);
+
+    // Add some data to the table
+    for (int i = 0; i < 25; ++i) {
+      adapter.getList().add("Item " + i);
+    }
+
+    // Checkbox column tied to selection.
+    Column<String, Boolean> checkboxCol = new Column<String, Boolean>(
+        new CheckboxCell()) {
+      @Override
+      public boolean dependsOnSelection() {
+        return true;
+      }
+
+      @Override
+      public Boolean getValue(String object) {
+        return selectionModel.isSelected(object);
+      }
+    };
+    table.addColumn(checkboxCol);
+    checkboxCol.setFieldUpdater(new FieldUpdater<String, Boolean>() {
+      public void update(int index, String object, Boolean value) {
+        selectionModel.setSelected(object, value);
+      }
+    });
+
+    // String column.
+    table.addColumn(new IdentityColumn<String>(new TextCell()),
+        "TextCell", "TextCell");
+
+    // Button column tied to selection.
+    Column<String, String> buttonCol = new Column<String, String>(
+        new ButtonCell()) {
+
+      @Override
+      public boolean dependsOnSelection() {
+        return true;
+      }
+
+      @Override
+      public String getValue(String object) {
+        if (selectionModel.isSelected(object)) {
+          return "Unselect";
+        } else {
+          return "Select";
+        }
+      }
+    };
+    buttonCol.setFieldUpdater(new FieldUpdater<String, String>() {
+      public void update(int index, String object, String value) {
+        selectionModel.setSelected(object, !selectionModel.isSelected(object));
+        Window.alert("You clicked: " + object);
+      }
+    });
+    table.addColumn(buttonCol, "ButtonCell", "ButtonCell");
+
+    // Add a Pager to control the table.
+    SimplePager<String> pager = new SimplePager<String>(table);
+
+    // Add buttons to increase the size of the table.
+    Button addBtn = new Button("Add Data Row", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        List<String> list = adapter.getList();
+        list.add("item " + list.size());
+      }
+    });
+    Button removeBtn = new Button("Remove Data Row", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        int size = adapter.getList().size();
+        if (size > 0) {
+          adapter.getList().remove(size - 1);
+        }
+      }
+    });
+
+    FlowPanel fp = new FlowPanel();
+    fp.add(table);
+    fp.add(pager);
+    fp.add(addBtn);
+    fp.add(removeBtn);
+    return fp;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellBrowserRecipe.java
similarity index 73%
copy from bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java
copy to bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellBrowserRecipe.java
index b163ded..b262238 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellBrowserRecipe.java
@@ -15,22 +15,22 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.list.shared.MultiSelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.bikeshed.tree.client.StandardTreeView;
+import com.google.gwt.bikeshed.tree.client.CellBrowser;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
 
 /**
- * Basic tree recipe.
+ * {@link CellBrowser} Recipe.
  */
-public class BasicTreeRecipe extends Recipe {
+public class CellBrowserRecipe extends Recipe {
 
-  public BasicTreeRecipe() {
-    super("Basic Tree");
+  public CellBrowserRecipe() {
+    super("Cell Browser");
   }
 
   @Override
@@ -45,11 +45,12 @@
       }
     });
 
-    StandardTreeView stree = new StandardTreeView(new MyTreeViewModel(
+    CellBrowser browser = new CellBrowser(new MyTreeViewModel(
         selectionModel), "...");
-    stree.setAnimationEnabled(true);
+    browser.setAnimationEnabled(true);
+    browser.setHeight("200px");
 
-    p.add(stree);
+    p.add(browser);
     p.add(new HTML("<hr>"));
     p.add(label);
 
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellListRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellListRecipe.java
new file mode 100644
index 0000000..c1f15fb
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellListRecipe.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.bikeshed.cookbook.client;
+
+import com.google.gwt.bikeshed.list.client.CellList;
+import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SingleSelectionModel;
+
+import java.util.List;
+
+/**
+ * {@link CellList} Recipe.
+ */
+public class CellListRecipe extends Recipe {
+
+  public CellListRecipe() {
+    super("Cell List");
+  }
+
+  @Override
+  protected Widget createWidget() {
+    ListViewAdapter<String> adapter = new ListViewAdapter<String>();
+    final List<String> list = adapter.getList();
+    for (int i = 0; i < 40; i++) {
+      list.add("" + ((i + 10) * 1000));
+    }
+
+    final CellList<String> cellList = new CellList<String>(new TextCell());
+    cellList.setPageSize(10);
+    final SelectionModel<String> selectionModel = new SingleSelectionModel<String>();
+    cellList.setSelectionModel(selectionModel);
+    adapter.addView(cellList);
+
+    new Timer() {
+      int index = 0;
+
+      @Override
+      public void run() {
+        if (cellList.isAttached()) {
+          incrementValue(index);
+          incrementValue(index + 15);
+          index = (index + 1) % 10;
+        }
+        schedule(100);
+      }
+
+      private void incrementValue(int i) {
+        // Set the value at index.
+        String oldValue = list.get(i);
+        int number = Integer.parseInt(oldValue);
+        String newValue = "" + (number + 1);
+        if (selectionModel.isSelected(oldValue)) {
+          // Move the selection with the value.
+          // TODO(jlabanca): Use a DTO with a unique ID instead.
+          selectionModel.setSelected(newValue, true);
+        }
+        list.set(i, newValue);
+      }
+    }.schedule(100);
+
+    // Add a Pager to control the table.
+    SimplePager<String> pager = new SimplePager<String>(cellList);
+
+    FlowPanel fp = new FlowPanel();
+    fp.add(cellList);
+    fp.add(pager);
+    return fp;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellTreeRecipe.java
similarity index 73%
rename from bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java
rename to bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellTreeRecipe.java
index b163ded..57b452b 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/CellTreeRecipe.java
@@ -15,22 +15,22 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.list.shared.MultiSelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.bikeshed.tree.client.StandardTreeView;
+import com.google.gwt.bikeshed.tree.client.CellTree;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.MultiSelectionModel;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
 
 /**
- * Basic tree recipe.
+ * {@link CellTree} recipe.
  */
-public class BasicTreeRecipe extends Recipe {
+public class CellTreeRecipe extends Recipe {
 
-  public BasicTreeRecipe() {
-    super("Basic Tree");
+  public CellTreeRecipe() {
+    super("Cell Tree");
   }
 
   @Override
@@ -45,11 +45,11 @@
       }
     });
 
-    StandardTreeView stree = new StandardTreeView(new MyTreeViewModel(
+    CellTree tree = new CellTree(new MyTreeViewModel(
         selectionModel), "...");
-    stree.setAnimationEnabled(true);
+    tree.setAnimationEnabled(true);
 
-    p.add(stree);
+    p.add(tree);
     p.add(new HTML("<hr>"));
     p.add(label);
 
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java
index 5c32123..04ee1ad 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java
@@ -15,13 +15,8 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.SingleSelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.bikeshed.tree.client.StandardTreeView;
-import com.google.gwt.bikeshed.tree.client.TreeViewModel;
+import com.google.gwt.bikeshed.tree.client.CellTree;
+import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -31,6 +26,11 @@
 import com.google.gwt.user.client.ui.LayoutPanel;
 import com.google.gwt.user.client.ui.RootLayoutPanel;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.TreeViewModel;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SingleSelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
 
 import java.util.List;
 
@@ -42,16 +42,16 @@
   interface Binder extends UiBinder<Widget, Cookbook> {
   }
 
-  static class CategoryCell extends Cell<Category, Void> {
+  static class CategoryCell extends AbstractCell<Category> {
     @Override
-    public void render(Category value, Void viewData, StringBuilder sb) {
+    public void render(Category value, Object viewData, StringBuilder sb) {
       sb.append(value.getTitle());
     }
   }
 
-  static class RecipeCell extends Cell<Recipe, Void> {
+  static class RecipeCell extends AbstractCell<Recipe> {
     @Override
-    public void render(Recipe value, Void viewData, StringBuilder sb) {
+    public void render(Recipe value, Object viewData, StringBuilder sb) {
       sb.append(value.getTitle());
     }
   }
@@ -92,11 +92,11 @@
   private static final Binder binder = GWT.create(Binder.class);
 
   @UiField DockLayoutPanel dock;
-  @UiField StandardTreeView recipeTree;
+  @UiField CellTree recipeTree;
   @UiField LayoutPanel container;
 
   private RecipeTreeModel recipeTreeModel;
-  private SimpleCellListRecipe defaultRecipe;
+  private CellListRecipe defaultRecipe;
   private Recipe curRecipe;
 
   public void onModuleLoad() {
@@ -120,18 +120,18 @@
   }
 
   @UiFactory
-  StandardTreeView createTreeView() {
-    return new StandardTreeView(recipeTreeModel, null);
+  CellTree createTreeView() {
+    return new CellTree(recipeTreeModel, null);
   }
 
   private void createRecipes(List<Category> cats) {
-    defaultRecipe = new SimpleCellListRecipe();
+    defaultRecipe = new CellListRecipe();
 
     cats.add(new Category("Lists", new Recipe[] {defaultRecipe}));
     cats.add(new Category("Tables", new Recipe[] {
         new BasicTableRecipe(), new EditableTableRecipe(),}));
     cats.add(new Category("Trees", new Recipe[] {
-        new BasicTreeRecipe(), new SideBySideTreeRecipe(),}));
+        new CellTreeRecipe(), new CellBrowserRecipe(),}));
     cats.add(new Category("Other", new Recipe[] {
         new ValidationRecipe(), new MailRecipe(),}));
   }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.ui.xml
index 932e99d..bf0473c 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.ui.xml
@@ -11,7 +11,7 @@
 			<g:HTML styleName='{styles.common.header}'>Google Web Toolkit Cookbook</g:HTML>
 		</g:north>
 		<g:west size="16">
-			<t:StandardTreeView styleName='{styles.common.box}' ui:field='recipeTree'/>
+			<t:CellTree styleName='{styles.common.box}' ui:field='recipeTree'/>
 		</g:west>
 		<g:center>
 		  <g:LayoutPanel styleName='{styles.common.box}' ui:field='container'/>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java
index db8de65..4c3536f 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/EditableTableRecipe.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,18 +15,18 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.cells.client.EditTextCell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.cells.client.TextCell;
 import com.google.gwt.bikeshed.list.client.Column;
 import com.google.gwt.bikeshed.list.client.Header;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
+import com.google.gwt.bikeshed.list.client.CellTable;
+import com.google.gwt.cell.client.EditTextCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.cell.client.TextCell;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.ListViewAdapter;
 
 import java.util.List;
 
@@ -35,7 +35,7 @@
  */
 public class EditableTableRecipe extends Recipe {
 
-  private static class EditTextColumn extends Column<String, String, String> {
+  private static class EditTextColumn extends Column<String, String> {
     public EditTextColumn() {
       super(new EditTextCell());
     }
@@ -53,7 +53,8 @@
   @Override
   protected Widget createWidget() {
     final ListViewAdapter<String> adapter = new ListViewAdapter<String>();
-    final PagingTableListView<String> table = new PagingTableListView<String>(10);
+    final CellTable<String> table = new CellTable<String>(
+        10);
     adapter.addView(table);
 
     for (int i = 0; i < 25; ++i) {
@@ -61,7 +62,7 @@
     }
 
     EditTextColumn column = new EditTextColumn();
-    Header<String> header = new Header<String>(TextCell.getInstance()) {
+    Header<String> header = new Header<String>(new TextCell()) {
       @Override
       public String getValue() {
         return "<b>item</b>";
@@ -69,20 +70,23 @@
     };
     table.addColumn(column, header);
 
-    column.setFieldUpdater(new FieldUpdater<String, String, String>() {
-      public void update(int index, String object, String value, String viewData) {
+    column.setFieldUpdater(new FieldUpdater<String, String>() {
+      public void update(int index, String object, String value) {
         adapter.getList().set(index, value);
       }
     });
 
-    FlowPanel plusMinusPanel = new FlowPanel();
-    Button addBtn = new Button("+", new ClickHandler() {
+    // Add a Pager to control the table.
+    SimplePager<String> pager = new SimplePager<String>(table);
+
+    // Add buttons to increase the size of the table.
+    Button addBtn = new Button("Add Data Row", new ClickHandler() {
       public void onClick(ClickEvent event) {
         List<String> list = adapter.getList();
         list.add("" + list.size());
       }
     });
-    Button removeBtn = new Button("-", new ClickHandler() {
+    Button removeBtn = new Button("Remove Data Row", new ClickHandler() {
       public void onClick(ClickEvent event) {
         int size = adapter.getList().size();
         if (size > 0) {
@@ -90,27 +94,12 @@
         }
       }
     });
-    plusMinusPanel.add(addBtn);
-    plusMinusPanel.add(removeBtn);
-
-    FlowPanel nextPrevPanel = new FlowPanel();
-    Button prevBtn = new Button("<", new ClickHandler() {
-      public void onClick(ClickEvent event) {
-        table.setPageStart(table.getPageStart() - table.getPageSize());
-      }
-    });
-    Button nextBtn = new Button(">", new ClickHandler() {
-      public void onClick(ClickEvent event) {
-        table.setPageStart(table.getPageStart() + table.getPageSize());
-      }
-    });
-    nextPrevPanel.add(prevBtn);
-    nextPrevPanel.add(nextBtn);
 
     FlowPanel fp = new FlowPanel();
     fp.add(table);
-    fp.add(nextPrevPanel);
-    fp.add(plusMinusPanel);
+    fp.add(pager);
+    fp.add(addBtn);
+    fp.add(removeBtn);
     return fp;
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java
index f17ee11..475f700 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MailRecipe.java
@@ -15,21 +15,17 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.cells.client.ButtonCell;
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.CheckboxCell;
-import com.google.gwt.bikeshed.cells.client.ClickableTextCell;
-import com.google.gwt.bikeshed.cells.client.DatePickerCell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.cells.client.TextCell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.bikeshed.list.client.Column;
 import com.google.gwt.bikeshed.list.client.Header;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.client.SimpleColumn;
-import com.google.gwt.bikeshed.list.shared.DefaultSelectionModel;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
+import com.google.gwt.cell.client.ButtonCell;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.ClickableTextCell;
+import com.google.gwt.cell.client.DatePickerCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.KeyUpEvent;
@@ -42,6 +38,9 @@
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.DefaultSelectionModel;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.ProvidesKey;
 
 import java.util.Collections;
 import java.util.Comparator;
@@ -282,7 +281,7 @@
 
   private MailSelectionModel selectionModel = new MailSelectionModel();
 
-  private PagingTableListView<Message> table;
+  private CellTable<Message> table;
 
   public MailRecipe() {
     super("Mail");
@@ -314,7 +313,7 @@
 
     addMessages(10);
 
-    table = new PagingTableListView<Message>(10);
+    table = new CellTable<Message>(10);
     table.setSelectionModel(selectionModel);
     adapter.addView(table);
 
@@ -332,8 +331,8 @@
         return selectionModel.getType().equals("ALL");
       }
     };
-    selectedHeader.setUpdater(new ValueUpdater<Boolean, Void>() {
-      public void update(Boolean value, Void viewData) {
+    selectedHeader.setUpdater(new ValueUpdater<Boolean>() {
+      public void update(Boolean value) {
         if (value == true) {
           selectionModel.setType("ALL");
         } else if (value == false) {
@@ -343,7 +342,7 @@
     });
     table.addColumn(selectedColumn, selectedHeader);
 
-    addColumn(table, "ID", TextCell.getInstance(),
+    addColumn(table, "ID", new TextCell(),
         new GetValue<Message, String>() {
           public String getValue(Message object) {
             return "" + object.id;
@@ -356,14 +355,14 @@
       }
     });
 
-    Column<Message, Date, Void> dateColumn = addColumn(table, "Date",
-        new DatePickerCell<Void>(), new GetValue<Message, Date>() {
+    Column<Message, Date> dateColumn = addColumn(table, "Date",
+        new DatePickerCell(), new GetValue<Message, Date>() {
           public Date getValue(Message object) {
             return object.date;
           }
         }, dateComparator);
-    dateColumn.setFieldUpdater(new FieldUpdater<Message, Date, Void>() {
-      public void update(int index, Message object, Date value, Void viewData) {
+    dateColumn.setFieldUpdater(new FieldUpdater<Message, Date>() {
+      public void update(int index, Message object, Date value) {
         Window.alert("Changed date from " + object.date + " to " + value);
         object.date = value;
         table.refresh();
@@ -382,15 +381,15 @@
       }
     });
 
-    SimpleColumn<Message, String> toggleColumn = new SimpleColumn<Message, String>(
-        ButtonCell.getInstance()) {
+    Column<Message, String> toggleColumn = new Column<Message, String>(
+        new ButtonCell()) {
       @Override
       public String getValue(Message object) {
         return object.isRead ? "Mark Unread" : "Mark Read";
       }
     };
-    toggleColumn.setFieldUpdater(new FieldUpdater<Message, String, Void>() {
-      public void update(int index, Message object, String value, Void viewData) {
+    toggleColumn.setFieldUpdater(new FieldUpdater<Message, String>() {
+      public void update(int index, Message object, String value) {
         object.isRead = !object.isRead;
         messages.set(index, object);
       }
@@ -434,26 +433,26 @@
     return p;
   }
 
-  private <C extends Comparable<C>> Column<Message, C, Void> addColumn(
-      PagingTableListView<Message> table, final String text,
-      final Cell<C, Void> cell, final GetValue<Message, C> getter,
+  private <C extends Comparable<C>> Column<Message, C> addColumn(
+      CellTable<Message> table, final String text,
+      final Cell<C> cell, final GetValue<Message, C> getter,
       final Comparator<Message> comparator) {
-    Column<Message, C, Void> column = new Column<Message, C, Void>(cell) {
+    Column<Message, C> column = new Column<Message, C>(cell) {
       @Override
       public C getValue(Message object) {
         return getter.getValue(object);
       }
     };
-    Header<String> header = new Header<String>(ClickableTextCell.getInstance()) {
+    Header<String> header = new Header<String>(new ClickableTextCell()) {
       @Override
       public String getValue() {
         return text;
       }
     };
-    header.setUpdater(new ValueUpdater<String, Void>() {
+    header.setUpdater(new ValueUpdater<String>() {
       boolean sortUp = true;
 
-      public void update(String value, Void viewData) {
+      public void update(String value) {
         if (comparator == null) {
           sortMessages(new Comparator<Message>() {
             public int compare(Message o1, Message o2) {
@@ -470,10 +469,10 @@
     return column;
   }
 
-  private Column<Message, String, Void> addColumn(
-      PagingTableListView<Message> table, final String text,
+  private Column<Message, String> addColumn(
+      CellTable<Message> table, final String text,
       final GetValue<Message, String> getter) {
-    return addColumn(table, text, TextCell.getInstance(), getter, null);
+    return addColumn(table, text, new TextCell(), getter, null);
   }
 
   private void addMessages(int count) {
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java
index 93fe842..cf83d50 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java
@@ -15,21 +15,22 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.cells.client.ButtonCell;
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.CheckboxCell;
-import com.google.gwt.bikeshed.cells.client.CompositeCell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.bikeshed.list.client.HasCell;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.tree.client.TreeViewModel;
+import com.google.gwt.cell.client.ButtonCell;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.CompositeCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.cell.client.HasCell;
+import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.view.client.AbstractListViewAdapter;
+import com.google.gwt.view.client.TreeViewModel;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.SelectionModel;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -98,20 +99,20 @@
   /**
    * The cell used to render integers.
    */
-  private static final Cell<Integer, Void> INTEGER_CELL = new Cell<Integer, Void>() {
+  private static final Cell<Integer> INTEGER_CELL = new AbstractCell<Integer>() {
     @Override
-    public void render(Integer value, Void viewData, StringBuilder sb) {
+    public void render(Integer value, Object viewData, StringBuilder sb) {
       sb.append(value);
     }
   };
 
-  private CompositeCell<String, Void> compositeCell = new CompositeCell<String, Void>();
+  private CompositeCell<String> compositeCell = new CompositeCell<String>();
   private SelectionModel<String> selectionModel;
 
   public MyTreeViewModel(final SelectionModel<String> selectionModel) {
     this.selectionModel = selectionModel;
-    compositeCell.addHasCell(new HasCell<String, Boolean, Void>() {
-      public Cell<Boolean, Void> getCell() {
+    compositeCell.addHasCell(new HasCell<String, Boolean>() {
+      public Cell<Boolean> getCell() {
         return new CheckboxCell() {
           @Override
           public boolean dependsOnSelection() {
@@ -120,10 +121,9 @@
         };
       }
 
-      public FieldUpdater<String, Boolean, Void> getFieldUpdater() {
-        return new FieldUpdater<String, Boolean, Void>() {
-          public void update(int index, String object, Boolean value,
-              Void viewData) {
+      public FieldUpdater<String, Boolean> getFieldUpdater() {
+        return new FieldUpdater<String, Boolean>() {
+          public void update(int index, String object, Boolean value) {
             selectionModel.setSelected(object, value);
           }
         };
@@ -133,16 +133,15 @@
         return selectionModel.isSelected(object);
       }
     });
-    compositeCell.addHasCell(new HasCell<String, String, Void>() {
-      public Cell<String, Void> getCell() {
-        return ButtonCell.getInstance();
+    compositeCell.addHasCell(new HasCell<String, String>() {
+      public Cell<String> getCell() {
+        return new ButtonCell();
       }
 
-      public FieldUpdater<String, String, Void> getFieldUpdater() {
-        return new FieldUpdater<String, String, Void>() {
-          public void update(int index, String object, String value,
-              Void viewData) {
-            Window.alert("Clicked " + object);
+      public FieldUpdater<String, String> getFieldUpdater() {
+        return new FieldUpdater<String, String>() {
+          public void update(int index, String object, String value) {
+            Window.alert("You clicked " + object);
           }
         };
       }
@@ -177,8 +176,8 @@
       AbstractListViewAdapter<Integer> adapter = new IntegerListViewAdapter(
           value.length());
       return new DefaultNodeInfo<Integer>(adapter, INTEGER_CELL, null,
-          new ValueUpdater<Integer, Void>() {
-            public void update(Integer value, Void viewData) {
+          new ValueUpdater<Integer>() {
+            public void update(Integer value) {
               Window.alert("Integer = " + value);
             }
           });
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java
index a9edc1c..9612e9d 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ScrollbarPager.java
@@ -15,8 +15,7 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.list.client.PagingListView;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.event.dom.client.ScrollEvent;
 import com.google.gwt.event.dom.client.ScrollHandler;
@@ -25,6 +24,7 @@
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.ScrollPanel;
 import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.view.client.PagingListView;
 
 /**
  * A pager for controlling a PagingListView that uses a native scrollbar.
@@ -42,11 +42,11 @@
   private int jump;
   private int pageSize;
   private ScrollPanel panel;
-  private PagingTableListView<T> view;
+  private CellTable<T> view;
   private int spaceAbove;
   private HTML spacer;
 
-  public ScrollbarPager(PagingTableListView<T> view) {
+  public ScrollbarPager(CellTable<T> view) {
     this.jump = getScrollPageAmount();
     this.panel = new ScrollPanel();
     this.height = view.getBodyHeight();
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SelectionColumn.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SelectionColumn.java
index 3f71a7e..9568f96 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SelectionColumn.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SelectionColumn.java
@@ -15,10 +15,10 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.cells.client.CheckboxCell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.list.client.SimpleColumn;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.view.client.SelectionModel;
 
 /**
  * A column that displays a checkbox that is synchronized with a given
@@ -26,14 +26,14 @@
  * 
  * @param <T> the record data type, used by the row and the selection model
  */
-public class SelectionColumn<T> extends SimpleColumn<T, Boolean> {
+public class SelectionColumn<T> extends Column<T, Boolean> {
   
   private final SelectionModel<T> selectionModel;
 
   public SelectionColumn(final SelectionModel<T> selectionModel) {
     super(new CheckboxCell());
-    setFieldUpdater(new FieldUpdater<T, Boolean, Void>() {
-      public void update(int index, T object, Boolean value, Void viewData) {
+    setFieldUpdater(new FieldUpdater<T, Boolean>() {
+      public void update(int index, T object, Boolean value) {
         selectionModel.setSelected(object, value);
       }
     });
@@ -47,6 +47,6 @@
   
   @Override
   public Boolean getValue(T object) {
-    return selectionModel.isSelected(object);
+    return object != null && selectionModel.isSelected(object);
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SideBySideTreeRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SideBySideTreeRecipe.java
deleted file mode 100644
index 5c18d2e..0000000
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SideBySideTreeRecipe.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.bikeshed.cookbook.client;
-
-import com.google.gwt.bikeshed.list.shared.MultiSelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.bikeshed.tree.client.SideBySideTreeView;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.Widget;
-
-/**
- * SideBySideTree Recipe.
- */
-public class SideBySideTreeRecipe extends Recipe {
-
-  public SideBySideTreeRecipe() {
-    super("Side-by-side Tree");
-  }
-
-  @Override
-  protected Widget createWidget() {
-    FlowPanel p = new FlowPanel();
-
-    final Label label = new Label();
-    final MultiSelectionModel<String> selectionModel = new MultiSelectionModel<String>();
-    selectionModel.addSelectionChangeHandler(new SelectionModel.SelectionChangeHandler() {
-      public void onSelectionChange(SelectionChangeEvent event) {
-        label.setText("Selected " + selectionModel.getSelectedSet().toString());
-      }
-    });
-
-    SideBySideTreeView sstree = new SideBySideTreeView(new MyTreeViewModel(
-        selectionModel), "...");
-    sstree.setAnimationEnabled(true);
-    sstree.setHeight("200px");
-
-    p.add(sstree);
-    p.add(new HTML("<hr>"));
-    p.add(label);
-
-    return p;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimpleCellListRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimpleCellListRecipe.java
deleted file mode 100644
index b30250d..0000000
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimpleCellListRecipe.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.bikeshed.cookbook.client;
-
-import com.google.gwt.bikeshed.cells.client.TextCell;
-import com.google.gwt.bikeshed.list.client.SimpleCellList;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.List;
-
-/**
- * SimpleCellList Recipe.
- */
-public class SimpleCellListRecipe extends Recipe {
-
-  public SimpleCellListRecipe() {
-    super("Simple Cell List");
-  }
-
-  @Override
-  protected Widget createWidget() {
-    ListViewAdapter<String> adapter = new ListViewAdapter<String>();
-    final List<String> list = adapter.getList();
-    for (int i = 0; i < 40; i++) {
-      list.add("" + ((i + 10) * 1000));
-    }
-
-    SimpleCellList<String> simpleCellList =
-      new SimpleCellList<String>(TextCell.getInstance(), 10, 5);
-    adapter.addView(simpleCellList);
-
-    new Timer() {
-      int index = 0;
-
-      @Override
-      public void run() {
-          list.set(index, "" + (Integer.parseInt(list.get(index)) + 1));
-          list.set(index + 15, "" + (Integer.parseInt(list.get(index + 15)) + 1));
-          index = (index + 1) % 10;
-          schedule(100);
-      }
-    }.schedule(100);
-
-    return simpleCellList;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java
index 0ec3273..63833d0 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SimplePager.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,34 +15,31 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.list.client.PagingListView;
+import com.google.gwt.bikeshed.list.client.AbstractPager;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.view.client.PagingListView;
 
 /**
- * A pager for controlling a PagingListView that uses a series of buttons
- * for demo purposes.
- *
+ * A pager for controlling a PagingListView that uses a series of buttons for
+ * demo purposes.
+ * 
  * @param <T> the type of the PagingListView being controlled
  */
-public class SimplePager<T> extends Composite implements PagingListView.Pager<T>,
-    ClickHandler {
+public class SimplePager<T> extends AbstractPager<T> implements ClickHandler {
 
-  private int dataSize;
   private Button nextPageButton;
-  private int pageSize;
-  private int pageStart;
   private Button prevPageButton;
   private Button remove1Button;
   private Button remove5Button;
-  private PagingListView<T> view;
   private Label infoLabel;
+  private PagingListView<T> view;
 
   public SimplePager(PagingListView<T> view) {
+    super(view);
     FlowPanel p = new FlowPanel();
     p.add(prevPageButton = makeButton("Previous Page", "PREV"));
     p.add(nextPageButton = makeButton("Next Page", "NEXT"));
@@ -54,7 +51,6 @@
     initWidget(p);
 
     this.view = view;
-    view.setPager(this);
   }
 
   /**
@@ -62,45 +58,20 @@
    * additional rows to be displayed.
    */
   public boolean canAddRows(int rows) {
-    return dataSize - pageSize >= rows;
+    return view.getDataSize() - view.getPageSize() >= rows;
   }
 
   /**
-   * Returns true if the page size is sufficient to allow a given number of
-   * rows to be removed.
+   * Returns true if the page size is sufficient to allow a given number of rows
+   * to be removed.
    */
   public boolean canRemoveRows(int rows) {
-    return pageSize > rows;
-  }
-
-  /**
-   * Returns true if there is enough data such that a call to
-   * {@link #nextPage()} will succeed in moving the starting point of the
-   * table forward.
-   */
-  public boolean hasNextPage() {
-    return pageStart + pageSize < dataSize;
-  }
-
-  /**
-   * Returns true if there is enough data such that a call to
-   * {@link #previousPage()} will succeed in moving the starting point of the
-   * table backward.
-   */
-  public boolean hasPreviousPage() {
-    return pageStart > 0 && dataSize > 0;
-  }
-
-  /**
-   * Advance the starting row by 'pageSize' rows.
-   */
-  public void nextPage() {
-    view.setPageStart(pageStart + pageSize);
+    return view.getPageSize() > rows;
   }
 
   public void onClick(ClickEvent event) {
     String id = ((Button) event.getSource()).getElement().getId();
-    
+
     if ("NEXT".equals(id)) {
       nextPage();
     } else if ("PREV".equals(id)) {
@@ -113,22 +84,14 @@
     updateButtons();
   }
 
+  @Override
   public void onRangeOrSizeChanged(PagingListView<T> listView) {
-    this.pageSize = listView.getPageSize();
-    this.pageStart = listView.getPageStart();
-    this.dataSize = listView.getDataSize();
+    super.onRangeOrSizeChanged(listView);
     updateButtons();
   }
 
-  /**
-   * Move the starting row back by 'pageSize' rows.
-   */
-  public void previousPage() {
-    view.setPageStart(pageStart - pageSize);
-  }
-
   private void addRows(int rows) {
-    view.setPageSize(pageSize + rows);
+    view.setPageSize(view.getPageSize() + rows);
   }
 
   private Button makeButton(String label, String id) {
@@ -139,7 +102,7 @@
   }
 
   private void removeRows(int rows) {
-    view.setPageSize(pageSize - rows);
+    view.setPageSize(view.getPageSize() - rows);
   }
 
   private void updateButtons() {
@@ -148,9 +111,11 @@
     prevPageButton.setEnabled(hasPreviousPage());
     nextPageButton.setEnabled(hasNextPage());
 
-    int page = (pageStart / pageSize) + 1;
-    int numPages = (dataSize + pageSize - 1) / pageSize;
-    infoLabel.setText("Page " + page + " of " + numPages + ": Page Start = " + pageStart + ", Page Size = " +
-        pageSize + ", Data Size = " + dataSize);
+    int page = (view.getPageStart() / view.getPageSize()) + 1;
+    int numPages = (view.getDataSize() + view.getPageSize() - 1)
+        / view.getPageSize();
+    infoLabel.setText("Page " + page + " of " + numPages + ": Page Start = "
+        + view.getPageStart() + ", Page Size = " + view.getPageSize()
+        + ", Data Size = " + view.getDataSize());
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidatableInputCell.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidatableInputCell.java
index 74079fe..f6137d9 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidatableInputCell.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidatableInputCell.java
@@ -15,18 +15,47 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.sample.bikeshed.cookbook.client.ValidatableField.DefaultValidatableField;
+import com.google.gwt.view.client.HasViewData;
 
 /**
- * A String {@link Cell} that supports validation using a
+ * A String {@link AbstractCell} that supports validation using a
  * {@link ValidatableField}.
  */
-public class ValidatableInputCell extends Cell<String, ValidatableField<String>> {
+public class ValidatableInputCell extends AbstractCell<String> {
+
+  /**
+   * Marks the cell as invalid.
+   * 
+   * @param container the viewData provider (usually a column or list view)
+   * @param key the key identifying this item
+   * @param value the new, invalid value
+   */
+  @SuppressWarnings("unchecked") // cast to ValidatableField<String>
+  public static void invalidate(HasViewData container, Object key, String value) {
+    ValidatableField<String> vf = (ValidatableField<String>) container.getViewData(key);
+    if (vf == null) {
+      vf = new DefaultValidatableField<String>(value);
+    }
+
+    vf.setInvalid(true);
+    container.setViewData(key, vf);
+  }
+
+  /**
+   * Marks the cell as valid.
+   * 
+   * @param container the viewData provider (usually a column or list view)
+   * @param key the key identifying this item
+   */
+  public static void validate(HasViewData container, Object key) {
+    container.setViewData(key, null);
+  }
 
   @Override
   public boolean consumesEvents() {
@@ -34,9 +63,12 @@
   }
 
   @Override
-  public ValidatableField<String> onBrowserEvent(Element parent, String value,
-      ValidatableField<String> viewData, NativeEvent event,
-      ValueUpdater<String, ValidatableField<String>> valueUpdater) {
+  @SuppressWarnings("unchecked") // cast to ValidatableField<String>
+  public Object onBrowserEvent(Element parent, String value,
+      Object viewData, NativeEvent event,
+      ValueUpdater<String> valueUpdater) {
+    ValidatableField<String> vf = (ValidatableField<String>) viewData;
+
     if (event.getType().equals("change")) {
       InputElement input = parent.getFirstChild().cast();
 
@@ -44,25 +76,28 @@
       input.getStyle().setColor("blue");
 
       // Create a new ValidatableField if needed
-      if (viewData == null) {
-        viewData = new DefaultValidatableField<String>(input.getValue());
+      if (vf == null) {
+        vf = new DefaultValidatableField<String>(input.getValue());
       }
-      viewData.setValue(input.getValue());
-      valueUpdater.update(value, viewData);
+      vf.setValue(input.getValue());
+      valueUpdater.update(vf.getValue());
     }
 
-    return viewData;
+    return vf;
   }
 
   @Override
-  public void render(String value, ValidatableField<String> viewData, StringBuilder sb) {
+  @SuppressWarnings("unchecked") // cast to ValidatableField<String>
+  public void render(String value, Object viewData, StringBuilder sb) {
+    ValidatableField<String> vf = (ValidatableField<String>) viewData;
+
     /*
      * If viewData is null, just paint the contents black. If it is non-null,
      * show the pending value and paint the contents red if they are known to be
      * invalid.
      */
-    String pendingValue = viewData == null ? null : viewData.getValue();
-    boolean invalid = viewData == null ? false : viewData.isInvalid();
+    String pendingValue = vf == null ? null : vf.getValue();
+    boolean invalid = vf == null ? false : vf.isInvalid();
 
     sb.append("<input type=\"text\" value=\"");
     if (pendingValue != null) {
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidationRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidationRecipe.java
index cdfeada..177ced7 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidationRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/ValidationRecipe.java
@@ -15,14 +15,13 @@
  */
 package com.google.gwt.sample.bikeshed.cookbook.client;
 
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
 import com.google.gwt.bikeshed.list.client.Column;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.bikeshed.list.client.TextColumn;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
+import com.google.gwt.cell.client.FieldUpdater;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.ListViewAdapter;
 
 import java.util.List;
 
@@ -65,7 +64,7 @@
   }
 
   public static boolean zipInvalid(int zip) {
-    return zip % 3 == 0;
+    return (zip < 0) || (zip % 3 == 0);
   }
 
   public ValidationRecipe() {
@@ -85,12 +84,7 @@
       list.add(new Address("GA", zip));
     }
 
-    PagingTableListView<Address> table = new PagingTableListView<Address>(10);
-    table.setProvidesKey(new ProvidesKey<Address>() {
-      public Object getKey(Address object) {
-        return object.key;
-      }
-    });
+    CellTable<Address> table = new CellTable<Address>(10);
     adapter.addView(table);
     TextColumn<Address> stateColumn = new TextColumn<Address>() {
       @Override
@@ -99,41 +93,42 @@
       }
     };
 
-    Column<Address, String, ValidatableField<String>> zipColumn =
-      new Column<Address, String, ValidatableField<String>>(
-        new ValidatableInputCell()) {
+    final Column<Address, String> zipColumn =
+      new Column<Address, String>(new ValidatableInputCell()) {
       @Override
       public String getValue(Address object) {
         return object.zip;
       }
     };
-    zipColumn.setFieldUpdater(new FieldUpdater<Address, String, ValidatableField<String>>() {
-      public void update(final int index, final Address object,
-          final String value, final ValidatableField<String> viewData) {
+    zipColumn.setFieldUpdater(new FieldUpdater<Address, String>() {
+      public void update(final int index, final Address address,
+          final String value) {
         // Perform validation after a 2-second delay
         new Timer() {
           @Override
           public void run() {
-            String pendingValue = viewData.getValue();
-
+            // Determine whether we have a valid zip code.
             int zip;
             try {
-              zip = Integer.parseInt(pendingValue);
+              zip = Integer.parseInt(value);
             } catch (NumberFormatException e) {
               zip = -1;
             }
             boolean zipInvalid = ValidationRecipe.zipInvalid(zip);
 
-            final Address newValue = new Address(object);
-            newValue.zip = pendingValue == null ? value : pendingValue;
-            newValue.zipInvalid = zipInvalid;
+            // Update the value.
+            final Address newAddress = new Address(address);
+            newAddress.zip = value;
+            newAddress.zipInvalid = zipInvalid;
 
-            viewData.setInvalid(zipInvalid);
-            if (!zipInvalid) {
-              viewData.setValue(null);
+            if (zipInvalid) {
+              ValidatableInputCell.invalidate(zipColumn, newAddress,
+                  newAddress.zip);
+            } else {
+              ValidatableInputCell.validate(zipColumn, newAddress);
             }
 
-            list.set(index, newValue);
+            list.set(index, newAddress);
           }
         }.schedule(2000);
       }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/ChangeCell.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/ChangeCell.java
index 0969bcb..79dd6b7 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/ChangeCell.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/ChangeCell.java
@@ -15,16 +15,16 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.cell.client.AbstractCell;
 
 /**
  * A cell that represents a
  * {@link com.google.gwt.sample.bikeshed.stocks.shared.StockQuote StockQuote}.
  */
-public class ChangeCell extends Cell<String, Void> {
+public class ChangeCell extends AbstractCell<String> {
 
   @Override
-  public void render(String value, Void viewData, StringBuilder sb) {
+  public void render(String value, Object viewData, StringBuilder sb) {
     if (value == null || value.length() == 0) {
       return;
     }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
index 1f0f9d5..9667b2b 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
@@ -15,12 +15,11 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.ButtonCell;
-import com.google.gwt.bikeshed.cells.client.CheckboxCell;
-import com.google.gwt.bikeshed.cells.client.CurrencyCell;
-import com.google.gwt.bikeshed.cells.client.ProfitLossCell;
-import com.google.gwt.bikeshed.cells.client.TextCell;
-import com.google.gwt.bikeshed.list.client.SimpleColumn;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.cell.client.ButtonCell;
+import com.google.gwt.cell.client.CheckboxCell;
+import com.google.gwt.cell.client.CurrencyCell;
+import com.google.gwt.cell.client.TextCell;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
 import com.google.gwt.sample.bikeshed.stocks.shared.Transaction;
 
@@ -29,15 +28,15 @@
  */
 public class Columns {
 
-  static SimpleColumn<StockQuote, String> buyColumn = new SimpleColumn<StockQuote, String>(
-      ButtonCell.getInstance()) {
+  static Column<StockQuote, String> buyColumn = new Column<StockQuote, String>(
+      new ButtonCell()) {
     @Override
     public String getValue(StockQuote object) {
       return "Buy";
     }
   };
 
-  static SimpleColumn<StockQuote, String> changeColumn = new SimpleColumn<StockQuote, String>(
+  static Column<StockQuote, String> changeColumn = new Column<StockQuote, String>(
       new ChangeCell()) {
     @Override
     public String getValue(StockQuote object) {
@@ -45,7 +44,7 @@
     }
   };
 
-  static SimpleColumn<StockQuote, Integer> dollarsColumn = new SimpleColumn<StockQuote, Integer>(
+  static Column<StockQuote, Integer> dollarsColumn = new Column<StockQuote, Integer>(
       new CurrencyCell()) {
     @Override
     public Integer getValue(StockQuote object) {
@@ -53,7 +52,7 @@
     }
   };
 
-  static SimpleColumn<StockQuote, Boolean> favoriteColumn = new SimpleColumn<StockQuote, Boolean>(
+  static Column<StockQuote, Boolean> favoriteColumn = new Column<StockQuote, Boolean>(
       new CheckboxCell()) {
     @Override
     public Boolean getValue(StockQuote object) {
@@ -64,7 +63,7 @@
   // TODO - use an ellipsis cell
   static HighlightingTextCell nameCell = new HighlightingTextCell();
 
-  static SimpleColumn<StockQuote, String> nameColumn = new SimpleColumn<StockQuote, String>(
+  static Column<StockQuote, String> nameColumn = new Column<StockQuote, String>(
       nameCell) {
     @Override
     public String getValue(StockQuote object) {
@@ -72,7 +71,7 @@
     }
   };
 
-  static SimpleColumn<StockQuote, Integer> priceColumn = new SimpleColumn<StockQuote, Integer>(
+  static Column<StockQuote, Integer> priceColumn = new Column<StockQuote, Integer>(
       new CurrencyCell()) {
     @Override
     public Integer getValue(StockQuote object) {
@@ -80,7 +79,7 @@
     }
   };
 
-  static SimpleColumn<StockQuote, Integer> profitLossColumn = new SimpleColumn<StockQuote, Integer>(
+  static Column<StockQuote, Integer> profitLossColumn = new Column<StockQuote, Integer>(
       new ProfitLossCell()) {
     @Override
     public Integer getValue(StockQuote object) {
@@ -88,26 +87,26 @@
     }
   };
 
-  static SimpleColumn<StockQuote, String> sellColumn = new SimpleColumn<StockQuote, String>(
-      ButtonCell.getInstance()) {
+  static Column<StockQuote, String> sellColumn = new Column<StockQuote, String>(
+      new ButtonCell()) {
     @Override
     public String getValue(StockQuote object) {
       return "Sell";
     }
   };
 
-  static TextCell textCell = TextCell.getInstance();
+  static TextCell textCell = new TextCell();
 
-  static SimpleColumn<StockQuote, String> sharesColumn =
-    new SimpleColumn<StockQuote, String>(textCell) {
+  static Column<StockQuote, String> sharesColumn =
+    new Column<StockQuote, String>(textCell) {
     @Override
     public String getValue(StockQuote object) {
       return "" + object.getSharesOwned();
     }
   };
 
-  static SimpleColumn<Transaction, String> subtotalColumn =
-    new SimpleColumn<Transaction, String>(textCell) {
+  static Column<Transaction, String> subtotalColumn =
+    new Column<Transaction, String>(textCell) {
     @Override
     public String getValue(Transaction object) {
       int price = object.getActualPrice() * object.getQuantity();
@@ -116,16 +115,16 @@
     }
   };
 
-  static SimpleColumn<StockQuote, String> tickerColumn =
-    new SimpleColumn<StockQuote, String>(textCell) {
+  static Column<StockQuote, String> tickerColumn =
+    new Column<StockQuote, String>(textCell) {
     @Override
     public String getValue(StockQuote object) {
       return object.getTicker();
     }
   };
 
-  static SimpleColumn<Transaction, String> transactionColumn =
-    new SimpleColumn<Transaction, String>(textCell) {
+  static Column<Transaction, String> transactionColumn =
+    new Column<Transaction, String>(textCell) {
     @Override
     public String getValue(Transaction object) {
       return object.toString();
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.java
index d9ad5ba..811be2a 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.java
@@ -15,8 +15,7 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -24,6 +23,7 @@
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.AbstractListViewAdapter;
 
 /**
  * Widget for favorite stocks.
@@ -33,7 +33,7 @@
   interface Binder extends UiBinder<Widget, FavoritesWidget> { }
   private static final Binder binder = GWT.create(Binder.class);
 
-  @UiField PagingTableListView<StockQuote> listView;
+  @UiField CellTable<StockQuote> table;
 
   private final AbstractListViewAdapter<StockQuote> adapter;
 
@@ -41,19 +41,19 @@
     this.adapter = adapter;
     initWidget(binder.createAndBindUi(this));
 
-    listView.addColumn(Columns.tickerColumn, "ticker");
-    listView.addColumn(Columns.priceColumn, "price");
-    listView.addColumn(Columns.changeColumn, "change");
-    listView.addColumn(Columns.sharesColumn, "shares");
-    listView.addColumn(Columns.dollarsColumn, "value");
-    listView.addColumn(Columns.profitLossColumn, "profit/loss");
-    listView.addColumn(Columns.buyColumn);
-    listView.addColumn(Columns.sellColumn);
+    table.addColumn(Columns.tickerColumn, "ticker");
+    table.addColumn(Columns.priceColumn, "price");
+    table.addColumn(Columns.changeColumn, "change");
+    table.addColumn(Columns.sharesColumn, "shares");
+    table.addColumn(Columns.dollarsColumn, "value");
+    table.addColumn(Columns.profitLossColumn, "profit/loss");
+    table.addColumn(Columns.buyColumn);
+    table.addColumn(Columns.sellColumn);
   }
 
   @UiFactory
-  PagingTableListView<StockQuote> createListView() {
-    PagingTableListView<StockQuote> view = new PagingTableListView<StockQuote>(10);
+  CellTable<StockQuote> createTable() {
+    CellTable<StockQuote> view = new CellTable<StockQuote>(10);
     adapter.addView(view);
     return view;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.ui.xml
index 06f00a5..837443c 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/FavoritesWidget.ui.xml
@@ -15,7 +15,7 @@
 
     <g:center>
       <g:ScrollPanel>
-        <l:PagingTableListView ui:field='listView' styleName='{styles.common.table}'/>
+        <l:CellTable ui:field='table' styleName='{styles.common.table}'/>
       </g:ScrollPanel>
     </g:center>
   </g:DockLayoutPanel>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/HighlightingTextCell.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/HighlightingTextCell.java
index 04ec4d8..3acd2c7 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/HighlightingTextCell.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/HighlightingTextCell.java
@@ -15,20 +15,20 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.regexp.shared.MatchResult;
 import com.google.gwt.regexp.shared.RegExp;
 
 /**
- * A {@link Cell} used to render text, with portions matching a given
+ * A {@link AbstractCell} used to render text, with portions matching a given
  * regular expression highlighted.
  */
-public class HighlightingTextCell extends Cell<String, Void> {
+public class HighlightingTextCell extends AbstractCell<String> {
 
   private RegExp highlightRegex;
 
   @Override
-  public void render(String value, Void viewData, StringBuilder sb) {
+  public void render(String value, Object viewData, StringBuilder sb) {
     // sb.append("<div style='overflow:hidden; white-space:nowrap; text-overflow:ellipsis;'>");
     sb.append("<div>");
     if (highlightRegex == null) {
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java
index 30d9c17..af75791 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.java
@@ -15,9 +15,8 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.list.client.SimpleCellList;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
+import com.google.gwt.bikeshed.list.client.CellList;
+import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.sample.bikeshed.stocks.shared.PlayerInfo;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -25,6 +24,7 @@
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.AbstractListViewAdapter;
 
 import java.util.List;
 
@@ -39,11 +39,11 @@
   private static final Binder binder = GWT.create(Binder.class);
 
   /**
-   * A {@link Cell} that displays the status of a single player.
+   * A {@link AbstractCell} that displays the status of a single player.
    */
-  private static final class PlayerInfoCell extends Cell<PlayerInfo, Void> {
+  private static final class PlayerInfoCell extends AbstractCell<PlayerInfo> {
     @Override
-    public void render(PlayerInfo value, Void viewData, StringBuilder sb) {
+    public void render(PlayerInfo value, Object viewData, StringBuilder sb) {
       sb.append("<div class='playerScoreBox'>");
       sb.append("<b>Name: </b>");
       sb.append(value.getDisplayName());
@@ -64,7 +64,7 @@
   }
 
   @UiField
-  SimpleCellList<PlayerInfo> listView;
+  CellList<PlayerInfo> cellList;
 
   private final AbstractListViewAdapter<PlayerInfo> adapter;
 
@@ -74,8 +74,8 @@
   }
 
   @UiFactory
-  SimpleCellList<PlayerInfo> createListView() {
-    SimpleCellList<PlayerInfo> view = new SimpleCellList<PlayerInfo>(new PlayerInfoCell(), 1, 1);
+  CellList<PlayerInfo> createCellList() {
+    CellList<PlayerInfo> view = new CellList<PlayerInfo>(new PlayerInfoCell());
     adapter.addView(view);
     return view;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.ui.xml
index 33b7a22..317e725 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/PlayerScoresWidget.ui.xml
@@ -15,7 +15,7 @@
 
     <g:center>
       <g:ScrollPanel styleName="{styles.common.padded}">
-        <l:SimpleCellList ui:field='listView'/>
+        <l:CellList ui:field='cellList'/>
       </g:ScrollPanel>
     </g:center>
   </g:DockLayoutPanel>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ProfitLossCell.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/ProfitLossCell.java
similarity index 78%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/ProfitLossCell.java
rename to bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/ProfitLossCell.java
index 8b8a5c6..bec3746 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ProfitLossCell.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/ProfitLossCell.java
@@ -13,16 +13,18 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.sample.bikeshed.stocks.client;
+
+import com.google.gwt.cell.client.AbstractCell;
 
 /**
- * A {@link Cell} used to render profit and loss.  Positive values are shown in
+ * A {@link AbstractCell} used to render profit and loss.  Positive values are shown in
  * green with a "+" sign and negative values are shown in red with a "-" sign.
  */
-public class ProfitLossCell extends Cell<Integer, Void> {
+public class ProfitLossCell extends AbstractCell<Integer> {
 
   @Override
-  public void render(Integer priceDelta, Void viewData, StringBuilder sb) {
+  public void render(Integer priceDelta, Object viewData, StringBuilder sb) {
     boolean negative = priceDelta < 0;
     if (negative) {
       priceDelta = -priceDelta;
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.java
index 46bca5d..5b7564f 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.java
@@ -15,8 +15,7 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
@@ -28,6 +27,7 @@
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.AbstractListViewAdapter;
 
 /**
  * A widget containing a search box and a results table.
@@ -37,7 +37,7 @@
   interface Binder extends UiBinder<Widget, StockQueryWidget> { }
   private static final Binder binder = GWT.create(Binder.class);
 
-  @UiField PagingTableListView<StockQuote> listView;
+  @UiField CellTable<StockQuote> table;
   @UiField TextBox queryField = new TextBox();
 
   private final AbstractListViewAdapter<StockQuote> adapter;
@@ -46,12 +46,12 @@
     this.adapter = adapter;
     initWidget(binder.createAndBindUi(this));
 
-    listView.addColumn(Columns.favoriteColumn);
-    listView.addColumn(Columns.tickerColumn, "ticker");
-    listView.addColumn(Columns.nameColumn, "name");
-    listView.addColumn(Columns.changeColumn, "change");
-    listView.addColumn(Columns.priceColumn, "price");
-    listView.addColumn(Columns.buyColumn);
+    table.addColumn(Columns.favoriteColumn);
+    table.addColumn(Columns.tickerColumn, "ticker");
+    table.addColumn(Columns.nameColumn, "name");
+    table.addColumn(Columns.changeColumn, "change");
+    table.addColumn(Columns.priceColumn, "price");
+    table.addColumn(Columns.buyColumn);
 
     // Focus the cursor on the name field when the app loads
     queryField.setFocus(true);
@@ -79,8 +79,8 @@
   }
 
   @UiFactory
-  PagingTableListView<StockQuote> createListView() {
-    PagingTableListView<StockQuote> view = new PagingTableListView<StockQuote>(10);
+  CellTable<StockQuote> createTable() {
+    CellTable<StockQuote> view = new CellTable<StockQuote>(10);
     adapter.addView(view);
     return view;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.ui.xml
index 235f154..671a925 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQueryWidget.ui.xml
@@ -18,7 +18,7 @@
 
     <g:center>
       <g:ScrollPanel>
-        <l:PagingTableListView ui:field='listView' styleName='{styles.common.table}'/>
+        <l:CellTable ui:field='table' styleName='{styles.common.table}'/>
       </g:ScrollPanel>
     </g:center>
   </g:DockLayoutPanel>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQuoteCell.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQuoteCell.java
index f1fdb99..c84dde4 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQuoteCell.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockQuoteCell.java
@@ -15,16 +15,16 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
 
 /**
  * A cell that represents a {@link StockQuote}.
  */
-public class StockQuoteCell extends Cell<StockQuote, Void> {
+public class StockQuoteCell extends AbstractCell<StockQuote> {
 
   @Override
-  public void render(StockQuote value, Void viewData, StringBuilder sb) {
+  public void render(StockQuote value, Object viewData, StringBuilder sb) {
     sb.append(value.getTicker() + " (" + value.getName() + "): "
         + value.getDisplayPrice());
   }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockService.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockService.java
index 96d53f5..98c11eb 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockService.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockService.java
@@ -15,12 +15,12 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.list.shared.Range;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockRequest;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockResponse;
 import com.google.gwt.sample.bikeshed.stocks.shared.Transaction;
 import com.google.gwt.user.client.rpc.RemoteService;
 import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
+import com.google.gwt.view.client.Range;
 
 /**
  * The client side stub for the RPC service.
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockServiceAsync.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockServiceAsync.java
index aec6a7e..9cb5963 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockServiceAsync.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StockServiceAsync.java
@@ -15,11 +15,11 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.list.shared.Range;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockRequest;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockResponse;
 import com.google.gwt.sample.bikeshed.stocks.shared.Transaction;
 import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.view.client.Range;
 
 /**
  * The async counterpart of <code>DataService</code>.
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
index d7e66ee..ae11b8c 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
@@ -15,12 +15,8 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.shared.AsyncListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.tree.client.SideBySideTreeView;
+import com.google.gwt.bikeshed.tree.client.CellBrowser;
+import com.google.gwt.cell.client.FieldUpdater;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.event.logical.shared.CloseEvent;
@@ -43,6 +39,10 @@
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.RootLayoutPanel;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.AsyncListViewAdapter;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.Range;
 
 import java.util.HashMap;
 import java.util.List;
@@ -79,7 +79,7 @@
   @UiField
   StockQueryWidget queryWidget;
   @UiField
-  SideBySideTreeView transactionTree;
+  CellBrowser transactionTree;
 
   /**
    * The popup used to purchase stock.
@@ -149,23 +149,20 @@
     RootLayoutPanel.get().add(binder.createAndBindUi(this));
 
     // Hook up handlers to columns and the buy/sell popup.
-    Columns.favoriteColumn.setFieldUpdater(new FieldUpdater<StockQuote, Boolean, Void>() {
-      public void update(int index, StockQuote object, Boolean value,
-          Void viewData) {
+    Columns.favoriteColumn.setFieldUpdater(new FieldUpdater<StockQuote, Boolean>() {
+      public void update(int index, StockQuote object, Boolean value) {
         setFavorite(object.getTicker(), value);
       }
     });
 
-    Columns.buyColumn.setFieldUpdater(new FieldUpdater<StockQuote, String, Void>() {
-      public void update(int index, StockQuote quote, String value,
-          Void viewData) {
+    Columns.buyColumn.setFieldUpdater(new FieldUpdater<StockQuote, String>() {
+      public void update(int index, StockQuote quote, String value) {
         buy(quote);
       }
     });
 
-    Columns.sellColumn.setFieldUpdater(new FieldUpdater<StockQuote, String, Void>() {
-      public void update(int index, StockQuote quote, String value,
-          Void viewData) {
+    Columns.sellColumn.setFieldUpdater(new FieldUpdater<StockQuote, String>() {
+      public void update(int index, StockQuote quote, String value) {
         sell(quote);
       }
     });
@@ -342,8 +339,8 @@
   }
 
   @UiFactory
-  SideBySideTreeView createTransactionTree() {
-    SideBySideTreeView treeView = new SideBySideTreeView(treeModel, null);
+  CellBrowser createTransactionTree() {
+    CellBrowser treeView = new CellBrowser(treeModel, null);
     treeView.setAnimationEnabled(true);
     return treeView;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.ui.xml
index 9e22821..2e0cca6 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.ui.xml
@@ -34,7 +34,7 @@
     <g:north size="18">
       <g:LayoutPanel styleName='{styles.common.box}'>
         <g:layer>
-          <t:SideBySideTreeView ui:field='transactionTree'/>
+          <t:CellBrowser ui:field='transactionTree'/>
         </g:layer>
       </g:LayoutPanel>
     </g:north>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.java
index 0e6fc77..77e23ee 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.java
@@ -15,10 +15,7 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.shared.AsyncListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.Range;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.i18n.client.NumberFormat;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
@@ -33,6 +30,9 @@
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.view.client.AsyncListViewAdapter;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.Range;
 
 /**
  * Mobile client for the stocks demo.
@@ -53,7 +53,7 @@
     return NumberFormat.getCurrencyFormat("USD").format(price / 100.0);
   }
 
-  @UiField PagingTableListView<StockQuote> listView;
+  @UiField CellTable<StockQuote> table;
   private final StockServiceAsync dataService = GWT.create(StockService.class);
   private AsyncListViewAdapter<StockQuote> favoritesListViewAdapter;
 
@@ -119,8 +119,8 @@
   }
 
   @UiFactory
-  PagingTableListView<StockQuote> createFavoritesWidget() {
-    PagingTableListView<StockQuote> favorite = new PagingTableListView<StockQuote>(10);
+  CellTable<StockQuote> createFavoritesWidget() {
+    CellTable<StockQuote> favorite = new CellTable<StockQuote>(10);
     favoritesListViewAdapter.addView(favorite);
 
     favorite.addColumn(Columns.tickerColumn, "ticker");
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.ui.xml b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.ui.xml
index 901ce55..9786f59 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksMobile.ui.xml
@@ -10,6 +10,6 @@
 
   <g:HTMLPanel>
     <div styleName='{styles.common.headerLeft}'>Portfolio / Favorites</div>
-    <l:PagingTableListView ui:field='listView' styleName='{styles.common.table}'/>
+    <l:CellTable ui:field='table' styleName='{styles.common.table}'/>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java
index 943370c..efaaff7 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java
@@ -15,19 +15,20 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.ButtonCell;
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.TextCell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.AsyncListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
-import com.google.gwt.bikeshed.list.shared.SingleSelectionModel;
-import com.google.gwt.bikeshed.tree.client.TreeViewModel;
+import com.google.gwt.cell.client.ButtonCell;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.cell.client.ValueUpdater;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
 import com.google.gwt.sample.bikeshed.stocks.shared.Transaction;
+import com.google.gwt.view.client.AbstractListViewAdapter;
+import com.google.gwt.view.client.AsyncListViewAdapter;
+import com.google.gwt.view.client.TreeViewModel;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.gwt.view.client.SingleSelectionModel;
 
 import java.util.HashMap;
 import java.util.List;
@@ -59,21 +60,21 @@
   }
 
   /**
-   * A {@link Cell} used to render a {@link StockQuote}.
+   * A {@link AbstractCell} used to render a {@link StockQuote}.
    */
-  private static final Cell<StockQuote, Void> STOCK_QUOTE_CELL = new Cell<StockQuote, Void>() {
+  private static final Cell<StockQuote> STOCK_QUOTE_CELL = new AbstractCell<StockQuote>() {
     @Override
-    public void render(StockQuote value, Void viewData, StringBuilder sb) {
+    public void render(StockQuote value, Object viewData, StringBuilder sb) {
       sb.append(value.getTicker() + " - " + value.getDisplayPrice());
     }
   };
 
   /**
-   * A {@link Cell} used to render a {@link Transaction}.
+   * A {@link AbstractCell} used to render a {@link Transaction}.
    */
-  private static final Cell<Transaction, Void> TRANSACTION_CELL = new Cell<Transaction, Void>() {
+  private static final Cell<Transaction> TRANSACTION_CELL = new AbstractCell<Transaction>() {
     @Override
-    public void render(Transaction value, Void viewData, StringBuilder sb) {
+    public void render(Transaction value, Object viewData, StringBuilder sb) {
       sb.append(value.toString());
     }
   };
@@ -125,7 +126,7 @@
     if (value == null) {
       // Return list of sectors.
       return new TreeViewModel.DefaultNodeInfo<String>(topLevelListViewAdapter,
-          TextCell.getInstance(), selectionModel, null);
+          new TextCell(), selectionModel, null);
     } else if ("Favorites".equals(value)) {
       // Return favorites. 
       return new TreeViewModel.DefaultNodeInfo<StockQuote>(
@@ -147,9 +148,9 @@
       list.add("Buy");
       list.add("Sell");
       return new TreeViewModel.DefaultNodeInfo<String>(adapter,
-          ButtonCell.getInstance(), selectionModel,
-          new ValueUpdater<String, Void>() {
-            public void update(String value, Void viewData) {
+          new ButtonCell(), selectionModel,
+          new ValueUpdater<String>() {
+            public void update(String value) {
               if ("Buy".equals(value)) {
                 updater.buy(lastStockQuote);
               } else {
@@ -172,7 +173,7 @@
       list.add("Actions");
       list.add("History");
       return new TreeViewModel.DefaultNodeInfo<String>(adapter,
-          TextCell.getInstance(), selectionModel, null);
+          new TextCell(), selectionModel, null);
     }
 
     throw new IllegalArgumentException(value.toString());
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
index c7db58b..a22fcb4 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
@@ -18,8 +18,6 @@
 import com.google.appengine.api.users.User;
 import com.google.appengine.api.users.UserService;
 import com.google.appengine.api.users.UserServiceFactory;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter.DefaultRange;
 import com.google.gwt.sample.bikeshed.stocks.client.StockService;
 import com.google.gwt.sample.bikeshed.stocks.shared.PlayerInfo;
 import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
@@ -28,6 +26,8 @@
 import com.google.gwt.sample.bikeshed.stocks.shared.StockResponse;
 import com.google.gwt.sample.bikeshed.stocks.shared.Transaction;
 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.AbstractListViewAdapter.DefaultRange;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockQuote.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockQuote.java
index 91c3249..51451cc 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockQuote.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockQuote.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.shared;
 
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
+import com.google.gwt.view.client.ProvidesKey;
 
 import java.io.Serializable;
 
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockRequest.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockRequest.java
index 80f8e4b..0f8fa9f 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/shared/StockRequest.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.sample.bikeshed.stocks.shared;
 
-import com.google.gwt.bikeshed.list.shared.Range;
+import com.google.gwt.view.client.Range;
 
 import java.io.Serializable;
 
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java
index c4d0816..d0f54dc 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -18,6 +18,7 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.resources.client.ClientBundle;
 import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.resources.client.CssResource.NotStrict;
 
 /**
@@ -29,21 +30,74 @@
    * Common styles.
    */
   public interface Common extends CssResource {
+
+    String approvedOption();
+
+    String blankOption();
+
     String box();
+
+    String deniedOption();
+
     String header();
+
     String headerLeft();
+
     String headerMain();
+
     String padded();
+
     String table();
+
+    /**
+     * Applied to the username portion of a tree item.
+     */
+    String usernameTreeItem();
+
+    /**
+     * Applied to the username portion of a tree item when selected.
+     */
+    String usernameTreeItemSelected();
   }
 
   /**
    * Shared resources.
    */
   public interface Resources extends ClientBundle {
+
     @NotStrict
     @Source("common.css")
     Common common();
+
+    /**
+     * Icon used to represent an approved item.
+     */
+    ImageResource approvedIcon();
+
+    /**
+     * Blank icon used for spacing.
+     */
+    ImageResource blankIcon();
+
+    /**
+     * Icon used to represent a denied item.
+     */
+    ImageResource deniedIcon();
+
+    /**
+     * Icon used to represent a user group.
+     */
+    ImageResource groupIcon();
+
+    /**
+     * Right rounded corner of a search box.
+     */
+    ImageResource searchRight();
+
+    /**
+     * Icon used to represent a user.
+     */
+    ImageResource userIcon();
   }
 
   private static Resources resources;
@@ -56,4 +110,8 @@
   public static Common common() {
     return resources.common();
   }
+
+  public static Resources resources() {
+    return resources;
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/approvedIcon.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/approvedIcon.png
new file mode 100644
index 0000000..9de89cc
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/approvedIcon.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/blankIcon.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/blankIcon.png
new file mode 100644
index 0000000..cf5edd9
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/blankIcon.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css
index 5162d1e..23c5357 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css
@@ -1,17 +1,20 @@
 /* global */
 body {
-  background-color: #eee;
   color: black;
   margin: 8px;
   margin-top: 3px;
 }
 
-body, table {
+body, table, td {
   font-family: Arial, sans-serif;
   font-weight: light;
   font-size: 12px;
 }
 
+h1 {
+  color: #4b4a4a;
+}
+
 /* odds and ends */
 .box {
   border: 1px solid #ccc;
@@ -43,6 +46,40 @@
   font-size: 18pt;
 }
 
+.usernameTreeItem {
+  color: #999;
+  font-size: 90%;
+}
+
+.usernameTreeItemSelected {
+  color: white;
+  font-size: 90%;
+}
+
+@sprite .blankOption {
+  gwt-image: 'blankIcon';
+  padding-left: 20px;
+  width: auto;
+  height: auto;
+  overflow: auto;
+}
+
+@sprite .approvedOption {
+  gwt-image: 'approvedIcon';
+  padding-left: 20px;
+  width: auto;
+  height: auto;
+  overflow: auto;
+}
+
+@sprite .deniedOption {
+  gwt-image: 'deniedIcon';
+  padding-left: 20px;
+  width: auto;
+  height: auto;
+  overflow: auto;
+}
+
 /* tables */
 .table {
   width: 100%;
@@ -51,53 +88,10 @@
   border-collapse: collapse;
 }
 
-.table th {
-  border-bottom: 1px solid #ccc;
-}
-
 .table td {
   border-bottom: 1px solid #ccc;
 }
 
-.table tfoot th {
-  border-width: 0px;
-}
-
-.table tr.hover {
-  background-color: lightblue;
-}
-
-.table tr.selected {
-  background-color: rgb(56, 117, 215);
-}
-
-/** Applies to StandardTreeView **/
-.gwt-tree-selectedItem {
-  background-color: rgb(56, 117, 215);
-}
-
-
-/** Applies to SideBySideTreeView **/
-.gwt-SideBySideTreeView {
-}
-
-.gwt-sstree-column {
-  border-right: 1px solid #aaa;
-  border-left: 1px solid #aaa;
-}
-
-.gwt-sstree-evenRow {
-  background-color: rgb(255, 255, 255);
-}
-
-.gwt-sstree-oddRow {
-  background-color: rgb(220, 220, 220);
-}
-
-.gwt-sstree-selectedItem {
-  background-color: rgb(56, 117, 215);
-}
-
 /* date picker */
 .gwt-DatePicker {
   border: 1px solid #A2BBDD;
@@ -194,9 +188,9 @@
 
 /* splitters */
 .gwt-SplitLayoutPanel-HDragger {
-  background: transparent url(hsplitter-grip.png) center center no-repeat;
+  background: #b6cef3 url(hsplitter-grip.png) center center no-repeat;
 }
 
 .gwt-SplitLayoutPanel-VDragger {
-  background: transparent url(vsplitter-grip.png) center center no-repeat;
+  background: #b6cef3 url(vsplitter-grip.png) center center no-repeat;
 }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/deniedIcon.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/deniedIcon.png
new file mode 100644
index 0000000..85dc91a
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/deniedIcon.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/groupIcon.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/groupIcon.png
new file mode 100644
index 0000000..af8e401
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/groupIcon.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchCenter.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchCenter.png
new file mode 100644
index 0000000..582af8e
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchCenter.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchLeft.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchLeft.png
new file mode 100644
index 0000000..9c86965
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchLeft.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchRight.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchRight.png
new file mode 100644
index 0000000..2841b7b
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/searchRight.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/userIcon.png b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/userIcon.png
new file mode 100644
index 0000000..bbf73fd
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/userIcon.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/Expenses.gwt.xml
similarity index 72%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/Expenses.gwt.xml
index 59522d6..2fcbfba 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/Expenses.gwt.xml
@@ -1,12 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
-<module rename-to="expensesCustomized">
+<module rename-to="expenses">
   <inherits name="com.google.gwt.user.User" />
   <inherits name="com.google.gwt.user.theme.standard.Standard" />
 
   <inherits name="com.google.gwt.sample.expenses.gwt.ExpensesCommon" />
-
-  <source path="customized" />
-  <entry-point
-    class="com.google.gwt.sample.expenses.gwt.customized.Customized" />
+  <entry-point class="com.google.gwt.sample.expenses.gwt.client.Expenses" />
 </module>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCommon.gwt.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCommon.gwt.xml
index 2510e8c..f316793 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCommon.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCommon.gwt.xml
@@ -3,8 +3,11 @@
 <module>
   <inherits name='com.google.gwt.app.App' />
   <inherits name='com.google.gwt.requestfactory.RequestFactory'/>
+  <inherits name='com.google.gwt.sample.bikeshed.style.Style'/>
   <inherits name="com.google.gwt.bikeshed.list.List" />
+  <inherits name="com.google.gwt.bikeshed.tree.Tree" />
 
+  <source path='client'/>
   <source path='request'/>
   <source path='place'/>
   <source path='ui'/>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesMobile.gwt.xml
similarity index 72%
copy from bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml
copy to bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesMobile.gwt.xml
index 59522d6..3ecd874 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesMobile.gwt.xml
@@ -1,12 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
-<module rename-to="expensesCustomized">
+<module rename-to="expensesMobile">
   <inherits name="com.google.gwt.user.User" />
   <inherits name="com.google.gwt.user.theme.standard.Standard" />
 
   <inherits name="com.google.gwt.sample.expenses.gwt.ExpensesCommon" />
-
-  <source path="customized" />
-  <entry-point
-    class="com.google.gwt.sample.expenses.gwt.customized.Customized" />
+  <entry-point class="com.google.gwt.sample.expenses.gwt.client.ExpensesMobile" />
 </module>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesScaffold.gwt.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/Scaffold.gwt.xml
similarity index 86%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesScaffold.gwt.xml
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/Scaffold.gwt.xml
index 1d3cf93..101e2c4 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesScaffold.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/Scaffold.gwt.xml
@@ -15,13 +15,10 @@
   License for the specific language governing permissions and limitations under
   the License.
 -->
-<module rename-to="expensesScaffold">
+<module rename-to="scaffold">
   <inherits name="com.google.gwt.user.User" />
   <inherits name="com.google.gwt.user.theme.standard.Standard" />
 
   <inherits name="com.google.gwt.sample.expenses.gwt.ExpensesCommon" />
-
-  <source path="scaffold" />
-  <entry-point
-    class="com.google.gwt.sample.expenses.gwt.scaffold.Scaffold" />
+  <entry-point class="com.google.gwt.sample.expenses.gwt.client.Scaffold" />
 </module>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ScaffoldCommon.gwt.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ScaffoldCommon.gwt.xml
new file mode 100644
index 0000000..f316793
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ScaffoldCommon.gwt.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name='com.google.gwt.app.App' />
+  <inherits name='com.google.gwt.requestfactory.RequestFactory'/>
+  <inherits name='com.google.gwt.sample.bikeshed.style.Style'/>
+  <inherits name="com.google.gwt.bikeshed.list.List" />
+  <inherits name="com.google.gwt.bikeshed.tree.Tree" />
+
+  <source path='client'/>
+  <source path='request'/>
+  <source path='place'/>
+  <source path='ui'/>
+</module>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ScaffoldMobile.gwt.xml
similarity index 60%
copy from bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml
copy to bikeshed/src/com/google/gwt/sample/expenses/gwt/ScaffoldMobile.gwt.xml
index 59522d6..8195106 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ExpensesCustomized.gwt.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ScaffoldMobile.gwt.xml
@@ -1,12 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 0.0.999//EN" "http://google-web-toolkit.googlecode.com/svn/tags/0.0.999/distro-source/core/src/gwt-module.dtd">
-<module rename-to="expensesCustomized">
+<module rename-to="scaffoldMobile">
   <inherits name="com.google.gwt.user.User" />
   <inherits name="com.google.gwt.user.theme.standard.Standard" />
 
-  <inherits name="com.google.gwt.sample.expenses.gwt.ExpensesCommon" />
-
-  <source path="customized" />
-  <entry-point
-    class="com.google.gwt.sample.expenses.gwt.customized.Customized" />
+  <inherits name="com.google.gwt.sample.expenses.gwt.ScaffoldCommon" />
+  <entry-point class="com.google.gwt.sample.expenses.gwt.client.ScaffoldMobile" />
 </module>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.java
new file mode 100644
index 0000000..84d9f15
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.bikeshed.list.client.CellTable;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.CurrencyCell;
+import com.google.gwt.cell.client.DateCell;
+import com.google.gwt.cell.client.FieldUpdater;
+import com.google.gwt.cell.client.SelectionCell;
+import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.sample.bikeshed.style.client.Styles;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
+import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord;
+import com.google.gwt.sample.expenses.gwt.request.ExpenseRecordChanged;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Anchor;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DialogBox;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.valuestore.shared.DeltaValueStore;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.view.client.ListViewAdapter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Details about the current expense report on the right side of the app,
+ * including the list of expenses.
+ */
+public class ExpenseDetails extends Composite implements
+    Receiver<List<ExpenseRecord>>, ExpenseRecordChanged.Handler {
+
+  /**
+   * String indicating approval.
+   */
+  private static final String APPROVED = "Approved";
+
+  /**
+   * String indicating denial.
+   */
+  private static final String DENIED = "Denied";
+
+  /**
+   * The maximum amount that can be approved for a given report.
+   */
+  private static final int MAX_COST = 500;
+
+  /**
+   * The cell used for approval status.
+   */
+  private static class ApprovalCell extends SelectionCell {
+
+    private final String approvedClass;
+    private final String blankClass;
+    private final String deniedClass;
+
+    public ApprovalCell(List<String> options) {
+      super(options);
+      approvedClass = " class='" + Styles.common().approvedOption() + "'";
+      blankClass = " class='" + Styles.common().blankOption() + "'";
+      deniedClass = " class='" + Styles.common().deniedOption() + "'";
+    }
+
+    @Override
+    public Object onBrowserEvent(Element parent, String value, Object viewData,
+        NativeEvent event, ValueUpdater<String> valueUpdater) {
+      String type = event.getType();
+      if ("change".equals(type)) {
+        SelectElement select = parent.getFirstChild().cast();
+        select.setClassName(Styles.common().blankOption());
+
+        // Remember which item is now selected.
+        int index = select.getSelectedIndex();
+        viewData = select.getOptions().getItem(index).getValue();
+        select.setDisabled(true);
+      }
+      super.onBrowserEvent(parent, value, viewData, event, valueUpdater);
+      return viewData;
+    }
+
+    @Override
+    public void render(String value, Object viewData, StringBuilder sb) {
+      boolean isApproved = APPROVED.equals(value);
+      boolean isDenied = DENIED.equals(value);
+
+      sb.append("<select style='background-color:white;border:1px solid #707172;width:10em;'");
+      if (isApproved) {
+        sb.append(approvedClass);
+      } else if (isDenied) {
+        sb.append(deniedClass);
+      } else {
+        sb.append(blankClass);
+      }
+      sb.append(">");
+      sb.append("<option></option>");
+
+      // Approved.
+      sb.append("<option");
+      sb.append(approvedClass);
+      if (isApproved) {
+        sb.append(" selected='selected'");
+      }
+      sb.append(">").append(APPROVED).append("</option>");
+
+      // Denied.
+      sb.append("<option");
+      sb.append(deniedClass);
+      if (isDenied) {
+        sb.append(" selected='selected'");
+      }
+      sb.append(">").append(DENIED).append("</option>");
+    }
+  }
+
+  class DenialPopup extends DialogBox {
+    private Button cancelButton = new Button("Cancel", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        reasonDenied = "";
+        hide();
+      }
+    });
+    private Button confirmButton = new Button("Confirm", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        reasonDenied = reasonBox.getText();
+        hide();
+      }
+    });
+
+    private ExpenseRecord expenseRecord;
+    private FlexTable layout = new FlexTable();
+    private TextBox reasonBox = new TextBox();
+    private String reasonDenied;
+
+    public DenialPopup() {
+      super(false, true);
+      setGlassEnabled(true);
+      setWidget(layout);
+
+      layout.setHTML(0, 0, "Reason for denial:");
+      layout.setWidget(1, 0, reasonBox);
+      HorizontalPanel p = new HorizontalPanel();
+      p.add(confirmButton);
+      p.add(cancelButton);
+      layout.setWidget(2, 0, p);
+    }
+
+    public ExpenseRecord getExpenseRecord() {
+      return expenseRecord;
+    }
+
+    public String getReasonDenied() {
+      return reasonDenied;
+    }
+
+    public void popup() {
+      center();
+      reasonBox.setFocus(true);
+    }
+
+    public void setExpenseRecord(ExpenseRecord expenseRecord) {
+      this.expenseRecord = expenseRecord;
+    }
+
+    public void setReasonDenied(String reasonDenied) {
+      this.reasonDenied = reasonDenied;
+      reasonBox.setText(reasonDenied);
+    }
+  }
+
+  interface ExpenseDetailsUiBinder extends UiBinder<Widget, ExpenseDetails> {
+  }
+
+  private static final GetValue<ExpenseRecord, Date> dateGetter = new GetValue<ExpenseRecord, Date>() {
+    public Date getValue(ExpenseRecord object) {
+      return object.getDate();
+    }
+  };
+
+  private static ExpenseDetailsUiBinder uiBinder = GWT.create(ExpenseDetailsUiBinder.class);
+
+  @UiField
+  Element approvedLabel;
+
+  @UiField
+  Element costLabel;
+
+  @UiField
+  Element errorText;
+
+  ExpensesRequestFactory expensesRequestFactory;
+
+  @UiField
+  TextBox notesBox;
+
+  @UiField
+  Element reportName;
+
+  @UiField
+  Anchor reportsLink;
+
+  @UiField
+  CellTable<ExpenseRecord> table;
+
+  private List<SortableHeader> allHeaders = new ArrayList<SortableHeader>();
+
+  private SortableColumn<ExpenseRecord, Date> dateColumn;
+
+  /**
+   * The {@link ExpenseRecord} that caused an error.
+   */
+  private ExpenseRecord errorExpense;
+
+  /**
+   * The adapter that provides expense items.
+   */
+  private ListViewAdapter<ExpenseRecord> items = new ListViewAdapter<ExpenseRecord>();
+
+  private Comparator<ExpenseRecord> lastComparator;
+
+  /**
+   * The current report being displayed.
+   */
+  private ReportRecord report;
+
+  /**
+   * The total amount that has been approved.
+   */
+  private double totalApproved;
+
+  public ExpenseDetails() {
+    initWidget(uiBinder.createAndBindUi(this));
+    items.addView(table);
+  }
+
+  public Anchor getReportsLink() {
+    return reportsLink;
+  }
+
+  public void onExpenseRecordChanged(ExpenseRecordChanged event) {
+    ExpenseRecord newRecord = event.getRecord();
+    String id = newRecord.getId();
+
+    int index = 0;
+    List<ExpenseRecord> list = items.getList();
+    for (ExpenseRecord r : list) {
+      if (r.getId().equals(id)) {
+        list.set(index, newRecord);
+      }
+      index++;
+    }
+
+    refreshCost();
+    if (lastComparator != null) {
+      sortExpenses(list, lastComparator);
+    }
+  }
+
+  public void onSuccess(List<ExpenseRecord> newValues) {
+    List<ExpenseRecord> list = new ArrayList<ExpenseRecord>(newValues);
+    sortExpenses(list, lastComparator);
+    items.setList(list);
+    refreshCost();
+  }
+
+  public void setExpensesRequestFactory(
+      ExpensesRequestFactory expensesRequestFactory) {
+    this.expensesRequestFactory = expensesRequestFactory;
+  }
+
+  /**
+   * Set the {@link ReportRecord} to show.
+   * 
+   * @param report the {@link ReportRecord}
+   * @param department the selected department
+   * @param employee the selected employee
+   */
+  public void setReportRecord(ReportRecord report, String department,
+      EmployeeRecord employee) {
+    this.report = report;
+    reportName.setInnerText(report.getPurpose());
+    notesBox.setText(report.getNotes());
+    costLabel.setInnerText("");
+    approvedLabel.setInnerText("");
+    totalApproved = 0;
+
+    // Update the breadcrumb.
+    reportsLink.setText(ExpenseList.getBreadcrumb(department, employee));
+
+    // Reset sorting state of table
+    lastComparator = dateColumn.getComparator(false);
+    for (SortableHeader header : allHeaders) {
+      header.setSorted(false);
+      header.setReverseSort(true);
+    }
+    allHeaders.get(0).setSorted(true);
+    allHeaders.get(0).setReverseSort(false);
+    table.refreshHeaders();
+
+    // Request the expenses.
+    requestExpenses();
+  }
+
+  @UiFactory
+  CellTable<ExpenseRecord> createTable() {
+    CellTable<ExpenseRecord> view = new CellTable<ExpenseRecord>(15);
+
+    dateColumn = addColumn(view, "Date", new DateCell(), dateGetter);
+    lastComparator = dateColumn.getComparator(false);
+
+    // Description column.
+    addColumn(view, "Description", new GetValue<ExpenseRecord, String>() {
+      public String getValue(ExpenseRecord object) {
+        return object.getDescription();
+      }
+    });
+
+    // Category column.
+    addColumn(view, "Category", new GetValue<ExpenseRecord, String>() {
+      public String getValue(ExpenseRecord object) {
+        return object.getCategory();
+      }
+    });
+
+    // Amount column.
+    addColumn(view, "Amount", new CurrencyCell(),
+        new GetValue<ExpenseRecord, Integer>() {
+          public Integer getValue(ExpenseRecord object) {
+            return (int) (object.getAmount().doubleValue() * 100);
+          }
+        });
+
+    // Dialog box to obtain a reason for a denial
+    final DenialPopup denialPopup = new DenialPopup();
+    denialPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
+      public void onClose(CloseEvent<PopupPanel> event) {
+        String reasonDenied = denialPopup.getReasonDenied();
+        ExpenseRecord record = denialPopup.getExpenseRecord();
+        if (reasonDenied == null || reasonDenied.length() == 0) {
+          // We need to redraw the table to reset the select box.
+          table.redraw();
+        } else {
+          updateExpenseRecord(record, "Denied", reasonDenied);
+        }
+      }
+    });
+
+    // Approval column.
+    List<String> options = new ArrayList<String>();
+    // TODO(rice): I18N
+    options.add("");
+    options.add("Approved");
+    options.add("Denied");
+    SortableColumn<ExpenseRecord, String> approvalColumn = addColumn(view,
+        "Approval Status", new ApprovalCell(options),
+        new GetValue<ExpenseRecord, String>() {
+          public String getValue(ExpenseRecord object) {
+            return object.getApproval();
+          }
+        });
+    approvalColumn.setFieldUpdater(new FieldUpdater<ExpenseRecord, String>() {
+      public void update(int index, final ExpenseRecord object, String value) {
+        if ("Denied".equals(value)) {
+          denialPopup.setExpenseRecord(object);
+          denialPopup.setReasonDenied(object.getReasonDenied());
+          denialPopup.popup();
+        } else {
+          updateExpenseRecord(object, value, "");
+        }
+      }
+    });
+
+    return view;
+  }
+
+  private <C extends Comparable<C>> SortableColumn<ExpenseRecord, C> addColumn(
+      final CellTable<ExpenseRecord> table, final String text,
+      final Cell<C> cell, final GetValue<ExpenseRecord, C> getter) {
+    final SortableColumn<ExpenseRecord, C> column = new SortableColumn<ExpenseRecord, C>(
+        cell) {
+      @Override
+      public C getValue(ExpenseRecord object) {
+        return getter.getValue(object);
+      }
+    };
+    final SortableHeader header = new SortableHeader(text);
+    allHeaders.add(header);
+
+    header.setUpdater(new ValueUpdater<String>() {
+      public void update(String value) {
+        header.setSorted(true);
+        header.toggleReverseSort();
+
+        for (SortableHeader otherHeader : allHeaders) {
+          if (otherHeader != header) {
+            otherHeader.setSorted(false);
+            otherHeader.setReverseSort(true);
+          }
+        }
+        sortExpenses(items.getList(),
+            column.getComparator(header.getReverseSort()));
+        table.refreshHeaders();
+      }
+    });
+    table.addColumn(column, header);
+    return column;
+  }
+
+  private Column<ExpenseRecord, String> addColumn(
+      CellTable<ExpenseRecord> table, final String text,
+      final GetValue<ExpenseRecord, String> getter) {
+    return addColumn(table, text, new TextCell(), getter);
+  }
+
+  /**
+   * Return a formatted currency string.
+   * 
+   * @param amount the amount in dollars
+   * @return a formatted string
+   */
+  private String formatCurrency(double amount) {
+    boolean negative = amount < 0;
+    if (negative) {
+      amount = -amount;
+    }
+    int dollars = (int) amount;
+    int cents = (int) ((amount * 100) % 100);
+
+    StringBuilder sb = new StringBuilder();
+    if (negative) {
+      sb.append("-");
+    }
+    sb.append("$");
+    sb.append(dollars);
+    sb.append('.');
+    if (cents < 10) {
+      sb.append('0');
+    }
+    sb.append(cents);
+    return sb.toString();
+  }
+
+  /**
+   * Get the columns displayed in the expense table.
+   */
+  private Collection<Property<?>> getExpenseColumns() {
+    List<Property<?>> columns = new ArrayList<Property<?>>();
+    columns.add(ExpenseRecord.amount);
+    columns.add(ExpenseRecord.approval);
+    columns.add(ExpenseRecord.category);
+    columns.add(ExpenseRecord.date);
+    columns.add(ExpenseRecord.description);
+    columns.add(ExpenseRecord.reasonDenied);
+    return columns;
+  }
+
+  /**
+   * Refresh the total cost and approved amount.
+   */
+  private void refreshCost() {
+    double totalCost = 0;
+    totalApproved = 0;
+    List<ExpenseRecord> records = items.getList();
+    for (ExpenseRecord record : records) {
+      double cost = record.getAmount();
+      totalCost += cost;
+      if (APPROVED.equals(record.getApproval())) {
+        totalApproved += cost;
+      }
+    }
+    costLabel.setInnerText(formatCurrency(totalCost));
+    approvedLabel.setInnerText(formatCurrency(totalApproved));
+  }
+
+  /**
+   * Request the expenses.
+   */
+  private void requestExpenses() {
+    expensesRequestFactory.expenseRequest().findExpensesByReport(
+        report.getRef(Record.id)).forProperties(getExpenseColumns()).to(this).fire();
+  }
+
+  /**
+   * Show an error message related to an expense.
+   * 
+   * @param expense the {@link ExpenseRecord} that caused the error
+   * @param message the error message
+   */
+  private void showExpenseError(ExpenseRecord expense, String message) {
+    errorExpense = expense;
+    errorText.setInnerText(message);
+  }
+
+  private void sortExpenses(List<ExpenseRecord> list,
+      final Comparator<ExpenseRecord> comparator) {
+    lastComparator = comparator;
+    Collections.sort(list, comparator);
+  }
+
+  private void updateExpenseRecord(ExpenseRecord record, String approval,
+      String reasonDenied) {
+    // Verify that the total is under the cap.
+    if (APPROVED.equals(approval) && !APPROVED.equals(record.getApproval())) {
+      double amount = record.getAmount();
+      if (amount + totalApproved > MAX_COST) {
+        showExpenseError(record,
+            "The total approved amount for an Expense Report cannot exceed $"
+                + MAX_COST);
+        table.redraw();
+        return;
+      }
+    }
+
+    // Create a delta and sync with the value store.
+    DeltaValueStore deltas = expensesRequestFactory.getValueStore().spawnDeltaView();
+    deltas.set(ExpenseRecord.approval, record, approval);
+    deltas.set(ExpenseRecord.reasonDenied, record, reasonDenied);
+    expensesRequestFactory.syncRequest(deltas).to(
+        new Receiver<Set<SyncResult>>() {
+          public void onSuccess(Set<SyncResult> response) {
+            for (SyncResult result : response) {
+              if (result.hasViolations()) {
+                // TODO(jlabanca): Handle errors.
+                result.getViolations();
+              }
+            }
+
+            // Request the updated expenses.
+            requestExpenses();
+          }
+        }).fire();
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.ui.xml
new file mode 100644
index 0000000..a0041a8
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseDetails.ui.xml
@@ -0,0 +1,137 @@
+<!DOCTYPE ui:UiBinder SYSTEM 'http://dl.google.com/gwt/DTD/xhtml.ent'>
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:l='urn:import:com.google.gwt.bikeshed.list.client'>
+
+  <ui:style>
+    .breadcrumb {
+      padding-left: 5px;
+      color: #4b4a4a;
+      font-size: 160%;
+      font-weight: bold;
+    }
+    
+    .reportsLink {
+      text-decoration: underline;
+      color: blue;
+      cursor: hand;
+      cursor: pointer;
+    }
+    
+    .details {
+      margin-left: 30px;
+      margin-top: 20px;
+    }
+    
+    .label {
+      font-weight: bold;
+    }
+    
+    .underline {
+      border-bottom: 1px solid black;
+    }
+    
+    .notes {
+      width: 400px;
+      border: 1px solid #5478af;
+      padding: 3px;
+    }
+    
+    .error {
+      color: red;
+    }
+    
+    .center {
+      border-top: 1px solid #88b0f2;
+    }
+    
+    .table {
+      border: 0px;
+    }
+  </ui:style>
+
+  <g:DockLayoutPanel
+    unit='PX'>
+    <g:north
+      size='150'>
+      <g:HTMLPanel>
+        <div
+          class='{style.breadcrumb}'>
+          <g:Anchor
+            styleName='{style.reportsLink}'
+            ui:field='reportsLink'>
+            Reports
+          </g:Anchor>
+          &gt;
+          <span ui:field='reportName' />
+        </div>
+
+        <table
+          class='{style.details}'
+          cellpadding='0'
+          cellspacing='0'>
+          <tr>
+            <td
+              class='{style.label} {style.underline}'>
+              Total Cost:
+            </td>
+            <td
+              width='150'
+              align='right'
+              class='{style.underline}'
+              ui:field='costLabel'>
+            </td>
+            <td width='150'>
+            </td>
+            <td
+              class='{style.label} {style.underline}'>
+              Approved So Far:
+            </td>
+            <td
+              width='150'
+              align='right'
+              class='{style.underline}'
+              ui:field='approvedLabel'>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <div style='height:30px'/>
+            </td>
+          </tr>
+          <tr>
+            <td
+              class='{style.label}'>
+              Notes:
+            </td>
+            <td
+              colspan='3'>
+              <g:TextBox
+                styleName='{style.notes}'
+                ui:field='notesBox'></g:TextBox>
+            </td>
+          </tr>
+          <tr>
+            <td
+              class='{style.error}'
+              colspan='4'
+              ui:field='errorText'>
+            </td>
+          </tr>
+        </table>
+      </g:HTMLPanel>
+    </g:north>
+    
+    <g:center>
+      <g:ScrollPanel
+        styleName='{style.center}'>
+        <l:CellTable
+          addStyleNames='{style.table}'
+          width='100%'
+          ui:field='table' />
+        </g:ScrollPanel>
+    </g:center>
+  </g:DockLayoutPanel>
+
+</ui:UiBinder> 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.java
new file mode 100644
index 0000000..65245f4
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.bikeshed.list.client.CellTable;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.bikeshed.list.client.SimplePager;
+import com.google.gwt.bikeshed.list.client.SimplePager.TextLocation;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.DateCell;
+import com.google.gwt.cell.client.ValueUpdater;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecordChanged;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.view.client.AsyncListViewAdapter;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SingleSelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeHandler;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * The list of expense reports on the left side of the app.
+ */
+public class ExpenseList extends Composite implements
+    Receiver<List<ReportRecord>>, ReportRecordChanged.Handler {
+
+  private static ExpenseListUiBinder uiBinder = GWT.create(ExpenseListUiBinder.class);
+
+  /**
+   * Utility method to get the first part of the breadcrumb based on the
+   * department and employee.
+   * 
+   * @param department the selected department
+   * @param employee the selected employee
+   * @return the breadcrumb
+   */
+  public static String getBreadcrumb(String department, EmployeeRecord employee) {
+    if (employee != null) {
+      return employee.getDisplayName() + "'s Reports";
+    } else if (department != null) {
+      return department + " Reports";
+    } else {
+      return "All Reports";
+    }
+  }
+
+  /**
+   * A text box that displays default text.
+   */
+  private static class DefaultTextBox extends TextBox {
+
+    /**
+     * The text color used when the box is disabled and empty.
+     */
+    private static final String TEXTBOX_DISABLED_COLOR = "#aaaaaa";
+
+    private final String defaultText;
+
+    public DefaultTextBox(final String defaultText) {
+      this.defaultText = defaultText;
+      resetDefaultText();
+
+      // Add focus and blur handlers.
+      addFocusHandler(new FocusHandler() {
+        public void onFocus(FocusEvent event) {
+          getElement().getStyle().clearColor();
+          if (defaultText.equals(getText())) {
+            setText("");
+          }
+        }
+      });
+      addBlurHandler(new BlurHandler() {
+        public void onBlur(BlurEvent event) {
+          if ("".equals(getText())) {
+            resetDefaultText();
+          }
+        }
+      });
+    }
+
+    public String getDefaultText() {
+      return defaultText;
+    }
+
+    /**
+     * Reset the text box to the default text.
+     */
+    public void resetDefaultText() {
+      setText(defaultText);
+      getElement().getStyle().setColor(TEXTBOX_DISABLED_COLOR);
+    }
+  }
+
+  interface ExpenseListUiBinder extends UiBinder<Widget, ExpenseList> {
+  }
+  /**
+   * Custom listener for this widget.
+   */
+  interface Listener {
+
+    /**
+     * Called when the user selects a report.
+     * 
+     * @param report the selected report
+     */
+    void onReportSelected(ReportRecord report);
+  }
+
+  /**
+   * A cell used to highlight search text.
+   */
+  private class HighlightCell extends AbstractCell<String> {
+
+    private static final String replaceString = "<span style='color:red;font-weight:bold;'>$1</span>";
+
+    @Override
+    public void render(String value, Object viewData, StringBuilder sb) {
+      if (value != null) {
+        if (searchRegExp != null) {
+          value = searchRegExp.replace(value, replaceString);
+        }
+        sb.append(value);
+      }
+    }
+  }
+
+  /**
+   * The adapter used to retrieve reports.
+   */
+  private class ReportAdapter extends AsyncListViewAdapter<ReportRecord> {
+    @Override
+    protected void onRangeChanged(ListView<ReportRecord> view) {
+      requestReports();
+    }
+  }
+
+  @UiField
+  Element breadcrumb;
+  @UiField
+  SimplePager<ReportRecord> pager;
+  @UiField(provided = true)
+  DefaultTextBox searchBox;
+  @UiField
+  Image searchButton;
+
+  /**
+   * The main table. We provide this in the constructor before calling
+   * {@link UiBinder#createAndBindUi(Object)} because the pager depends on it.
+   */
+  @UiField(provided = true)
+  CellTable<ReportRecord> table;
+
+  private List<SortableHeader> allHeaders = new ArrayList<SortableHeader>();
+
+  /**
+   * The employee being searched.
+   */
+  private EmployeeRecord employee;
+
+  /**
+   * Indicates that the report count is stale.
+   */
+  private boolean isCountStale = true;
+
+  /**
+   * The field to sort by.
+   */
+  private String orderBy = ReportRecord.purpose.getName();
+
+  private Listener listener;
+
+  /**
+   * The columns to request with each report.
+   */
+  private final List<Property<?>> reportColumns;
+
+  /**
+   * The adapter that provides reports.
+   */
+  private final ReportAdapter reports = new ReportAdapter();
+
+  /**
+   * The factory used to send requests.
+   */
+  private ExpensesRequestFactory requestFactory;
+
+  /**
+   * The string that the user searched for.
+   */
+  private RegExp searchRegExp;
+
+  public ExpenseList() {
+    reportColumns = new ArrayList<Property<?>>();
+    reportColumns.add(ReportRecord.created);
+    reportColumns.add(ReportRecord.purpose);
+    reportColumns.add(ReportRecord.notes);
+
+    // Initialize the widget.
+    createTable();
+    searchBox = new DefaultTextBox("search");
+    initWidget(uiBinder.createAndBindUi(this));
+
+    // Add the view to the adapter.
+    reports.addView(table);
+
+    // Listen for key events from the text boxes.
+    searchBox.addKeyUpHandler(new KeyUpHandler() {
+      public void onKeyUp(KeyUpEvent event) {
+        // Search on enter.
+        if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
+          isCountStale = true;
+          requestReports();
+          return;
+        }
+
+        // Highlight as the user types.
+        String text = searchBox.getText();
+        if (text.length() > 0) {
+          searchRegExp = RegExp.compile("(" + text + ")", "i");
+        } else {
+          searchRegExp = null;
+        }
+        table.redraw();
+      }
+    });
+    searchButton.addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        isCountStale = true;
+        requestReports();
+      }
+    });
+  }
+
+  public void onReportChanged(ReportRecordChanged event) {
+    ReportRecord changed = event.getRecord();
+    String changedId = changed.getId();
+    List<ReportRecord> records = table.getDisplayedItems();
+    int i = 0;
+    for (ReportRecord record : records) {
+      if (record != null && changedId.equals(record.getId())) {
+        List<ReportRecord> changedList = new ArrayList<ReportRecord>();
+        changedList.add(changed);
+        reports.updateViewData(i + table.getPageStart(), 1, changedList);
+      }
+      i++;
+    }
+  }
+
+  public void onSuccess(List<ReportRecord> newValues) {
+    reports.updateViewData(table.getPageStart(), newValues.size(), newValues);
+  }
+
+  /**
+   * Set the current department and employee to filter on.
+   * 
+   * @param department the department, or null if none selected
+   * @param employee the employee, or null if none selected
+   */
+  public void setEmployee(String department, EmployeeRecord employee) {
+    this.employee = employee;
+    isCountStale = true;
+    searchBox.resetDefaultText();
+    breadcrumb.setInnerText(getBreadcrumb(department, employee));
+
+    // Refresh the table.
+    pager.setPageStart(0);
+    table.refresh();
+  }
+
+  public void setListener(Listener listener) {
+    this.listener = listener;
+  }
+
+  public void setRequestFactory(ExpensesRequestFactory factory) {
+    this.requestFactory = factory;
+    requestReports();
+  }
+
+  @UiFactory
+  SimplePager<ReportRecord> createPager() {
+    SimplePager<ReportRecord> p = new SimplePager<ReportRecord>(table,
+        TextLocation.RIGHT);
+    p.setRangeLimited(true);
+    return p;
+  }
+
+  /**
+   * Add a sortable column to the table.
+   * 
+   * @param <C> the data type for the column
+   * @param text the header text
+   * @param cell the cell used to render the column
+   * @param getter the getter to retrieve the value for the column
+   * @param property the property to sort by
+   * @return the column
+   */
+  private <C> Column<ReportRecord, C> addColumn(final String text,
+      final Cell<C> cell, final GetValue<ReportRecord, C> getter,
+      final Property<?> property) {
+    final Column<ReportRecord, C> column = new Column<ReportRecord, C>(cell) {
+      @Override
+      public C getValue(ReportRecord object) {
+        return getter.getValue(object);
+      }
+    };
+    final SortableHeader header = new SortableHeader(text);
+    allHeaders.add(header);
+
+    header.setUpdater(new ValueUpdater<String>() {
+      public void update(String value) {
+        header.setSorted(true);
+        header.toggleReverseSort();
+
+        for (SortableHeader otherHeader : allHeaders) {
+          if (otherHeader != header) {
+            otherHeader.setSorted(false);
+            otherHeader.setReverseSort(true);
+          }
+        }
+        table.refreshHeaders();
+
+        // Request sorted rows.
+        orderBy = property.getName();
+        if (header.getReverseSort()) {
+          orderBy += " DESC";
+        }
+        searchBox.resetDefaultText();
+        searchRegExp = null;
+        requestReports();
+      }
+    });
+    table.addColumn(column, header);
+    return column;
+  }
+
+  /**
+   * Create the {@link CellTable}.
+   */
+  private void createTable() {
+    table = new CellTable<ReportRecord>(50);
+
+    // Add a selection model.
+    final SingleSelectionModel<ReportRecord> selectionModel = new SingleSelectionModel<ReportRecord>();
+    table.setSelectionModel(selectionModel);
+    table.setSelectionEnabled(true);
+    selectionModel.addSelectionChangeHandler(new SelectionChangeHandler() {
+      public void onSelectionChange(SelectionChangeEvent event) {
+        Object selected = selectionModel.getSelectedObject();
+        if (selected != null && listener != null) {
+          listener.onReportSelected((ReportRecord) selected);
+        }
+      }
+    });
+
+    // Purpose column.
+    addColumn("Purpose", new HighlightCell(),
+        new GetValue<ReportRecord, String>() {
+          public String getValue(ReportRecord object) {
+            return object.getPurpose();
+          }
+        }, ReportRecord.purpose);
+
+    // Notes column.
+    addColumn("Notes", new HighlightCell(),
+        new GetValue<ReportRecord, String>() {
+          public String getValue(ReportRecord object) {
+            return object.getNotes();
+          }
+        }, ReportRecord.notes);
+
+    // Created column.
+    addColumn("Created", new DateCell(), new GetValue<ReportRecord, Date>() {
+      public Date getValue(ReportRecord object) {
+        return object.getCreated();
+      }
+    }, ReportRecord.created);
+  }
+
+  /**
+   * Send a request for reports in the current range.
+   */
+  private void requestReports() {
+    if (requestFactory == null) {
+      return;
+    }
+
+    // Get the parameters.
+    String startsWith = searchBox.getText();
+    if (searchBox.getDefaultText().equals(startsWith)) {
+      startsWith = "";
+    }
+    Range range = table.getRange();
+    Long employeeId = employee == null ? -1 : new Long(employee.getId());
+
+    // If a search string is specified, the results will not be sorted.
+    if (startsWith.length() > 0) {
+      for (SortableHeader header : allHeaders) {
+        header.setSorted(false);
+        header.setReverseSort(false);
+      }
+      table.refreshHeaders();
+    }
+
+    // Request the total data size.
+    if (isCountStale) {
+      isCountStale = false;
+      requestFactory.reportRequest().countReportsBySearch(employeeId,
+          startsWith).to(new Receiver<Long>() {
+        public void onSuccess(Long response) {
+          reports.updateDataSize(response.intValue(), true);
+        }
+      }).fire();
+    }
+
+    // Request reports in the current range.
+    requestFactory.reportRequest().findReportEntriesBySearch(employeeId,
+        startsWith, orderBy, range.getStart(), range.getLength()).forProperties(
+        reportColumns).to(this).fire();
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.ui.xml
new file mode 100644
index 0000000..0acea2e
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseList.ui.xml
@@ -0,0 +1,146 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+  xmlns:ui="urn:ui:com.google.gwt.uibinder"
+  xmlns:g="urn:import:com.google.gwt.user.client.ui"
+  xmlns:l='urn:import:com.google.gwt.bikeshed.list.client'>
+
+  <ui:with
+    field='styles'
+    type='com.google.gwt.sample.bikeshed.style.client.Styles.Resources' />
+
+  <ui:image
+    field='searchCenter'
+    src='../../../bikeshed/style/client/searchCenter.png'
+    repeatStyle="Horizontal" />
+  <ui:image
+    field='searchLeft'
+    src='../../../bikeshed/style/client/searchLeft.png' />
+
+  <ui:style>
+    .top {
+      padding-top: 4px;
+    }
+    
+    .breadcrumb {
+      padding-left: 5px;
+      color: #4b4a4a;
+      font-size: 160%;
+      font-weight: bold;
+    }
+    
+    .textBox {
+      width: 250px;
+      border: 0px;
+      padding: 0px;
+      height: 16px;
+      font-size: 12px;
+    }
+    
+    .searchButton {
+      cursor: pointer;
+      cursor: hand;
+    }
+    
+    @sprite .searchCenter {
+      gwt-image: 'searchCenter';
+      padding-top: 5px;
+    }
+    
+    @sprite .searchLeft {
+      gwt-image: 'searchLeft';
+    }
+    
+    .tableControls {
+      background: white;
+    }
+    
+    .center {
+      border-top: 1px solid #88b0f2;
+      border-bottom: 1px solid #88b0f2;
+    }
+    
+    .table {
+      border: 0px;
+    }
+  </ui:style>
+
+  <g:DockLayoutPanel
+    unit='PX'>
+
+    <g:north
+      size='40'>
+      <g:HTMLPanel
+        styleName='{style.top}'>
+        <table
+          class='{style.tableControls}'
+          width='100%'
+          cellspacing='0'
+          cellpadding='0'>
+          <tr>
+            <td
+              class='{style.breadcrumb}'
+              align='left'
+              colspan='3'
+              ui:field='breadcrumb'>
+              All Reports
+            </td>
+            <td
+              align='right'
+              valign='middle'>
+              <table
+                cellspacing='0'
+                cellpadding='0'>
+                <tr>
+                  <td
+                    valign='top'>
+                    <div
+                      class='{style.searchLeft}' />
+                  </td>
+                  <td
+                    valign='top'
+                    class='{style.searchCenter}'>
+                    <g:TextBox
+                      styleName='{style.textBox}'
+                      ui:field='searchBox' />
+                  </td>
+                  <td
+                    valign='top'>
+                    <g:Image
+                      addStyleNames='{style.searchButton}'
+                      ui:field='searchButton'
+                      resource='{styles.searchRight}' />
+                  </td>
+                  <td
+                    width='10'>
+                &nbsp;
+                  </td>
+                </tr>
+              </table>
+            </td>
+          </tr>
+        </table>
+      </g:HTMLPanel>
+    </g:north>
+
+    <g:center>
+      <g:ScrollPanel
+        styleName='{style.center}'>
+        <l:CellTable
+          addStyleNames='{style.table}'
+          width='100%'
+          ui:field='table' />
+      </g:ScrollPanel>
+    </g:center>
+
+    <g:south
+      size='30'>
+      <g:HTMLPanel>
+        <div
+          style='position:absolute;right:30px;'>
+          <l:SimplePager
+            ui:field='pager' />
+        </div>
+      </g:HTMLPanel>
+    </g:south>
+  </g:DockLayoutPanel>
+</ui:UiBinder> 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseTree.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseTree.java
new file mode 100644
index 0000000..bc4658b
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpenseTree.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.bikeshed.tree.client.CellTree;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.IconCellDecorator;
+import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.bikeshed.style.client.Styles;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.view.client.AsyncListViewAdapter;
+import com.google.gwt.view.client.TreeViewModel;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.ProvidesKey;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SingleSelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeHandler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * The employee tree located on the left of the app.
+ */
+public class ExpenseTree extends Composite {
+
+  /**
+   * Custom listener for this widget.
+   */
+  public interface Listener {
+
+    /**
+     * Called when the user selects a tree item.
+     * 
+     * @param department the selected department name
+     * @param employee the selected employee
+     */
+    void onSelection(String department, EmployeeRecord employee);
+  }
+
+  /**
+   * A {@link AbstractCell} that represents an {@link EmployeeRecord}.
+   */
+  private class EmployeeCell extends IconCellDecorator<EmployeeRecord> {
+
+    public EmployeeCell() {
+      super(Styles.resources().userIcon(), new AbstractCell<EmployeeRecord>() {
+
+        private final String usernameStyle = Styles.common().usernameTreeItem();
+        private final String usernameStyleSelected = Styles.common().usernameTreeItemSelected();
+
+        @Override
+        public boolean dependsOnSelection() {
+          return true;
+        }
+
+        @Override
+        public void render(EmployeeRecord value, Object viewData,
+            StringBuilder sb) {
+          if (value != null) {
+            sb.append(value.getDisplayName()).append("<br>");
+            sb.append("<span class='").append(usernameStyle);
+            if (lastEmployee != null
+                && lastEmployee.getId().equals(value.getId())) {
+              sb.append(" ").append(usernameStyleSelected);
+            }
+            sb.append("'>");
+            sb.append(value.getUserName());
+            sb.append("</span>");
+          }
+        }
+      });
+    }
+  }
+
+  /**
+   * The {@link ListViewAdapter} used for Employee lists.
+   */
+  private class EmployeeListViewAdapter extends
+      AsyncListViewAdapter<EmployeeRecord> implements
+      Receiver<List<EmployeeRecord>> {
+
+    private final String department;
+
+    public EmployeeListViewAdapter(String department) {
+      this.department = department;
+    }
+
+    @Override
+    public void addView(ListView<EmployeeRecord> view) {
+      super.addView(view);
+
+      // Request the count anytime a view is added.
+      requestFactory.employeeRequest().countEmployeesByDepartment(department).to(
+          new Receiver<Long>() {
+            public void onSuccess(Long response) {
+              updateDataSize(response.intValue(), true);
+            }
+          }).fire();
+    }
+
+    public void onSuccess(List<EmployeeRecord> response) {
+      updateViewData(0, response.size(), response);
+    }
+
+    @Override
+    protected void onRangeChanged(ListView<EmployeeRecord> view) {
+      Range range = view.getRange();
+      requestFactory.employeeRequest().findEmployeeEntriesByDepartment(
+          department, range.getStart(), range.getLength()).forProperties(
+          getEmployeeMenuProperties()).to(this).fire();
+    }
+  }
+
+  /**
+   * The {@link TreeViewModel} used to browse expense reports.
+   */
+  private class ExpensesTreeViewModel implements TreeViewModel {
+
+    /**
+     * The department cell singleton.
+     */
+    private final Cell<String> departmentCell = new IconCellDecorator<String>(
+        Styles.resources().groupIcon(), new TextCell());
+
+    /**
+     * The {@link EmployeeCell} singleton.
+     */
+    private final EmployeeCell employeeCell = new EmployeeCell();
+
+    public <T> NodeInfo<?> getNodeInfo(T value) {
+      if (value == null) {
+        // Top level.
+        return new DefaultNodeInfo<String>(departments, departmentCell,
+            selectionModel, null);
+      } else if (isAllDepartment(value)) {
+        // Employees are not displayed under the 'All' Department.
+        return null;
+      } else if (value instanceof String) {
+        // Second level.
+        EmployeeListViewAdapter adapter = new EmployeeListViewAdapter(
+            (String) value);
+        return new DefaultNodeInfo<EmployeeRecord>(adapter, employeeCell,
+            selectionModel, null);
+      }
+
+      return null;
+    }
+
+    public boolean isLeaf(Object value) {
+      return !isDepartment(value) || isAllDepartment(value);
+    }
+
+    /**
+     * @return true if the object is the All department
+     */
+    private boolean isAllDepartment(Object value) {
+      return departments.getList().get(0).equals(value);
+    }
+
+    /**
+     * @return true if the object is a department
+     */
+    private boolean isDepartment(Object value) {
+      return departments.getList().contains(value.toString());
+    }
+  }
+
+  /**
+   * The adapter that provides departments.
+   */
+  private ListViewAdapter<String> departments = new ListViewAdapter<String>();
+
+  /**
+   * The last selected department.
+   */
+  private String lastDepartment;
+
+  /**
+   * The last selected employee.
+   */
+  private EmployeeRecord lastEmployee;
+
+  /**
+   * The listener of this widget.
+   */
+  private Listener listener;
+
+  /**
+   * The factory used to send requests.
+   */
+  private ExpensesRequestFactory requestFactory;
+
+  /**
+   * The shared {@link SingleSelectionModel}.
+   */
+  private final SingleSelectionModel<Object> selectionModel = new SingleSelectionModel<Object>();
+
+  /**
+   * The main widget.
+   */
+  private CellTree tree;
+
+  public ExpenseTree() {
+    createTree();
+    initWidget(tree);
+    getElement().getStyle().setOverflow(Overflow.AUTO);
+
+    // Initialize the departments.
+    List<String> departmentList = departments.getList();
+    departmentList.add("All");
+    departmentList.add("Engineering");
+    // The Finance department is empty.
+    departmentList.add("Finance");
+    departmentList.add("Marketing");
+    departmentList.add("Operations");
+    departmentList.add("Sales");
+  }
+
+  public void setListener(Listener listener) {
+    this.listener = listener;
+  }
+
+  public void setRequestFactory(ExpensesRequestFactory factory) {
+    this.requestFactory = factory;
+  }
+
+  /**
+   * Create the {@link CellTree}.
+   */
+  private void createTree() {
+    // Listen for selection. We need to add this handler before the CellBrowser
+    // adds its own handler.
+    selectionModel.addSelectionChangeHandler(new SelectionChangeHandler() {
+      public void onSelectionChange(SelectionChangeEvent event) {
+        Object selected = selectionModel.getSelectedObject();
+        if (selected == null) {
+          lastEmployee = null;
+          lastDepartment = null;
+        } else if (selected instanceof EmployeeRecord) {
+          lastEmployee = (EmployeeRecord) selected;
+        } else if (selected instanceof String) {
+          lastEmployee = null;
+          lastDepartment = (String) selected;
+        }
+
+        if (listener != null) {
+          listener.onSelection(lastDepartment, lastEmployee);
+        }
+      }
+    });
+    selectionModel.setKeyProvider(new ProvidesKey<Object>() {
+      public Object getKey(Object item) {
+        if (item instanceof EmployeeRecord) {
+          return Expenses.EMPLOYEE_RECORD_KEY_PROVIDER.getKey((EmployeeRecord) item);
+        }
+        return item;
+      }
+    });
+
+    // Create a CellBrowser.
+    tree = new CellTree(new ExpensesTreeViewModel(), null);
+    tree.setAnimationEnabled(true);
+  }
+
+  private Collection<Property<?>> getEmployeeMenuProperties() {
+    List<Property<?>> columns = new ArrayList<Property<?>>();
+    columns.add(EmployeeRecord.displayName);
+    columns.add(EmployeeRecord.userName);
+    return columns;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Expenses.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Expenses.java
new file mode 100644
index 0000000..0496431
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Expenses.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
+import com.google.gwt.sample.expenses.gwt.request.ExpenseRecordChanged;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecordChanged;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.view.client.ProvidesKey;
+
+/**
+ * Entry point for the Expenses app.
+ */
+public class Expenses implements EntryPoint {
+
+  /**
+   * The key provider for {@link EmployeeRecord}s.
+   */
+  public static final ProvidesKey<EmployeeRecord> EMPLOYEE_RECORD_KEY_PROVIDER = new ProvidesKey<EmployeeRecord>() {
+    public Object getKey(EmployeeRecord item) {
+      return item == null ? null : item.getId();
+    }
+  };
+
+  private String lastDepartment;
+  private EmployeeRecord lastEmployee;
+  private ExpensesRequestFactory requestFactory;
+  private ExpensesShell shell;
+
+  public void onModuleLoad() {
+    final HandlerManager eventBus = new HandlerManager(null);
+    requestFactory = GWT.create(ExpensesRequestFactory.class);
+    requestFactory.init(eventBus);
+
+    RootLayoutPanel root = RootLayoutPanel.get();
+
+    shell = new ExpensesShell();
+    final ExpenseTree expenseTree = shell.getExpenseTree();
+    final ExpenseList expenseList = shell.getExpenseList();
+    final ExpenseDetails expenseDetails = shell.getExpenseDetails();
+
+    root.add(shell);
+
+    // Listen for requests from ExpenseTree.
+    expenseTree.setListener(new ExpenseTree.Listener() {
+      public void onSelection(String department, EmployeeRecord employee) {
+        lastDepartment = department;
+        lastEmployee = employee;
+        expenseList.setEmployee(department, employee);
+        shell.showExpenseDetails(false);
+      }
+    });
+    expenseTree.setRequestFactory(requestFactory);
+
+    // Listen for requests from the ExpenseList.
+    expenseList.setListener(new ExpenseList.Listener() {
+      public void onReportSelected(ReportRecord report) {
+        expenseDetails.setExpensesRequestFactory(requestFactory);
+        expenseDetails.setReportRecord(report, lastDepartment, lastEmployee);
+        shell.showExpenseDetails(true);
+      }
+    });
+    expenseList.setRequestFactory(requestFactory);
+    eventBus.addHandler(ReportRecordChanged.TYPE, expenseList);
+
+    eventBus.addHandler(ExpenseRecordChanged.TYPE, expenseDetails);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java
new file mode 100644
index 0000000..b08ad6f
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobile.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+
+/**
+ * Entry point for the mobile version of the Expenses app.
+ */
+public class ExpensesMobile implements EntryPoint {
+
+  /**
+   * TODO(jgw): Put this some place more sensible.
+   */
+  public static String formatCurrency(int price) {
+    StringBuilder sb = new StringBuilder();
+
+    boolean negative = price < 0;
+    if (negative) {
+      price = -price;
+    }
+    int dollars = price / 100;
+    int cents = price % 100;
+
+    if (negative) {
+      sb.append("-");
+    }
+    sb.append("$");
+    sb.append(dollars);
+    sb.append('.');
+    if (cents < 10) {
+      sb.append('0');
+    }
+    sb.append(cents);
+
+    return sb.toString();
+  }
+
+  /**
+   * This is the entry point method.
+   */
+  public void onModuleLoad() {
+    final HandlerManager eventBus = new HandlerManager(null);
+    final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class);
+    requestFactory.init(eventBus);
+
+    final ExpensesMobileShell shell = new ExpensesMobileShell(requestFactory);
+    RootLayoutPanel.get().add(shell);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java
new file mode 100644
index 0000000..0dce739
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiFactory;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DeckPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * TODO
+ */
+public class ExpensesMobileShell extends Composite {
+
+  interface ShellUiBinder extends UiBinder<Widget, ExpensesMobileShell> { }
+  private static ShellUiBinder BINDER = GWT.create(ShellUiBinder.class);
+
+  private final ExpensesRequestFactory requestFactory;
+
+  @UiField DeckPanel deck;
+  @UiField MobileReportList reportList;
+  @UiField MobileExpenseList expenseList;
+  @UiField MobileExpenseDetails expenseDetails;
+
+  @UiField Button backButton, forwardButton;
+  @UiField SpanElement titleSpan;
+
+  public ExpensesMobileShell(ExpensesRequestFactory requestFactory) {
+    this.requestFactory = requestFactory;
+    initWidget(BINDER.createAndBindUi(this));
+
+    deck.showWidget(0);
+  }
+
+  @UiFactory
+  MobileReportList createReportList() {
+    return new MobileReportList(new MobileReportList.Listener() {
+      public void onReportSelected(ReportRecord report) {
+        expenseList.show(report);
+        deck.showWidget(1);
+      }
+    }, requestFactory);
+  }
+
+  @UiFactory
+  MobileExpenseList createExpenseList() {
+    return new MobileExpenseList(new MobileExpenseList.Listener() {
+      public void onExpenseSelected(ExpenseRecord expense) {
+        expenseDetails.show(expense);
+        deck.showWidget(2);
+      }
+    }, requestFactory);
+  }
+
+  @UiHandler("backButton")
+  void onBack(ClickEvent evt) {
+    int idx = deck.getVisibleWidget();
+    if (idx > 0) {
+      deck.showWidget(idx - 1);
+    }
+  }
+
+  @UiHandler("forwardButton")
+  void onForward(ClickEvent evt) {
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.ui.xml
new file mode 100644
index 0000000..72e6884
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesMobileShell.ui.xml
@@ -0,0 +1,51 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:l='urn:import:com.google.gwt.bikeshed.list.client'
+  xmlns:c='urn:import:com.google.gwt.sample.expenses.gwt.client'>
+
+  <ui:with field='styles' type='com.google.gwt.sample.bikeshed.style.client.Styles' />
+
+  <ui:style>
+    .title {
+      border-bottom: 1px solid #c3c3c3;
+    }
+
+    .titleText {
+      font-family: Times New Roman, sans-serif;
+      color: #7b8fae;
+      font-size: 18pt;
+      font-weight: bold;
+    }
+
+    .bar {
+      position: relative;
+      border-bottom: 1px solid #c3c3c3;
+    }
+
+    .backButton {
+    }
+    
+    .forwardButton {
+      position: absolute;
+      right: 0px;
+    }
+  </ui:style>
+
+  <g:HTMLPanel styleName='{style.title}'>
+    <div class='{style.title}'>Expenses Sample</div>
+
+    <div class='{style.bar}'>
+      <g:Button styleName='{style.backButton}' ui:field='backButton'>&lt;</g:Button>
+      <span ui:field='titleSpan'/>
+      <g:Button styleName='{style.forwardButton}' ui:field='forwardButton'>&gt;</g:Button>
+    </div>
+
+    <g:DeckPanel ui:field='deck'>
+      <c:MobileReportList ui:field='reportList'/>
+      <c:MobileExpenseList ui:field='expenseList'/>
+      <c:MobileExpenseDetails ui:field='expenseDetails'/>
+    </g:DeckPanel>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesShell.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesShell.java
new file mode 100644
index 0000000..3158893
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesShell.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.layout.client.Layout;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.LayoutPanel;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * UI shell for expenses sample app.
+ */
+public class ExpensesShell extends Composite {
+
+  interface ShellUiBinder extends UiBinder<Widget, ExpensesShell> {
+  }
+
+  private static ShellUiBinder uiBinder = GWT.create(ShellUiBinder.class);
+
+  @UiField
+  ExpenseList expenseList;
+  @UiField
+  ExpenseTree expenseTree;
+  @UiField
+  LayoutPanel layoutPanel;
+  @UiField
+  SplitLayoutPanel splitLayout;
+
+  private final ExpenseDetails expenseDetails = new ExpenseDetails();
+
+  public ExpensesShell() {
+    initWidget(uiBinder.createAndBindUi(this));
+    splitLayout.setWidgetMinSize(expenseTree, 150);
+
+    // Handle breadcrumb events from Expense Details.
+    expenseDetails.getReportsLink().addClickHandler(new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        showExpenseDetails(false);
+      }
+    });
+  }
+
+  public ExpenseDetails getExpenseDetails() {
+    return expenseDetails;
+  }
+
+  public ExpenseList getExpenseList() {
+    return expenseList;
+  }
+
+  public ExpenseTree getExpenseTree() {
+    return expenseTree;
+  }
+
+  /**
+   * Show or hide the expense details. When showing, the expense list is hidden.
+   * 
+   * @param isShowing true to show details, false to show reports list
+   */
+  public void showExpenseDetails(boolean isShowing) {
+    if (isShowing) {
+      showWidget(expenseDetails, false);
+    } else {
+      showWidget(expenseList, true);
+    }
+  }
+
+  /**
+   * Slide a widget into view.
+   * 
+   * @param widget the widget to show
+   * @param fromLeft true to slide from left, false to slide from right
+   */
+  private void showWidget(Widget widget, boolean fromLeft) {
+    // Early out if the widget is already in the layout panel.
+    final Widget current = layoutPanel.getWidget(0);
+    if (current == widget) {
+      return;
+    }
+
+    // Initialize the layout.
+    layoutPanel.add(widget);
+    layoutPanel.setWidgetLeftWidth(current, 0, Unit.PCT, 100, Unit.PCT);
+    if (fromLeft) {
+      layoutPanel.setWidgetLeftWidth(widget, -100, Unit.PCT, 100, Unit.PCT);
+    } else {
+      layoutPanel.setWidgetLeftWidth(widget, 100, Unit.PCT, 100, Unit.PCT);
+    }
+    layoutPanel.forceLayout();
+
+    // Slide into view.
+    if (fromLeft) {
+      layoutPanel.setWidgetLeftWidth(current, 100, Unit.PCT, 100, Unit.PCT);
+    } else {
+      layoutPanel.setWidgetLeftWidth(current, -100, Unit.PCT, 100, Unit.PCT);
+    }
+    layoutPanel.setWidgetLeftWidth(widget, 0, Unit.PCT, 100, Unit.PCT);
+    layoutPanel.animate(500, new Layout.AnimationCallback() {
+      public void onAnimationComplete() {
+        // Remove the old widget when the animation completes.
+        layoutPanel.remove(current);
+      }
+
+      public void onLayout(Layer layer, double progress) {
+      }
+    });
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesShell.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesShell.ui.xml
new file mode 100644
index 0000000..f62292c
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ExpensesShell.ui.xml
@@ -0,0 +1,70 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:l='urn:import:com.google.gwt.bikeshed.list.client'
+  xmlns:c='urn:import:com.google.gwt.sample.expenses.gwt.client'>
+
+  <ui:with
+    field='styles'
+    type='com.google.gwt.sample.bikeshed.style.client.Styles' />
+
+  <ui:style>
+    .title {
+      border-bottom: 1px solid #c3c3c3;
+    }
+    
+    .titleText {
+      font-family: Times New Roman, sans-serif;
+      color: #7b8fae;
+      font-size: 18pt;
+      font-weight: bold;
+    }
+  </ui:style>
+
+  <g:DockLayoutPanel
+    unit='PX'>
+    <g:north
+      size='50'>
+      <g:HTMLPanel
+        styleName='{style.title}'>
+        <table
+          height='100%'>
+          <tr>
+            <td>
+              <img
+                height='40'
+                src='http://www.google.com/intl/en_ALL/images/logo.gif' />
+            </td>
+            <td
+              class='{style.titleText}'
+              valign='middle'>
+              Expenses Sample
+                </td>
+          </tr>
+        </table>
+      </g:HTMLPanel>
+    </g:north>
+
+    <g:center>
+      <g:SplitLayoutPanel
+        ui:field='splitLayout'>
+        <g:west
+          size='250'>
+          <c:ExpenseTree
+            ui:field='expenseTree' />
+        </g:west>
+        <g:center>
+          <g:LayoutPanel
+            ui:field='layoutPanel'>
+            <g:layer>
+              <c:ExpenseList
+                ui:field='expenseList' />
+            </g:layer>
+          </g:LayoutPanel>
+        </g:center>
+      </g:SplitLayoutPanel>
+    </g:center>
+  </g:DockLayoutPanel>
+
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/GetValue.java
similarity index 67%
copy from bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java
copy to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/GetValue.java
index 46883ee..eb9e166 100644
--- a/bikeshed/src/com/google/gwt/user/client/ui/TakesValueList.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/GetValue.java
@@ -1,27 +1,26 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.user.client.ui;
-
-import java.util.List;
+package com.google.gwt.sample.expenses.gwt.client;
 
 /**
- * Implemented by objects that display a list of values.
- *
- * @param <V> value type
+ * An interface for retrieving a value of type C from a record of type T.
+ * 
+ * @param <T> the underlying record data type
+ * @param <C> the extracted data type
  */
-public interface TakesValueList<V> {
-  void setValueList(List<V> newValues);
+public interface GetValue<T, C> {
+  C getValue(T object);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseDetails.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseDetails.java
new file mode 100644
index 0000000..b8feb43
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseDetails.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.Date;
+
+/**
+ * TODO
+ */
+public class MobileExpenseDetails extends Composite {
+
+  interface Binder extends UiBinder<Widget, MobileExpenseDetails> { }
+  private static Binder BINDER = GWT.create(Binder.class);
+
+  @UiField TextBox nameText, categoryText, priceText;
+  @UiField ListBox dateYear, dateMonth, dateDay;
+
+  public MobileExpenseDetails() {
+    initWidget(BINDER.createAndBindUi(this));
+
+    populateList(dateYear, 2000, 2010);
+    populateList(dateMonth, 1, 12);
+    populateList(dateDay, 1, 31);
+  }
+
+  private void populateList(ListBox list, int start, int end) {
+    for (int i = start; i <= end; ++i) {
+      if (i < 10) {
+        list.addItem("0" + i);
+      } else {
+        list.addItem("" + i);
+      }
+    }
+  }
+
+  public void show(ExpenseRecord expense) {
+    nameText.setText(expense.getDescription());
+    categoryText.setText(expense.getCategory());
+    priceText.setText(ExpensesMobile.formatCurrency(expense.getAmount().intValue()));
+
+    Date d = expense.getDate();
+    dateYear.setSelectedIndex(d.getYear() + 1900 - 2000);
+    dateMonth.setSelectedIndex(d.getMonth());
+    dateDay.setSelectedIndex(d.getDate() - 1);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseDetails.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseDetails.ui.xml
new file mode 100644
index 0000000..3002724
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseDetails.ui.xml
@@ -0,0 +1,23 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:l='urn:import:com.google.gwt.bikeshed.list.client'
+  xmlns:c='urn:import:com.google.gwt.sample.expenses.gwt.client'>
+
+  <ui:with field='styles' type='com.google.gwt.sample.bikeshed.style.client.Styles' />
+
+  <ui:style>
+  </ui:style>
+
+  <g:HTMLPanel>
+    <div>Name: <g:TextBox ui:field='nameText'/></div>
+    <div>
+      Date: <g:ListBox ui:field='dateYear'/> /
+            <g:ListBox ui:field='dateMonth'/> /
+            <g:ListBox ui:field='dateDay'/>
+    </div>
+    <div>Category: <g:TextBox ui:field='categoryText'/></div>
+    <div>Price: <g:TextBox ui:field='priceText'/></div>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
new file mode 100644
index 0000000..2759fad
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileExpenseList.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.bikeshed.list.client.CellList;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.request.ExpenseRecord;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SingleSelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * TODO
+ */
+public class MobileExpenseList extends Composite implements
+    Receiver<List<ExpenseRecord>> {
+
+  /**
+   * TODO
+   */
+  public interface Listener {
+    void onExpenseSelected(ExpenseRecord expense);
+  }
+
+  private final ExpensesRequestFactory requestFactory;
+  private final CellList<ExpenseRecord> expenseList;
+  private final ListViewAdapter<ExpenseRecord> expenseAdapter;
+  private final SingleSelectionModel<ExpenseRecord> expenseSelection;
+
+  public MobileExpenseList(final Listener listener,
+      final ExpensesRequestFactory requestFactory) {
+    this.requestFactory = requestFactory;
+    expenseAdapter = new ListViewAdapter<ExpenseRecord>();
+
+    expenseList = new CellList<ExpenseRecord>(
+        new AbstractCell<ExpenseRecord>() {
+          @Override
+          public void render(ExpenseRecord value, Object viewData,
+              StringBuilder sb) {
+            sb.append("<div onclick='' class='item'>" + value.getDescription() + " " +
+                ExpensesMobile.formatCurrency(value.getAmount().intValue())
+                + "</div>");
+          }
+        });
+
+    expenseSelection = new SingleSelectionModel<ExpenseRecord>();
+    expenseList.setSelectionModel(expenseSelection);
+    expenseSelection.addSelectionChangeHandler(new SelectionModel.SelectionChangeHandler() {
+      public void onSelectionChange(SelectionChangeEvent event) {
+        listener.onExpenseSelected(expenseSelection.getSelectedObject());
+      }
+    });
+
+    expenseAdapter.addView(expenseList);
+    initWidget(expenseList);
+  }
+
+  public void onSuccess(List<ExpenseRecord> newValues) {
+    expenseAdapter.setList(newValues);
+  }
+
+  public void show(ReportRecord report) {
+    requestFactory.expenseRequest().findExpensesByReport(
+        report.getRef(ReportRecord.id)).forProperties(getExpenseColumns()).to(
+        this).fire();
+  }
+
+  private Collection<Property<?>> getExpenseColumns() {
+    List<Property<?>> columns = new ArrayList<Property<?>>();
+    columns.add(ExpenseRecord.description);
+    columns.add(ExpenseRecord.amount);
+    return columns;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
new file mode 100644
index 0000000..d10967f
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/MobileReportList.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.bikeshed.list.client.CellList;
+import com.google.gwt.cell.client.AbstractCell;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.view.client.ListViewAdapter;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SingleSelectionModel;
+import com.google.gwt.view.client.SelectionModel.SelectionChangeEvent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * TODO
+ */
+public class MobileReportList extends Composite implements
+    Receiver<List<ReportRecord>> {
+
+  /**
+   * TODO
+   */
+  public interface Listener {
+    void onReportSelected(ReportRecord report);
+  }
+
+  private final CellList<ReportRecord> reportList;
+  private final ListViewAdapter<ReportRecord> reportAdapter;
+  private final SingleSelectionModel<ReportRecord> reportSelection;
+
+  public MobileReportList(final Listener listener,
+      final ExpensesRequestFactory requestFactory) {
+    reportAdapter = new ListViewAdapter<ReportRecord>();
+
+    reportList = new CellList<ReportRecord>(
+        new AbstractCell<ReportRecord>() {
+          @Override
+          public void render(ReportRecord value, Object viewData,
+              StringBuilder sb) {
+            sb.append("<div onclick='' class='item'>" + value.getPurpose() + "</div>");
+          }
+        });
+
+    reportSelection = new SingleSelectionModel<ReportRecord>();
+    reportSelection.addSelectionChangeHandler(new SelectionModel.SelectionChangeHandler() {
+      public void onSelectionChange(SelectionChangeEvent event) {
+        listener.onReportSelected(reportSelection.getSelectedObject());
+      }
+    });
+
+    reportList.setSelectionModel(reportSelection);
+    reportAdapter.addView(reportList);
+
+    initWidget(reportList);
+
+    requestFactory.reportRequest().findAllReports().forProperties(
+        getReportColumns()).to(this).fire();
+  }
+
+  public void onSuccess(List<ReportRecord> newValues) {
+    reportAdapter.setList(newValues);
+  }
+
+  private Collection<Property<?>> getReportColumns() {
+    List<Property<?>> columns = new ArrayList<Property<?>>();
+    columns.add(ReportRecord.created);
+    columns.add(ReportRecord.purpose);
+    return columns;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Scaffold.java
similarity index 64%
copy from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java
copy to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Scaffold.java
index cd97352..f254e2d 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/Scaffold.java
@@ -13,24 +13,25 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold;
+package com.google.gwt.sample.expenses.gwt.client;
 
+import com.google.gwt.app.place.Activity;
 import com.google.gwt.app.place.ActivityManager;
-import com.google.gwt.app.place.ActivityMapper;
 import com.google.gwt.app.place.PlaceController;
 import com.google.gwt.app.place.PlacePicker;
+import com.google.gwt.app.util.IsWidget;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesEntityTypesProcessor;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.ui.ListActivitiesMapper;
 import com.google.gwt.sample.expenses.gwt.ui.ScaffoldListPlaceRenderer;
 import com.google.gwt.user.client.ui.RootLayoutPanel;
-import com.google.gwt.user.client.ui.Widget;
 import com.google.gwt.valuestore.shared.Record;
 
 import java.util.ArrayList;
@@ -45,7 +46,7 @@
   public void onModuleLoad() {
 
     /* App controllers and services */
-    
+
     final HandlerManager eventBus = new HandlerManager(null);
     final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class);
     requestFactory.init(eventBus);
@@ -63,18 +64,35 @@
     placePicker.setPlaces(getTopPlaces());
 
     /*
-     * The body is run by an ActivitManager that listens for PlaceChange events
-     * and finds the corresponding Activity to run
+     * The app is run by ActivityManager instances that listen for place change
+     * events and run the appropriate Activity
+     * 
+     * The top half runs list activities of a traditional master / details view,
+     * although here "master" is a misnomer. The two ActivityManagers are
+     * completely independent of one another.
      */
+    final ActivityManager<ScaffoldPlace> masterActivityManager = new ActivityManager<ScaffoldPlace>(
+        new ScaffoldMasterActivities(new ListActivitiesMapper(eventBus,
+            requestFactory, placeController)), eventBus);
 
-    final ActivityMapper<ScaffoldPlace> mapper = new ScaffoldActivities(
-        requestFactory, placeController);
-    final ActivityManager<ScaffoldPlace> activityManager = new ActivityManager<ScaffoldPlace>(
-        mapper, eventBus);
+    masterActivityManager.setDisplay(new Activity.Display() {
+      public void showActivityWidget(IsWidget widget) {
+        shell.getMasterPanel().setWidget(
+            widget == null ? null : widget.asWidget());
+      }
+    });
 
-    activityManager.setDisplay(new ActivityManager.View() {
-      public void setWidget(Widget widget) {
-        shell.getBody().setWidget(widget);
+    /*
+     * The bottom half handles details
+     */
+    final ActivityManager<ScaffoldPlace> detailsActivityManager = new ActivityManager<ScaffoldPlace>(
+        new ScaffoldDetailsActivities(requestFactory, placeController),
+        eventBus);
+
+    detailsActivityManager.setDisplay(new Activity.Display() {
+      public void showActivityWidget(IsWidget widget) {
+        shell.getDetailsPanel().setWidget(
+            widget == null ? null : widget.asWidget());
       }
     });
 
@@ -84,7 +102,7 @@
     loading.getParentElement().removeChild(loading);
 
     /* And show the user the shell */
-    
+
     RootLayoutPanel.get().add(shell);
   }
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldDetailsActivities.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldDetailsActivities.java
new file mode 100644
index 0000000..b2add58
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldDetailsActivities.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.app.place.Activity;
+import com.google.gwt.app.place.ActivityMapper;
+import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.sample.expenses.gwt.client.place.BaseScaffoldPlaceFilter;
+import com.google.gwt.sample.expenses.gwt.client.place.EmployeeScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ReportScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
+import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeActivitiesMapper;
+import com.google.gwt.sample.expenses.gwt.ui.report.ReportActivitiesMapper;
+
+/**
+ * Finds the activity to run for a particular {@link ScaffoldPlace} in the bottom
+ * half of the {@link ScaffoldShell}.
+ */
+public final class ScaffoldDetailsActivities implements
+    ActivityMapper<ScaffoldPlace> {
+
+  private final ActivityMapper<EmployeeScaffoldPlace> employeeActivities;
+  private final ActivityMapper<ReportScaffoldPlace> reportActivities;
+  
+  public ScaffoldDetailsActivities(ExpensesRequestFactory requestFactory,
+      PlaceController<ScaffoldPlace> placeController) {
+    this.employeeActivities = new EmployeeActivitiesMapper(
+        requestFactory, placeController);
+    this.reportActivities = new ReportActivitiesMapper(requestFactory,
+        placeController);
+  }
+
+  public Activity getActivity(ScaffoldPlace place) {
+    return place.acceptFilter(new BaseScaffoldPlaceFilter<Activity>(null) {
+      public Activity filter(EmployeeScaffoldPlace place) {
+        return employeeActivities.getActivity(place);
+      }
+
+      public Activity filter(ReportScaffoldPlace place) {
+        return reportActivities.getActivity(place);
+      }
+    });
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMasterActivities.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMasterActivities.java
new file mode 100644
index 0000000..64a5af7
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMasterActivities.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.app.place.Activity;
+import com.google.gwt.app.place.ActivityMapper;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+
+/**
+ * Finds the activity to run for a particular {@link ScaffoldPlace} in the top
+ * half of the {@link ScaffoldShell}.
+ */
+public final class ScaffoldMasterActivities implements
+    ActivityMapper<ScaffoldPlace> {
+
+  private final ActivityMapper<ListScaffoldPlace> listActivities;
+
+  private Activity last = null;
+
+  public ScaffoldMasterActivities(
+      ActivityMapper<ListScaffoldPlace> listActivities) {
+    this.listActivities = listActivities;
+  }
+
+  public Activity getActivity(ScaffoldPlace place) {
+    if (place instanceof ListScaffoldPlace) {
+      last = listActivities.getActivity((ListScaffoldPlace) place);
+    }
+
+    return last;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobile.java
similarity index 76%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobile.java
index cd97352..b336ac3 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/Scaffold.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobile.java
@@ -13,24 +13,26 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold;
+package com.google.gwt.sample.expenses.gwt.client;
 
+import com.google.gwt.app.place.Activity;
 import com.google.gwt.app.place.ActivityManager;
 import com.google.gwt.app.place.ActivityMapper;
 import com.google.gwt.app.place.PlaceController;
 import com.google.gwt.app.place.PlacePicker;
+import com.google.gwt.app.util.IsWidget;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesEntityTypesProcessor;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.ui.ListActivitiesMapper;
 import com.google.gwt.sample.expenses.gwt.ui.ScaffoldListPlaceRenderer;
 import com.google.gwt.user.client.ui.RootLayoutPanel;
-import com.google.gwt.user.client.ui.Widget;
 import com.google.gwt.valuestore.shared.Record;
 
 import java.util.ArrayList;
@@ -38,14 +40,16 @@
 import java.util.List;
 
 /**
- * Application for browsing the entities of the Expenses app.
+ * Mobile application for browsing the entities of the Expenses app.
+ * 
+ * TODO(jgw): Make this actually mobile-friendly.
  */
-public class Scaffold implements EntryPoint {
+public class ScaffoldMobile implements EntryPoint {
 
   public void onModuleLoad() {
 
     /* App controllers and services */
-    
+
     final HandlerManager eventBus = new HandlerManager(null);
     final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class);
     requestFactory.init(eventBus);
@@ -54,7 +58,7 @@
 
     /* Top level UI */
 
-    final ScaffoldShell shell = new ScaffoldShell();
+    final ScaffoldMobileShell shell = new ScaffoldMobileShell();
 
     /* Left side lets us pick from all the types of entities */
 
@@ -67,14 +71,15 @@
      * and finds the corresponding Activity to run
      */
 
-    final ActivityMapper<ScaffoldPlace> mapper = new ScaffoldActivities(
+    final ActivityMapper<ScaffoldPlace> mapper = new ScaffoldMobileActivities(
+        new ListActivitiesMapper(eventBus, requestFactory, placeController),
         requestFactory, placeController);
     final ActivityManager<ScaffoldPlace> activityManager = new ActivityManager<ScaffoldPlace>(
         mapper, eventBus);
 
-    activityManager.setDisplay(new ActivityManager.View() {
-      public void setWidget(Widget widget) {
-        shell.getBody().setWidget(widget);
+    activityManager.setDisplay(new Activity.Display() {
+      public void showActivityWidget(IsWidget widget) {
+        shell.getBody().setWidget(widget == null ? null : widget.asWidget());
       }
     });
 
@@ -84,7 +89,7 @@
     loading.getParentElement().removeChild(loading);
 
     /* And show the user the shell */
-    
+
     RootLayoutPanel.get().add(shell);
   }
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldActivities.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileActivities.java
similarity index 74%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldActivities.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileActivities.java
index da47156..f7ae53a 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldActivities.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileActivities.java
@@ -13,25 +13,25 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold;
+package com.google.gwt.sample.expenses.gwt.client;
 
 import com.google.gwt.app.place.Activity;
 import com.google.gwt.app.place.ActivityMapper;
 import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.sample.expenses.gwt.client.place.EmployeeScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ReportScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlaceFilter;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.EmployeeScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ReportScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlaceFilter;
-import com.google.gwt.sample.expenses.gwt.ui.ListActivitiesMapper;
 import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeActivitiesMapper;
 import com.google.gwt.sample.expenses.gwt.ui.report.ReportActivitiesMapper;
 
 /**
  * Finds the activity to run for a particular {@link ScaffoldPlace}.
  */
-public final class ScaffoldActivities implements ActivityMapper<ScaffoldPlace> {
+public final class ScaffoldMobileActivities implements
+    ActivityMapper<ScaffoldPlace> {
 
   private final ActivityMapper<ListScaffoldPlace> listActivitiesBuilder;
   private final ActivityMapper<EmployeeScaffoldPlace> employeeActivitiesBuilder;
@@ -41,10 +41,11 @@
    * @param requestFactory
    * @param placeController
    */
-  public ScaffoldActivities(ExpensesRequestFactory requestFactory,
+  public ScaffoldMobileActivities(
+      ActivityMapper<ListScaffoldPlace> listActivitiesBuilder,
+      ExpensesRequestFactory requestFactory,
       PlaceController<ScaffoldPlace> placeController) {
-    this.listActivitiesBuilder = new ListActivitiesMapper(requestFactory,
-        placeController);
+    this.listActivitiesBuilder = listActivitiesBuilder;
     this.employeeActivitiesBuilder = new EmployeeActivitiesMapper(
         requestFactory, placeController);
     this.reportActivitiesBuilder = new ReportActivitiesMapper(requestFactory,
@@ -53,7 +54,6 @@
 
   public Activity getActivity(ScaffoldPlace place) {
     return place.acceptFilter(new ScaffoldPlaceFilter<Activity>() {
-
       public Activity filter(EmployeeScaffoldPlace place) {
         return employeeActivitiesBuilder.getActivity(place);
       }
@@ -67,4 +67,4 @@
       }
     });
   }
-}
\ No newline at end of file
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileShell.java
similarity index 80%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileShell.java
index b5eb33f..ec5ed38 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileShell.java
@@ -1,24 +1,24 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold;
+package com.google.gwt.sample.expenses.gwt.client;
 
 import com.google.gwt.app.place.PlacePickerView;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
@@ -26,18 +26,18 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * The outermost UI of the application.
+ * TODO
  */
-public class ScaffoldShell extends Composite {
-  interface Binder extends UiBinder<Widget, ScaffoldShell> {
-  }
+public class ScaffoldMobileShell extends Composite {
+
+  interface Binder extends UiBinder<Widget, ScaffoldMobileShell> { }
   private static final Binder BINDER = GWT.create(Binder.class);
 
   @UiField SimplePanel body;
-  @UiField PlacePickerView<ListScaffoldPlace> placesBox;
   @UiField DivElement error;
+  @UiField PlacePickerView<ListScaffoldPlace> placesBox;
 
-  public ScaffoldShell() {
+  public ScaffoldMobileShell() {
     initWidget(BINDER.createAndBindUi(this));
   }
 
@@ -48,9 +48,6 @@
     return body;
   }
 
-  /**
-   * @return the navigator
-   */
   public PlacePickerView<ListScaffoldPlace> getPlacesBox() {
     return placesBox;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileShell.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileShell.ui.xml
new file mode 100644
index 0000000..88f1194
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldMobileShell.ui.xml
@@ -0,0 +1,39 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:a='urn:import:com.google.gwt.app.client'>
+
+  <ui:style>
+    .disabled {
+     color: gray;
+    }
+
+    .banner {
+       background-color: wheat;
+     }
+
+    .title {
+       text-align: left;
+       margin-left: 1em;
+     }
+
+    .error {
+     text-align: center;
+     background-color: red;
+    }
+
+    .users {
+    }
+  </ui:style>
+
+  <g:HTMLPanel>
+    <g:HTML styleName='{style.banner}'>
+      <div class='{style.error}' ui:field='error'></div>
+      <h2 class='{style.title}'>Expenses Entity Browser</h2>
+    </g:HTML>
+
+    <a:ListBoxPlacePickerView width='90%' visibleItemCount='10' ui:field='placesBox'/>
+
+    <g:SimplePanel ui:field='body'/>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldShell.java
similarity index 79%
copy from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.java
copy to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldShell.java
index b5eb33f..fa15af0 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldShell.java
@@ -13,12 +13,12 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold;
+package com.google.gwt.sample.expenses.gwt.client;
 
 import com.google.gwt.app.place.PlacePickerView;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
@@ -33,7 +33,8 @@
   }
   private static final Binder BINDER = GWT.create(Binder.class);
 
-  @UiField SimplePanel body;
+  @UiField SimplePanel master;
+  @UiField SimplePanel details;
   @UiField PlacePickerView<ListScaffoldPlace> placesBox;
   @UiField DivElement error;
 
@@ -42,10 +43,17 @@
   }
 
   /**
-   * @return the body
+   * @return the panel to hold the details
    */
-  public SimplePanel getBody() {
-    return body;
+  public SimplePanel getDetailsPanel() {
+    return details;
+  }
+
+  /**
+   * @return the panel to hold the master list
+   */
+  public SimplePanel getMasterPanel() {
+    return master;
   }
 
   /**
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldShell.ui.xml
similarity index 78%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.ui.xml
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldShell.ui.xml
index d75e07f..9bf579a 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldShell.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/ScaffoldShell.ui.xml
@@ -44,12 +44,16 @@
     </g:west>
 
     <g:center>
-      <g:SimplePanel ui:field='body'>
-      <g:HTML>
-      <h3>Welcome to Expenses</h3>
-      <p>Choose an entity set from the list on the left to get started</p>
-      </g:HTML>
+      <g:FlowPanel>
+      <g:SimplePanel ui:field='master'>
+        <g:HTML>
+          <h3>Welcome to Expenses</h3>
+          <p>Choose an entity set from the list on the left to get started</p>
+        </g:HTML>
       </g:SimplePanel>
+      <g:SimplePanel ui:field='details'>
+      </g:SimplePanel>
+      </g:FlowPanel>
     </g:center>
   </g:DockLayoutPanel>
 </ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/SortableColumn.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/SortableColumn.java
new file mode 100644
index 0000000..02c4add
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/SortableColumn.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.cell.client.Cell;
+
+import java.util.Comparator;
+
+/**
+ * A column that provides forward and reverse {@link Comparator}s.
+ * 
+ * @param <T> the row type
+ * @param <C> the column type
+ */
+public abstract class SortableColumn<T, C> extends Column<T, C> {
+  private Comparator<T> forwardComparator;
+
+  private Comparator<T> reverseComparator;
+
+  public SortableColumn(Cell<C> cell) {
+    super(cell);
+  }
+
+  /**
+   * Convenience method to return a {@link Comparator} that may be used to sort
+   * records of type T by the values of this column, using the natural ordering
+   * of the column type C. If C does not implement Comparable<C>, a runtime
+   * exception will be thrown when the returned comparator's
+   * {@link Comparator#compare(Object, Object) compare} method is called. If
+   * reverse is true, the returned comparator will sort in reverse order. The
+   * returned comparator instances are cached for future calls.
+   * 
+   * @param reverse if true, sort in reverse
+   * @return an instance of Comparator<T>
+   */
+  public Comparator<T> getComparator(final boolean reverse) {
+    if (!reverse && forwardComparator != null) {
+      return forwardComparator;
+    }
+    if (reverse && reverseComparator != null) {
+      return reverseComparator;
+    }
+    Comparator<T> comparator = new Comparator<T>() {
+      @SuppressWarnings("unchecked")
+      public int compare(T o1, T o2) {
+        C c1 = getValue(o1);
+        C c2 = getValue(o2);
+        int comparison = ((Comparable<C>) c1).compareTo(c2);
+        return reverse ? -comparison : comparison;
+      }
+    };
+
+    if (reverse) {
+      reverseComparator = comparator;
+    } else {
+      forwardComparator = comparator;
+    }
+    return comparator;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/SortableHeader.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/SortableHeader.java
new file mode 100644
index 0000000..01181d7
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/SortableHeader.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client;
+
+import com.google.gwt.bikeshed.list.client.Header;
+import com.google.gwt.cell.client.ClickableTextCell;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+
+/**
+ * A {@link Header} subclass that maintains sorting state and displays an icon
+ * to indicate the sort direction.
+ */
+public class SortableHeader extends Header<String> {
+
+  /**
+   * Image resources.
+   */
+  public static interface Resources extends ClientBundle {
+
+    ImageResource downArrow();
+
+    ImageResource upArrow();
+  }
+
+  private static final Resources RESOURCES = GWT.create(Resources.class);
+  private static final int IMAGE_WIDTH = 16;
+  private static final String DOWN_ARROW = makeImage(RESOURCES.downArrow());
+  private static final String UP_ARROW = makeImage(RESOURCES.upArrow());
+
+  private static String makeImage(ImageResource resource) {
+    AbstractImagePrototype proto = AbstractImagePrototype.create(resource);
+    return proto.getHTML().replace("style='",
+        "style='position:absolute;right:0px;top:0px;");
+  }
+
+  private boolean reverseSort = false;
+  private boolean sorted = false;
+  private String text;
+
+  SortableHeader(String text) {
+    super(new ClickableTextCell());
+    this.text = text;
+  }
+
+  public boolean getReverseSort() {
+    return reverseSort;
+  }
+
+  @Override
+  public String getValue() {
+    return text;
+  }
+
+  public void render(StringBuilder sb) {
+    int imageWidth = IMAGE_WIDTH;
+    sb.append("<div style='position:relative;padding-right:");
+    sb.append(imageWidth);
+    sb.append("px;'>");
+    if (sorted) {
+      if (reverseSort) {
+        sb.append(DOWN_ARROW);
+      } else {
+        sb.append(UP_ARROW);
+      }
+    } else {
+      sb.append("<div style='position:absolute;display:none;'></div>");
+    }
+    sb.append("<div>");
+    sb.append(text);
+    sb.append("</div></div>");
+  }
+
+  public void setReverseSort(boolean reverseSort) {
+    this.reverseSort = reverseSort;
+  }
+
+  public void setSorted(boolean sorted) {
+    this.sorted = sorted;
+  }
+
+  public void toggleReverseSort() {
+    this.reverseSort = !this.reverseSort;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/downArrow.png b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/downArrow.png
new file mode 100644
index 0000000..eb00305
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/downArrow.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/BaseScaffoldPlaceFilter.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/BaseScaffoldPlaceFilter.java
new file mode 100644
index 0000000..e460616
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/BaseScaffoldPlaceFilter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.client.place;
+
+/**
+ * A convenient base implementation of {@link ScaffoldPlaceFilter}. Just
+ * override the methods for the types of places that you actually care about.
+ * <p>
+ * <strong>NB</strong>It is a bad idea to use this class if your code needs to
+ * be extended when new subclasses of {@link ScaffoldPlace} are added. If that's
+ * the case, implement {@link ScaffoldPlaceFilter} yourself, so that the
+ * compiler will let you know to update your code.
+ * 
+ * @param <T> the type of the default value
+ */
+public class BaseScaffoldPlaceFilter<T> implements ScaffoldPlaceFilter<T> {
+  private final T defaultValue;
+  
+  public BaseScaffoldPlaceFilter(T defaultValue) {
+    this.defaultValue = defaultValue;
+  }
+
+  public T filter(EmployeeScaffoldPlace place) {
+    return defaultValue;
+  }
+
+  public T filter(ListScaffoldPlace place) {
+    return defaultValue;
+  }
+
+  public T filter(ReportScaffoldPlace place) {
+    return defaultValue;
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/BaseScaffoldPlaceProcessor.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/BaseScaffoldPlaceProcessor.java
similarity index 86%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/BaseScaffoldPlaceProcessor.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/BaseScaffoldPlaceProcessor.java
index c12fd7f..930a4f9 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/BaseScaffoldPlaceProcessor.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/BaseScaffoldPlaceProcessor.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 /**
  * A convenient base implementation of {@link ScaffoldPlaceProcessor}. Just
@@ -21,8 +21,8 @@
  * <p>
  * <strong>NB</strong>It is a bad idea to use this class if your code needs to
  * be extended when new subclasses of {@link ScaffoldPlace} are added. If that's
- * the case, implement ScaffoldPlaceVisitor yourself, so that the compiler will
- * let you know to update your code.
+ * the case, implement {@link ScaffoldPlaceProcessor} yourself, so that the compiler
+ * will let you know to update your code.
  */
 public class BaseScaffoldPlaceProcessor implements ScaffoldPlaceProcessor {
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/EmployeeScaffoldPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/EmployeeScaffoldPlace.java
similarity index 95%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/EmployeeScaffoldPlace.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/EmployeeScaffoldPlace.java
index 2e67c63..078b492 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/EmployeeScaffoldPlace.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/EmployeeScaffoldPlace.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ListScaffoldPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ListScaffoldPlace.java
similarity index 96%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ListScaffoldPlace.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ListScaffoldPlace.java
index e2abdee..9fa2fe3 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ListScaffoldPlace.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ListScaffoldPlace.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 import com.google.gwt.valuestore.shared.Record;
 
@@ -72,6 +72,4 @@
     result = prime * result + ((type == null) ? 0 : type.hashCode());
     return result;
   }
-  
-  
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ReportScaffoldPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ReportScaffoldPlace.java
similarity index 95%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ReportScaffoldPlace.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ReportScaffoldPlace.java
index 731e6b0..ec213dc 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ReportScaffoldPlace.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ReportScaffoldPlace.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlace.java
similarity index 93%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlace.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlace.java
index 4cf866b..75d56ad 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlace.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlace.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 import com.google.gwt.app.place.Place;
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceFilter.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlaceFilter.java
similarity index 93%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceFilter.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlaceFilter.java
index 903a6fb..a56fff4 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceFilter.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlaceFilter.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 /**
  * Implemented by objects that filter {@link ScaffoldPlace} subtypes to other
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceProcessor.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlaceProcessor.java
similarity index 93%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceProcessor.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlaceProcessor.java
index 3cc11d1..3293201 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceProcessor.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldPlaceProcessor.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 /**
  * Implemented by objects to allow them to process specific sub-types of
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldRecordPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldRecordPlace.java
similarity index 96%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldRecordPlace.java
rename to bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldRecordPlace.java
index 1fd97a5..7ad5cef 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldRecordPlace.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/place/ScaffoldRecordPlace.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.sample.expenses.gwt.client.place;
 
 /**
  * A place in the app focused on the {@link Values} of a particular type of
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/upArrow.png b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/upArrow.png
new file mode 100644
index 0000000..03031f2
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/client/upArrow.png
Binary files differ
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/Customized.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/Customized.java
deleted file mode 100644
index 75c8b6a..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/Customized.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.expenses.gwt.customized;
-
-import com.google.gwt.core.client.EntryPoint;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.shared.HandlerManager;
-import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
-import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
-import com.google.gwt.sample.expenses.gwt.request.ReportRecordChanged;
-import com.google.gwt.user.client.ui.RootLayoutPanel;
-import com.google.gwt.valuestore.shared.DeltaValueStore;
-import com.google.gwt.valuestore.shared.Property;
-import com.google.gwt.valuestore.shared.Record;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Entry point classes define <code>onModuleLoad()</code>.
- * <p>
- * This app is a mess right now, but it will become the showcase example of a
- * custom app written to RequestFactory
- */
-public class Customized implements EntryPoint {
-
-  /**
-   * This is the entry point method.
-   */
-  public void onModuleLoad() {
-    final HandlerManager eventBus = new HandlerManager(null);
-    final ExpensesRequestFactory requestFactory = GWT.create(ExpensesRequestFactory.class);
-    requestFactory.init(eventBus);
-
-    RootLayoutPanel root = RootLayoutPanel.get();
-
-    final CustomizedShell shell = new CustomizedShell();
-    final EmployeeList employees = new EmployeeList(shell.users);
-
-    root.add(shell);
-
-    shell.setListener(new CustomizedShell.Listener() {
-      public void setPurpose(ReportRecord report, String purpose) {
-        DeltaValueStore deltaValueStore = requestFactory.getValueStore().spawnDeltaView();
-        deltaValueStore.set(ReportRecord.purpose, report, purpose);
-        requestFactory.syncRequest(deltaValueStore).fire();
-      }
-    });
-
-    employees.setListener(new EmployeeList.Listener() {
-      public void onEmployeeSelected(EmployeeRecord e) {
-        requestFactory.reportRequest().findReportsByEmployee(
-            e.getRef(Record.id)).forProperties(getReportColumns()).to(shell).fire();
-      }
-    });
-
-    eventBus.addHandler(ReportRecordChanged.TYPE, shell);
-
-    requestFactory.employeeRequest().findAllEmployees().forProperties(
-        getEmployeeMenuProperties()).to(employees).fire();
-  }
-
-  private Collection<Property<?>> getEmployeeMenuProperties() {
-    List<Property<?>> columns = new ArrayList<Property<?>>();
-    columns.add(EmployeeRecord.displayName);
-    columns.add(EmployeeRecord.userName);
-    return columns;
-  }
-
-  private Collection<Property<?>> getReportColumns() {
-    List<Property<?>> columns = new ArrayList<Property<?>>();
-    columns.add(ReportRecord.created);
-    columns.add(ReportRecord.purpose);
-    return columns;
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/CustomizedShell.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/CustomizedShell.java
deleted file mode 100644
index 3401a94..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/CustomizedShell.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.expenses.gwt.customized;
-
-import com.google.gwt.bikeshed.cells.client.DateCell;
-import com.google.gwt.bikeshed.cells.client.EditTextCell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.cells.client.TextCell;
-import com.google.gwt.bikeshed.list.client.Column;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
-import com.google.gwt.sample.expenses.gwt.request.ReportRecordChanged;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiFactory;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.TakesValueList;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.Date;
-import java.util.List;
-
-/**
- * UI shell for expenses sample app. A horrible clump of stuff that should be
- * refactored into proper MVP pieces.
- */
-public class CustomizedShell extends Composite implements
-    TakesValueList<ReportRecord>, ReportRecordChanged.Handler {
-  interface Listener {
-    void setPurpose(ReportRecord report, String purpose);
-  }
-
-  interface ShellUiBinder extends UiBinder<Widget, CustomizedShell> {
-  }
-
-  private static ShellUiBinder uiBinder = GWT.create(ShellUiBinder.class);
-
-  @UiField Element error;
-  @UiField PagingTableListView<ReportRecord> listView;
-  @UiField ListBox users;
-
-  private Column<ReportRecord, Date, Void> createdCol = new Column<ReportRecord, Date, Void>(
-      new DateCell()) {
-    @Override
-    public Date getValue(ReportRecord object) {
-      return object.getCreated();
-    }
-  };
-  private Listener listener;
-  private final ListViewAdapter<ReportRecord> adapter;
-
-  private Column<ReportRecord, String, String> purposeCol = new Column<ReportRecord, String, String>(
-      new EditTextCell()) {
-    @Override
-    public String getValue(ReportRecord object) {
-      return object.getPurpose();
-    }
-  };
-
-  private Column<ReportRecord, String, Void> statusCol = new Column<ReportRecord, String, Void>(
-      TextCell.getInstance()) {
-    @Override
-    public String getValue(ReportRecord object) {
-      return "...";
-    }
-  };
-
-  private List<ReportRecord> values;
-
-  public CustomizedShell() {
-    adapter = new ListViewAdapter<ReportRecord>();
-    initWidget(uiBinder.createAndBindUi(this));
-
-    listView.addColumn(createdCol, "Created");
-    listView.addColumn(statusCol, "Status (tbd)");
-    listView.addColumn(purposeCol, "Purpose");
-
-    purposeCol.setFieldUpdater(new FieldUpdater<ReportRecord, String, String>() {
-      public void update(int index, ReportRecord object, String value,
-          String viewData) {
-        adapter.getList().set(index, object);
-        listener.setPurpose(object, value);
-      }
-    });
-  }
-
-  public List<ReportRecord> getValues() {
-    return values;
-  }
-
-  public void onReportChanged(ReportRecordChanged event) {
-    refresh();
-  }
-
-  public void setListener(Listener listener) {
-    this.listener = listener;
-  }
-
-  public void setValueList(List<ReportRecord> newValues) {
-    this.values = newValues;
-    refresh();
-  }
-
-  @UiFactory
-  PagingTableListView<ReportRecord> createListView() {
-    PagingTableListView<ReportRecord> table = new PagingTableListView<ReportRecord>(10);
-    adapter.addView(table);
-    return table;
-  }
-
-  private void refresh() {
-    adapter.setList(values);
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/CustomizedShell.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/CustomizedShell.ui.xml
deleted file mode 100644
index 49c6dd2..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/CustomizedShell.ui.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
-<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
-  xmlns:g='urn:import:com.google.gwt.user.client.ui'
-  xmlns:l='urn:import:com.google.gwt.bikeshed.list.client'>
-
-  <ui:style>
-    .disabled {
-     color: gray;
-    }
-
-    table.reports td {
-     border-width: 1px;
-     padding: 1px;
-     border-style: solid;
-     background-color: white;
-    }
-
-    .error {
-     position: absolute;
-     width: 100%;
-     text-align: center;
-     background-color: red;
-    }
-
-    .users {
-     position: absolute;
-     right: 0;
-    }
-  </ui:style>
-
-  <g:DockLayoutPanel unit='EM'>
-    <g:north size='3'>
-      <g:HTMLPanel>
-        <div class='{style.error}' ui:field='error'></div>
-        <g:ListBox addStyleNames='{style.users}' ui:field='users'></g:ListBox>
-      </g:HTMLPanel>
-    </g:north>
-
-    <g:west size='15'>
-      <g:HTML styleName='{style.disabled}'>
-        <div>New expense report</div>
-        <div>Submitted reports</div>
-        <div>Pending reports</div>
-        <div>Generate summary</div>
-      </g:HTML>
-    </g:west>
-
-    <g:center>
-      <g:HTMLPanel width='100%' height='100%'>
-        <h1>Expenses</h1>
-        <l:PagingTableListView ui:field='listView'/>
-      </g:HTMLPanel>
-    </g:center>
-  </g:DockLayoutPanel>
-</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/EmployeeList.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/EmployeeList.java
deleted file mode 100644
index 80e80fb..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/customized/EmployeeList.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2010 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.gwt.sample.expenses.gwt.customized;
-
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.TakesValueList;
-
-import java.util.List;
-
-/**
- * Manages the Employee ListBox. This should grow into a proper View, with a
- * corresponding Presenter factored out of {@link Customized}
- */
-public final class EmployeeList implements TakesValueList<EmployeeRecord> {
-  interface Listener {
-    void onEmployeeSelected(EmployeeRecord e);
-  }
-
-  private final class MyChangeHandler implements ChangeHandler {
-    public void onChange(ChangeEvent event) {
-      int selectedIndex = listBox.getSelectedIndex();
-      EmployeeRecord values = employeeValues.get(selectedIndex);
-      listener.onEmployeeSelected(values);
-    }
-  }
-
-  private final ListBox listBox;
-  private List<EmployeeRecord> employeeValues;
-  private Listener listener;
-
-  /**
-   * @param listBox
-   */
-  public EmployeeList(ListBox listBox) {
-    this.listBox = listBox;
-    listBox.addChangeHandler(new MyChangeHandler());
-  }
-
-  public void setListener(Listener listener) {
-    this.listener = listener;
-  }
-
-  public void setValueList(List<EmployeeRecord> newValues) {
-    this.employeeValues = newValues;
-    listBox.clear();
-    for (int i = 0; i < employeeValues.size(); i++) {
-      EmployeeRecord values = employeeValues.get(i);
-      listBox.addItem(values.getDisplayName());
-    }
-  }
-}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesDetailsPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesDetailsPlace.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesDetailsPlace.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesEditorPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesEditorPlace.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesEditorPlace.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesListPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesListPlace.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesListPlace.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesPlace.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesPlace.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesPlaces.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesPlaces.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesPlaces.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesRecordPlace.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesRecordPlace.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/place/ExpensesRecordPlace.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
index 8a21d16..75dc442 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRecord.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
+import com.google.gwt.requestfactory.shared.ServerType;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
@@ -24,17 +25,27 @@
  * <p>
  * IRL this class will be generated by a JPA-savvy tool run before compilation.
  */
-@ServerType(type = com.google.gwt.sample.expenses.server.domain.Employee.class, token = "Employee")
+@ServerType(type = com.google.gwt.sample.expenses.server.domain.Employee.class)
 public interface EmployeeRecord extends Record {
+
+  /**
+   * Used as input to {@link com.google.gwt.valuestore.shared.DeltaValueStore#create(Record)
+   * DeltaValueStore#create()} and in the wire format during sync requests.
+   */
+  String TOKEN = "EmployeeRecord";
+
   Property<String> userName = new Property<String>("userName", String.class);
   Property<String> displayName = new Property<String>("displayName",
       String.class);
-  Property<EmployeeRecord> supervisor = new Property<EmployeeRecord>(
-      "supervisor", EmployeeRecord.class);
+  Property<String> password = new Property<String>("password", String.class);
+  Property<String> supervisorKey = new Property<String>("supervisorKey",
+      String.class);
 
   String getDisplayName();
 
-  EmployeeRecord getSupervisor();
+  String getPassword();
+
+  String getSupervisorKey();
 
   String getUserName();
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
index 2a46b8a..65a762a 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/EmployeeRequest.java
@@ -15,11 +15,11 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
-import com.google.gwt.requestfactory.shared.EntityListRequest;
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.requestfactory.shared.RecordRequest;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.ServerOperation;
 import com.google.gwt.valuestore.shared.PropertyReference;
-import com.google.gwt.valuestore.shared.Record;
 
 /**
  * "API Generated" request selector interface implemented by objects that give
@@ -34,6 +34,38 @@
    * Defines the server operations that handle these requests.
    */
   public enum ServerOperations implements RequestFactory.RequestDefinition {
+    COUNT_EMPLOYEES {
+      public String getDomainMethodName() {
+        return "countEmployees";
+      }
+
+      public Class<?> getReturnType() {
+        return Long.class;
+      }
+
+      public boolean isReturnTypeList() {
+        return false;
+      }
+    },
+
+    COUNT_EMPLOYEES_BY_DEPARTMENT {
+      public String getDomainMethodName() {
+        return "countEmployeesByDepartment";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[]{java.lang.String.class};
+      }
+
+      public Class<?> getReturnType() {
+        return Long.class;
+      }
+
+      public boolean isReturnTypeList() {
+        return false;
+      }
+    },
+
     FIND_ALL_EMPLOYEES {
       public String getDomainMethodName() {
         return "findAllEmployees";
@@ -42,13 +74,37 @@
 
     FIND_EMPLOYEE {
       public String getDomainMethodName() {
-        return "findListOfOneEmployee";
+        return "findEmployee";
       }
 
       public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.String.class};
+        return new Class[] {Long.class};
       }
-   };
+
+      public boolean isReturnTypeList() {
+        return false;
+      }
+    },
+
+    FIND_EMPLOYEE_ENTRIES {
+      public String getDomainMethodName() {
+        return "findEmployeeEntries";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[]{int.class, int.class};
+      }
+    },
+
+    FIND_EMPLOYEE_ENTRIES_BY_DEPARTMENT {
+      public String getDomainMethodName() {
+        return "findEmployeeEntriesByDepartment";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[]{java.lang.String.class, int.class, int.class};
+      }
+    };
 
     public String getDomainClassName() {
       return "com.google.gwt.sample.expenses.server.domain.Employee";
@@ -58,20 +114,51 @@
       return null;
     }
 
-    public Class<? extends Record> getReturnType() {
+    public Class<?> getReturnType() {
       return EmployeeRecord.class;
     }
+
+    public boolean isReturnTypeList() {
+      return true;
+    }
   }
 
   /**
    * @return a request object
    */
+  @ServerOperation("COUNT_EMPLOYEES")
+  RequestFactory.RequestObject<Long> countEmployees();
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("COUNT_EMPLOYEES_BY_DEPARTMENT")
+  RequestFactory.RequestObject<Long> countEmployeesByDepartment(
+      String department);
+
+  /**
+   * @return a request object
+   */
   @ServerOperation("FIND_ALL_EMPLOYEES")
-  EntityListRequest<EmployeeRecord> findAllEmployees();
+  RecordListRequest<EmployeeRecord> findAllEmployees();
 
   /**
    * @return a request object
    */
   @ServerOperation("FIND_EMPLOYEE")
-  EntityListRequest<EmployeeRecord> findEmployee(PropertyReference<String> id);
+  RecordRequest<EmployeeRecord> findEmployee(PropertyReference<String> id);
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("FIND_EMPLOYEE_ENTRIES")
+  RecordListRequest<EmployeeRecord> findEmployeeEntries(int firstResult,
+      int maxResults);
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("FIND_EMPLOYEE_ENTRIES_BY_DEPARTMENT")
+  RecordListRequest<EmployeeRecord> findEmployeeEntriesByDepartment(
+      String department, int firstResult, int maxResults);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
new file mode 100644
index 0000000..80c22e2
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecord.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.request;
+
+import com.google.gwt.requestfactory.shared.ServerType;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.util.Date;
+
+/**
+ * "API Generated" DTO interface based on
+ * {@link com.google.gwt.sample.expenses.server.domain.Expense}.
+ * <p>
+ * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ */
+@ServerType(type = com.google.gwt.sample.expenses.server.domain.Expense.class)
+public interface ExpenseRecord extends Record {
+
+  /**
+   * Used as input to {@link com.google.gwt.valuestore.shared.DeltaValueStore#create(Record)
+   * DeltaValueStore#create()} and in the wire format during sync requests.
+   */
+  String TOKEN = "ExpenseRecord";
+
+  Property<Double> amount = new Property<Double>("amount", Double.class);
+  Property<String> approval = new Property<String>("approval", String.class);
+  Property<String> category = new Property<String>("category", String.class);
+  Property<Date> date = new Property<Date>("date", Date.class);
+  Property<String> description = new Property<String>("description", String.class);
+  Property<String> reasonDenied = new Property<String>("reasonDenied", String.class);
+  Property<String> reportId = new Property<String>("reportId", String.class);
+
+  Double getAmount();
+  
+  String getApproval();
+  
+  String getCategory();
+
+  Date getDate();
+
+  String getDescription();
+
+  String getId();
+
+  String getReasonDenied();
+
+  String getReportId();
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecordChanged.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecordChanged.java
new file mode 100644
index 0000000..6286d37
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRecordChanged.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.request;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.valuestore.shared.RecordChangedEvent;
+import com.google.gwt.requestfactory.shared.RequestFactory.WriteOperation;
+
+/**
+ * "API Generated" event posted when the values of an {@link ExpenseRecord}
+ * change.
+ * <p>
+ * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ */
+public class ExpenseRecordChanged extends
+    RecordChangedEvent<ExpenseRecord, ExpenseRecordChanged.Handler> {
+
+  /**
+   * Implemented by handlers of this type of event.
+   */
+  public interface Handler extends EventHandler {
+    void onExpenseRecordChanged(ExpenseRecordChanged event);
+  }
+
+  public static final Type<Handler> TYPE = new Type<Handler>();
+
+  public ExpenseRecordChanged(ExpenseRecord record, WriteOperation writeOperation) {
+    super(record, writeOperation);
+  }
+
+  @Override
+  public GwtEvent.Type<Handler> getAssociatedType() {
+    return TYPE;
+  }
+
+  @Override
+  protected void dispatch(Handler handler) {
+    handler.onExpenseRecordChanged(this);
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRequest.java
new file mode 100644
index 0000000..4cce650
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpenseRequest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.request;
+
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.ServerOperation;
+import com.google.gwt.valuestore.shared.PropertyReference;
+import com.google.gwt.valuestore.shared.Record;
+
+/**
+ * "API Generated" request selector interface implemented by objects that give
+ * client access to the methods of
+ * {@link com.google.gwt.sample.expenses.server.domain.Expense}.
+ * <p>
+ * IRL this class will be generated by a JPA-savvy tool run before compilation.
+ */
+public interface ExpenseRequest {
+
+  /**
+   * Defines the server operations that handle these requests.
+   */
+  public enum ServerOperations implements RequestFactory.RequestDefinition {
+    FIND_ALL_EXPENSES {
+      public String getDomainMethodName() {
+        return "findAllExpenses";
+      }
+    },
+
+    FIND_EXPENSE {
+      public String getDomainMethodName() {
+        return "findListOfOneExpense";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[] {java.lang.Long.class};
+      }
+    },
+
+    FIND_EXPENSES_BY_REPORT {
+      public String getDomainMethodName() {
+        return "findExpensesByReport";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[] {java.lang.Long.class};
+      }
+    };
+
+    public String getDomainClassName() {
+      return "com.google.gwt.sample.expenses.server.domain.Expense";
+    }
+
+    public Class<?>[] getParameterTypes() {
+      return null;
+    }
+
+    public Class<? extends Record> getReturnType() {
+      return ExpenseRecord.class;
+    }
+    
+    public boolean isReturnTypeList() {
+      return true;
+    }
+  }
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("FIND_ALL_EXPENSES")
+  RecordListRequest<ExpenseRecord> findAllExpenses();
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("FIND_EXPENSE")
+  RecordListRequest<ExpenseRecord> findExpense(
+      PropertyReference<String> id);
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("FIND_EXPENSES_BY_REPORT")
+  RecordListRequest<ExpenseRecord> findExpensesByReport(
+      PropertyReference<String> reportId);
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesEntityTypesProcessor.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesEntityTypesProcessor.java
index 7184ed4..d42649f 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesEntityTypesProcessor.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesEntityTypesProcessor.java
@@ -17,7 +17,7 @@
 
 import com.google.gwt.valuestore.shared.Record;
 
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 /**
@@ -45,9 +45,9 @@
 
   private static Set<Class<? extends Record>> get() {
     if (set == null) {
-      HashSet<Class<? extends Record>> newInstance = new HashSet<Class<? extends Record>>();
-      newInstance.add(ReportRecord.class);
+      Set<Class<? extends Record>> newInstance = new LinkedHashSet<Class<? extends Record>>();
       newInstance.add(EmployeeRecord.class);
+      newInstance.add(ReportRecord.class);
       set = newInstance;
     }
     return set;
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesRequestFactory.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesRequestFactory.java
index d3de7bc..e87b704 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesRequestFactory.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesRequestFactory.java
@@ -35,6 +35,11 @@
   /**
    * @return a request selector
    */
+  ExpenseRequest expenseRequest();
+
+  /**
+   * @return a request selector
+   */
   ReportRequest reportRequest();
 
   // todo: probably will also need something like this to support custom service
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
index c08a2ed..ab85505 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ExpensesServerSideOperations.java
@@ -47,6 +47,7 @@
     Map<String, RequestDefinition> newMap = new HashMap<String, RequestDefinition>();
     putAll(EmployeeRequest.ServerOperations.values(), newMap);
     putAll(ReportRequest.ServerOperations.values(), newMap);
+    putAll(ExpenseRequest.ServerOperations.values(), newMap);
     map = Collections.unmodifiableMap(newMap);
   }
 
@@ -54,6 +55,7 @@
     Set<Class<? extends Record>> records = new HashSet<Class<? extends Record>>();
     records.add(EmployeeRecord.class);
     records.add(ReportRecord.class);
+    records.add(ExpenseRecord.class);
     return records;
   }
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
index 617e306..39adaf1 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRecord.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
+import com.google.gwt.requestfactory.shared.ServerType;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
 
@@ -29,14 +30,27 @@
 @ServerType(type = com.google.gwt.sample.expenses.server.domain.Report.class)
 public interface ReportRecord extends Record {
 
+  /**
+   * Used as input to {@link com.google.gwt.valuestore.shared.DeltaValueStore#create(Record)
+   * DeltaValueStore#create()} and in the wire format during sync requests.
+   */
+  String TOKEN = "ReportRecord";
+
+  Property<String> approvedSupervisorKey = new Property<String>("approvedSupervisorKey",
+      String.class);
   Property<Date> created = new Property<Date>("created", Date.class);
+  Property<String> notes = new Property<String>("notes", String.class);
   Property<String> purpose = new Property<String>("purpose", String.class);
-  Property<EmployeeRecord> reporter = new Property<EmployeeRecord>("reporter",
-      EmployeeRecord.class);
+  Property<String> reporterKey = new Property<String>("reporterKey",
+      String.class);
+
+  String getApprovedSupervisorKey();
 
   Date getCreated();
+  
+  String getNotes();
 
   String getPurpose();
-
-  EmployeeRecord getReporter();
+  
+  String getReporterKey();
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
index 7988cbd..2621dd9 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/request/ReportRequest.java
@@ -15,11 +15,11 @@
  */
 package com.google.gwt.sample.expenses.gwt.request;
 
-import com.google.gwt.requestfactory.shared.EntityListRequest;
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.requestfactory.shared.RecordRequest;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.ServerOperation;
 import com.google.gwt.valuestore.shared.PropertyReference;
-import com.google.gwt.valuestore.shared.Record;
 
 /**
  * "API Generated" request selector interface implemented by objects that give
@@ -34,6 +34,38 @@
    * Defines the server operations that handle these requests.
    */
   public enum ServerOperations implements RequestFactory.RequestDefinition {
+    COUNT_REPORTS {
+      public String getDomainMethodName() {
+        return "countReports";
+      }
+
+      public Class<?> getReturnType() {
+        return long.class;
+      }
+
+      public boolean isReturnTypeList() {
+        return false;
+      }
+    },
+
+    COUNT_REPORTS_BY_SEARCH {
+      public String getDomainMethodName() {
+        return "countReportsBySearch";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[] {java.lang.Long.class, java.lang.String.class};
+      }
+
+      public Class<?> getReturnType() {
+        return long.class;
+      }
+
+      public boolean isReturnTypeList() {
+        return false;
+      }
+    },
+
     FIND_ALL_REPORTS {
       public String getDomainMethodName() {
         return "findAllReports";
@@ -42,11 +74,15 @@
 
     FIND_REPORT {
       public String getDomainMethodName() {
-        return "findListOfOneReport";
+        return "findReport";
       }
 
       public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.String.class};
+        return new Class[] {java.lang.Long.class};
+      }
+
+      public boolean isReturnTypeList() {
+        return false;
       }
     },
 
@@ -56,7 +92,29 @@
       }
 
       public Class<?>[] getParameterTypes() {
-        return new Class[] {java.lang.String.class};
+        return new Class[] {java.lang.Long.class};
+      }
+    },
+
+    FIND_REPORT_ENTRIES {
+      public String getDomainMethodName() {
+        return "findReportEntries";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[] {int.class, int.class};
+      }
+    },
+
+    FIND_REPORT_ENTRIES_BY_SEARCH {
+      public String getDomainMethodName() {
+        return "findReportEntriesBySearch";
+      }
+
+      public Class<?>[] getParameterTypes() {
+        return new Class[]{
+            java.lang.Long.class, java.lang.String.class,
+            java.lang.String.class, int.class, int.class};
       }
     };
 
@@ -68,27 +126,58 @@
       return null;
     }
 
-    public Class<? extends Record> getReturnType() {
+    public Class<?> getReturnType() {
       return ReportRecord.class;
     }
+
+    public boolean isReturnTypeList() {
+      return true;
+    }
   }
 
   /**
    * @return a request object
    */
+  @ServerOperation("COUNT_REPORTS")
+  RequestFactory.RequestObject<Long> countReports();
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("COUNT_REPORTS_BY_SEARCH")
+  RequestFactory.RequestObject<Long> countReportsBySearch(Long employeeId,
+      String startsWith);
+
+  /**
+   * @return a request object
+   */
   @ServerOperation("FIND_ALL_REPORTS")
-  EntityListRequest<ReportRecord> findAllReports();
+  RecordListRequest<ReportRecord> findAllReports();
 
   /**
    * @return a request object
    */
   @ServerOperation("FIND_REPORT")
-  EntityListRequest<ReportRecord> findReport(PropertyReference<String> id);
+  RecordRequest<ReportRecord> findReport(PropertyReference<String> id);
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("FIND_REPORT_ENTRIES")
+  RecordListRequest<ReportRecord> findReportEntries(int firstResult, int maxResults);
+
+  /**
+   * @return a request object
+   */
+  @ServerOperation("FIND_REPORT_ENTRIES_BY_SEARCH")
+  RecordListRequest<ReportRecord> findReportEntriesBySearch(Long employeeId,
+      String startsWith, String orderBy, int firstResult, int maxResults);
 
   /**
    * @return a request object
    */
   @ServerOperation("FIND_REPORTS_BY_EMPLOYEE")
-  EntityListRequest<ReportRecord> findReportsByEmployee(
+  RecordListRequest<ReportRecord> findReportsByEmployee(
       PropertyReference<String> id);
+
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsRequester.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsRequester.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsViewBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsViewBuilder.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldDetailsViewBuilder.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListRequester.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListRequester.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListViewBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListViewBuilder.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/ScaffoldListViewBuilder.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ExpensesKeyNameRenderer.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ExpensesKeyNameRenderer.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ExpensesKeyNameRenderer.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ListActivitiesMapper.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ListActivitiesMapper.java
index 09e6f4b..bca5684 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ListActivitiesMapper.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ListActivitiesMapper.java
@@ -18,11 +18,12 @@
 import com.google.gwt.app.place.Activity;
 import com.google.gwt.app.place.ActivityMapper;
 import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.ui.employee.EmployeeListActivity;
 import com.google.gwt.sample.expenses.gwt.ui.report.ReportListActivity;
 
@@ -36,19 +37,22 @@
 public class ListActivitiesMapper implements ActivityMapper<ListScaffoldPlace> {
   private final ExpensesRequestFactory requests;
   private final PlaceController<ScaffoldPlace> placeController;
+  private HandlerManager eventBus;
 
-  public ListActivitiesMapper(ExpensesRequestFactory requests,
+  public ListActivitiesMapper(HandlerManager eventBus,
+      ExpensesRequestFactory requests,
       PlaceController<ScaffoldPlace> placeController) {
+    this.eventBus = eventBus;
     this.requests = requests;
     this.placeController = placeController;
   }
 
   public Activity getActivity(ListScaffoldPlace place) {
     if (place.getType().equals(EmployeeRecord.class)) {
-      return new EmployeeListActivity(requests, placeController);
+      return new EmployeeListActivity(eventBus, requests, placeController);
     }
     if (place.getType().equals(ReportRecord.class)) {
-      return new ReportListActivity(requests, placeController);
+      return new ReportListActivity(eventBus, requests, placeController);
     }
 
     throw new RuntimeException("Unable to locate a activity for " + place);
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ListPlaceRenderer.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ListPlaceRenderer.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ListPlaceRenderer.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ScaffoldListPlaceRenderer.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ScaffoldListPlaceRenderer.java
index 0a5043d..2a88ca4 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ScaffoldListPlaceRenderer.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/ScaffoldListPlaceRenderer.java
@@ -16,9 +16,9 @@
 package com.google.gwt.sample.expenses.gwt.ui;
 
 import com.google.gwt.app.util.Renderer;
+import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
 import com.google.gwt.valuestore.shared.Record;
 
 /**
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/AllEmployeesRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/AllEmployeesRequester.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/AllEmployeesRequester.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeActivitiesMapper.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeActivitiesMapper.java
index c67d1d1..91c4552 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeActivitiesMapper.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeActivitiesMapper.java
@@ -18,9 +18,9 @@
 import com.google.gwt.app.place.Activity;
 import com.google.gwt.app.place.ActivityMapper;
 import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.sample.expenses.gwt.client.place.EmployeeScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.EmployeeScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
 
 /**
  * Maps {@link EmployeeScaffoldPlace} instances to the {@link Activity} to run.
@@ -39,7 +39,7 @@
   public Activity getActivity(EmployeeScaffoldPlace place) {
     switch (place.getOperation()) {
       case DETAILS:
-        return new EmployeeDetailsActivity(place.getId(), requests);
+        return new EmployeeDetailsActivity(place.getId(), requests, placeController);
 
       case EDIT:
         return new EmployeeEditActivity(place.getId(), requests,
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java
index 68f1ea8..ed48615 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsActivity.java
@@ -16,56 +16,70 @@
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
 import com.google.gwt.app.place.AbstractActivity;
+import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.client.place.EmployeeScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldRecordPlace.Operation;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.TakesValueList;
 import com.google.gwt.valuestore.shared.Value;
-
-import java.util.List;
+import com.google.gwt.valuestore.ui.RecordDetailsView;
 
 /**
- * An {@link com.google.gwt.app.place.Activity Activity} that requests and displays detailed information on a
- * given employee.
+ * An {@link com.google.gwt.app.place.Activity Activity} that requests and
+ * displays detailed information on a given employee.
  */
-public class EmployeeDetailsActivity extends AbstractActivity {
-  class RequestCallBack implements TakesValueList<EmployeeRecord> {
-    public void setValueList(List<EmployeeRecord> listOfOne) {
-      EmployeeRecord record = listOfOne.get(0);
+public class EmployeeDetailsActivity extends AbstractActivity implements
+    RecordDetailsView.Delegate {
+  private static RecordDetailsView<EmployeeRecord> defaultView;
 
-      StringBuilder list = new StringBuilder("<h3>Employee " + record.getId()
-          + "</h3>");
-
-      String user = record.getUserName();
-      list.append("<div>");
-      list.append("<label>").append("User Name: ").append("</label>");
-      list.append("<span>").append(user).append("</span>");
-      list.append("</div>");
-
-      list.append("<div>");
-      String display = record.getDisplayName();
-      list.append("<label>").append("Display Name: ").append("</label>");
-      list.append("<span>").append(display).append("</span>");
-      list.append("</div>");
-
-      callback.onStarted(new HTML(list.toString()));
+  private static RecordDetailsView<EmployeeRecord> getDefaultView() {
+    if (defaultView == null) {
+      defaultView = new EmployeeDetailsView();
     }
+    return defaultView;
   }
 
   private final ExpensesRequestFactory requests;
-
+  private final PlaceController<ScaffoldPlace> placeController;
+  private final RecordDetailsView<EmployeeRecord> view;
   private String id;
 
-  private Callback callback;
-
-  public EmployeeDetailsActivity(String id, ExpensesRequestFactory requests) {
-    this.requests = requests;
-    this.id = id;
+  /**
+   * Creates an activity that uses the default singleton view instance.
+   */
+  public EmployeeDetailsActivity(String id, ExpensesRequestFactory requests,
+      PlaceController<ScaffoldPlace> placeController) {
+    this(id, requests, placeController, getDefaultView());
   }
 
-  public void start(Callback callback) {
-    this.callback = callback;
-    requests.employeeRequest().findEmployee(Value.of(id)).to(
-        new RequestCallBack()).fire();
+  /**
+   * Creates an activity that uses its own view instance.
+   */
+  public EmployeeDetailsActivity(String id, ExpensesRequestFactory requests,
+      PlaceController<ScaffoldPlace> placeController,
+      RecordDetailsView<EmployeeRecord> view) {
+    this.placeController = placeController;
+    this.id = id;
+    this.requests = requests;
+    view.setDelegate(this);
+    this.view = view;
+  }
+
+  public void editClicked() {
+    placeController.goTo(new EmployeeScaffoldPlace(view.getValue(),
+        Operation.EDIT));
+  }
+
+  public void start(final Display display) {
+    Receiver<EmployeeRecord> callback = new Receiver<EmployeeRecord>() {
+      public void onSuccess(EmployeeRecord record) {
+        view.setValue(record);
+        display.showActivityWidget(view);
+      }
+    };
+
+    requests.employeeRequest().findEmployee(Value.of(id)).to(callback).fire();
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsBuilder.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsBuilder.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
new file mode 100644
index 0000000..2f334ba
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.ui.employee;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.valuestore.ui.RecordDetailsView;
+
+/**
+ * Details view for employee records.
+ */
+public class EmployeeDetailsView extends Composite implements
+    RecordDetailsView<EmployeeRecord> {
+  interface Binder extends UiBinder<HTMLPanel, EmployeeDetailsView> {
+  }
+
+  private static final Binder BINDER = GWT.create(Binder.class);
+
+  EmployeeRecord record;
+
+  @UiField
+  SpanElement idSpan;
+  @UiField
+  SpanElement versionSpan;
+  @UiField
+  SpanElement displayName;
+  @UiField
+  SpanElement userName;
+  @UiField
+  SpanElement password;
+  @UiField
+  SpanElement supervisorKey;
+  @UiField
+  Button edit;
+
+  private Delegate delegate;
+
+  public EmployeeDetailsView() {
+    initWidget(BINDER.createAndBindUi(this));
+  }
+
+  public Widget asWidget() {
+    return this;
+  }
+
+  public EmployeeRecord getValue() {
+    return record;
+  }
+
+  @UiHandler("edit")
+  public void onEditClicked(@SuppressWarnings("unused") ClickEvent e) {
+    delegate.editClicked();
+  }
+
+  public void setDelegate(Delegate delegate) {
+    this.delegate = delegate;
+  }
+
+  public void setValue(EmployeeRecord record) {
+    this.record = record;
+    displayName.setInnerText(record.getDisplayName());
+    userName.setInnerText(record.getUserName());
+    password.setInnerText(record.getPassword());
+    supervisorKey.setInnerText(record.getSupervisorKey());
+    idSpan.setInnerText(record.getId());
+    versionSpan.setInnerText(record.getVersion().toString());
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml
new file mode 100644
index 0000000..7781e71
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeDetailsView.ui.xml
@@ -0,0 +1,15 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <g:HTMLPanel>
+    <h3><ui:msg>Employee <span ui:field='idSpan' /> (v<span ui:field='versionSpan'/>)</ui:msg></h3>
+
+  <div><span>Display Name:</span> <span ui:field='displayName'></span></div>
+  <div><span>User Name:</span> <span ui:field='userName'></span></div>
+  <div><span>Supervisor Key:</span> <span ui:field='supervisorKey'></span></div>
+  <div><span>Password:</span> <span ui:field='password'></span></div>
+
+  <g:Button ui:field='edit'>Edit</g:Button>
+
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
index 64eda5d..a77b0bf 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java
@@ -15,35 +15,23 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
-import com.google.gwt.app.place.AbstractActivity;
 import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.client.place.EmployeeScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldRecordPlace.Operation;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ListScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.TakesValueList;
-import com.google.gwt.valuestore.shared.DeltaValueStore;
 import com.google.gwt.valuestore.shared.Value;
+import com.google.gwt.valuestore.ui.AbstractRecordEditActivity;
 import com.google.gwt.valuestore.ui.RecordEditView;
 
-import java.util.List;
-
 /**
- * An activity that requests all info on an employee, allows the user to  edit it,
- * and persists the results. 
+ * An activity that requests all info on an employee, allows the user to edit
+ * it, and persists the results.
  */
-public class EmployeeEditActivity extends AbstractActivity implements
-    RecordEditView.Delegate {
-  class RequestCallBack implements TakesValueList<EmployeeRecord> {
-    public void setValueList(List<EmployeeRecord> listOfOne) {
-      view.setEnabled(true);
-      EmployeeRecord record = listOfOne.get(0);
-      view.setValue(record);
-      callback.onStarted(view.asWidget());
-    }
-  }
-
+public class EmployeeEditActivity extends
+    AbstractRecordEditActivity<EmployeeRecord> {
   private static RecordEditView<EmployeeRecord> defaultView;
 
   private static RecordEditView<EmployeeRecord> getDefaultView() {
@@ -54,13 +42,8 @@
   }
 
   private final ExpensesRequestFactory requests;
-  private final RecordEditView<EmployeeRecord> view;
-  private final String id;
   private final PlaceController<ScaffoldPlace> placeController;
 
-  private DeltaValueStore deltas;
-  private Callback callback;
-
   /**
    * Creates an activity that uses the default singleton view instance.
    */
@@ -75,34 +58,19 @@
   public EmployeeEditActivity(String id, RecordEditView<EmployeeRecord> view,
       ExpensesRequestFactory requests,
       PlaceController<ScaffoldPlace> placeController) {
+    super(view, id, requests);
     this.requests = requests;
-    this.id = id;
-    this.view = view;
-    this.deltas = requests.getValueStore().spawnDeltaView();
     this.placeController = placeController;
-    view.setDelegate(this);
-    view.setDeltaValueStore(deltas);
-  }
-
-  public void saveClicked() {
-    if (deltas.isChanged()) {
-      view.setEnabled(false);
-      DeltaValueStore toCommit = deltas;
-      deltas = null;
-      requests.syncRequest(toCommit).fire(); // TODO Need callback, idiot
-      placeController.goTo(new ListScaffoldPlace(EmployeeRecord.class));
-    }
-  }
-
-  public void start(Callback callback) {
-    this.callback = callback;
-    requests.employeeRequest().findEmployee(Value.of(id)).to(
-        new RequestCallBack()).fire();
   }
 
   @Override
-  public boolean willStop() {
-    return deltas == null || !deltas.isChanged()
-        || Window.confirm("Dude! Really drop your edits?");
+  protected void fireFindRequest(Value<String> id,
+      Receiver<EmployeeRecord> callback) {
+    requests.employeeRequest().findEmployee(id).to(callback).fire();
+  }
+
+  @Override
+  protected void exit() {
+    placeController.goTo(new EmployeeScaffoldPlace(getId(), Operation.DETAILS));
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java
index f08842d..e87c0dd 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java
@@ -15,10 +15,10 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
+import com.google.gwt.app.client.EditorSupport;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
@@ -26,12 +26,13 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwt.user.client.ui.TextBox;
 import com.google.gwt.valuestore.shared.DeltaValueStore;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.ui.RecordEditView;
 
-import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -42,35 +43,59 @@
   interface Binder extends UiBinder<HTMLPanel, EmployeeEditView> {
   }
 
+  interface DataBinder extends EditorSupport<EmployeeRecord, EmployeeEditView> {
+  }
+
   private static final Binder BINDER = GWT.create(Binder.class);
+  private static final DataBinder DATA_BINDER = GWT.create(DataBinder.class);
 
   @UiField
   TextBox displayName;
   @UiField
+  TextBox password;
+  @UiField
+  TextBox supervisorKey;
+  @UiField
   TextBox userName;
   @UiField
+  Button cancel;
+  @UiField
   Button save;
   @UiField
-  SpanElement idSpan;
+  InlineLabel id;
+  @UiField
+  InlineLabel version;
+  @UiField
+  DivElement errors;
 
   private Delegate delegate;
   private DeltaValueStore deltas;
-  
+
   private EmployeeRecord record;
 
   public EmployeeEditView() {
     initWidget(BINDER.createAndBindUi(this));
+    DATA_BINDER.init(this);
   }
 
   public EmployeeEditView asWidget() {
     return this;
   }
 
+  public DeltaValueStore getDeltaValueStore() {
+    return deltas;
+  }
+
   public Set<Property<?>> getProperties() {
-    Set<Property<?>> rtn = new HashSet<Property<?>>();
-    rtn.add(EmployeeRecord.userName);
-    rtn.add(EmployeeRecord.displayName);
-    return rtn;
+    return DATA_BINDER.getProperties();
+  }
+
+  public EmployeeRecord getValue() {
+    return record;
+  }
+
+  public boolean isChanged() {
+    return DATA_BINDER.isChanged(this);
   }
 
   public void setDelegate(Delegate delegate) {
@@ -82,30 +107,26 @@
   }
 
   public void setEnabled(boolean enabled) {
-    displayName.setEnabled(enabled);
-    userName.setEnabled(enabled);
+    DATA_BINDER.setEnabled(this, enabled);
     save.setEnabled(enabled);
   }
 
   public void setValue(EmployeeRecord value) {
     this.record = value;
-    displayName.setValue(record.getDisplayName());
-    userName.setValue(record.getUserName());
-    idSpan.setInnerText(record.getId());
+    DATA_BINDER.setValue(this, value);
   }
 
-  @UiHandler("displayName")
-  void onDisplayNameChange(ValueChangeEvent<String> event) {
-    deltas.set(EmployeeRecord.displayName, record, event.getValue());
+  public void showErrors(Map<String, String> errorMap) {
+    DATA_BINDER.showErrors(this, errorMap);
+  }
+
+  @UiHandler("cancel")
+  void onCancel(@SuppressWarnings("unused") ClickEvent event) {
+    delegate.cancelClicked();
   }
 
   @UiHandler("save")
   void onSave(@SuppressWarnings("unused") ClickEvent event) {
     delegate.saveClicked();
   }
-
-  @UiHandler("userName")
-  void onUserNameChange(ValueChangeEvent<String> event) {
-    deltas.set(EmployeeRecord.userName, record, event.getValue());
-  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml
index 9847a45..12ce484 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.ui.xml
@@ -1,12 +1,16 @@
 <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
-  xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:b='urn:import:com.google.gwt.bikeshed.list.client'>
+  xmlns:g='urn:import:com.google.gwt.user.client.ui' >
   <g:HTMLPanel>
-    <h3><ui:msg>Edit Employee <span ui:field='idSpan' /></ui:msg></h3>
+    <h3><ui:msg>Edit Employee <g:InlineLabel ui:field='id'></g:InlineLabel> (v<g:InlineLabel ui:field='version'/>)</ui:msg></h3>
+    <div ui:field='errors' style='background-color: red; '></div>
 
-    <span>Display Name:</span> <g:TextBox ui:field='displayName'></g:TextBox>
-    <span>User Name:</span> <g:TextBox ui:field='userName'></g:TextBox>
+    <div><span>Display Name:</span> <g:TextBox ui:field='displayName'></g:TextBox></div>
+    <div><span>User Name:</span> <g:TextBox ui:field='userName'></g:TextBox></div>
+    <div><span>Supervisor Key:</span> <g:TextBox ui:field='supervisorKey'></g:TextBox></div>
+    <div><span>Password:</span> <g:PasswordTextBox ui:field='password'></g:PasswordTextBox></div>
 
     <g:Button ui:field='save'>Save</g:Button>
+    <g:Button ui:field='cancel'>Cancel</g:Button>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListActivity.java
index 1277e26..64c283c 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListActivity.java
@@ -16,14 +16,19 @@
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
 import com.google.gwt.app.place.PlaceController;
-import com.google.gwt.requestfactory.shared.EntityListRequest;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.sample.expenses.gwt.client.place.EmployeeScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldRecordPlace.Operation;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
+import com.google.gwt.sample.expenses.gwt.request.EmployeeRecordChanged;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.EmployeeScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldRecordPlace.Operation;
 import com.google.gwt.valuestore.ui.AbstractRecordListActivity;
 import com.google.gwt.valuestore.ui.RecordListView;
+import com.google.gwt.view.client.Range;
 
 /**
  * Activity that requests and displays all {@EmployeeRecord}
@@ -43,35 +48,57 @@
 
   private final ExpensesRequestFactory requests;
   private final PlaceController<ScaffoldPlace> placeController;
+  private final HandlerManager eventBus;
+  private HandlerRegistration registration;
 
   /**
    * Creates an activity that uses the default singleton view instance.
    */
-  public EmployeeListActivity(ExpensesRequestFactory requests,
+  public EmployeeListActivity(HandlerManager eventBus,
+      ExpensesRequestFactory requests,
       PlaceController<ScaffoldPlace> placeController) {
-    this(requests, getDefaultView(), placeController);
+    this(eventBus, requests, getDefaultView(), placeController);
   }
 
   /**
    * Creates an activity that uses its own view instance.
    */
-  public EmployeeListActivity(ExpensesRequestFactory requests,
-      RecordListView<EmployeeRecord> view,
+  public EmployeeListActivity(HandlerManager eventBus,
+      ExpensesRequestFactory requests, RecordListView<EmployeeRecord> view,
       PlaceController<ScaffoldPlace> placeController) {
     super(view);
+    this.eventBus = eventBus;
     this.requests = requests;
     this.placeController = placeController;
   }
 
-  public void edit(EmployeeRecord record) {
-    placeController.goTo(new EmployeeScaffoldPlace(record, Operation.EDIT));
+  @Override
+  public void onStop() {
+    registration.removeHandler();
   }
 
   public void showDetails(EmployeeRecord record) {
     placeController.goTo(new EmployeeScaffoldPlace(record, Operation.DETAILS));
   }
 
-  protected EntityListRequest<EmployeeRecord> createRequest() {
-    return requests.employeeRequest().findAllEmployees();
+  @Override
+  public void start(Display display) {
+    this.registration = eventBus.addHandler(EmployeeRecordChanged.TYPE,
+        new EmployeeRecordChanged.Handler() {
+          public void onEmployeeChanged(EmployeeRecordChanged event) {
+            update(event.getRecord());
+          }
+        });
+    super.start(display);
+  }
+
+  protected RecordListRequest<EmployeeRecord> createRangeRequest(Range range) {
+    return requests.employeeRequest().findEmployeeEntries(range.getStart(),
+        range.getLength());
+  }
+
+  @Override
+  protected void fireCountRequest(Receiver<Long> callback) {
+    requests.employeeRequest().countEmployees().to(callback).fire();
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
index 0d2ce87..780af96 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.java
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.employee;
 
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
 import com.google.gwt.uibinder.client.UiBinder;
@@ -36,7 +36,7 @@
 
   private static final Binder BINDER = GWT.create(Binder.class);
 
-  @UiField PagingTableListView<EmployeeRecord> table;
+  @UiField CellTable<EmployeeRecord> table;
 
   public EmployeeListView() {
     init(BINDER.createAndBindUi(this), table, getColumns());
@@ -49,6 +49,8 @@
 
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.userName));
     columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.displayName));
+    columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.password));
+    columns.add(PropertyColumn.<EmployeeRecord> getStringPropertyColumn(EmployeeRecord.supervisorKey));
 
     return columns;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.ui.xml
index a9b3748..72afb17 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeListView.ui.xml
@@ -4,6 +4,6 @@
   xmlns:b='urn:import:com.google.gwt.bikeshed.list.client'>
   <g:HTMLPanel>
     <h3><ui:msg>Employees</ui:msg></h3>
-    <b:PagingTableListView ui:field='table'/>
+    <b:CellTable ui:field='table'/>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/AllReportsRequester.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/AllReportsRequester.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/AllReportsRequester.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportActivitiesMapper.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportActivitiesMapper.java
index da3226a..408535e 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportActivitiesMapper.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportActivitiesMapper.java
@@ -18,9 +18,9 @@
 import com.google.gwt.app.place.Activity;
 import com.google.gwt.app.place.ActivityMapper;
 import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.sample.expenses.gwt.client.place.ReportScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ReportScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
 
 /**
  * Maps {@link ReportScaffoldPlace} instances to the {@link Activity} to run.
@@ -38,7 +38,7 @@
   public Activity getActivity(ReportScaffoldPlace place) {
     switch (place.getOperation()) {
       case DETAILS:
-        return new ReportDetailsActivity(place.getId(), requests);
+        return new ReportDetailsActivity(place.getId(), requests, placeController);
       case EDIT:
         return new ReportEditActivity(place.getId(), requests, placeController);
     }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java
index 5a8cd42..8eed053 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsActivity.java
@@ -16,55 +16,69 @@
 package com.google.gwt.sample.expenses.gwt.ui.report;
 
 import com.google.gwt.app.place.AbstractActivity;
+import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.client.place.ReportScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldRecordPlace.Operation;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.TakesValueList;
 import com.google.gwt.valuestore.shared.Value;
-
-import java.util.List;
+import com.google.gwt.valuestore.ui.RecordDetailsView;
 
 /**
  * An {@link com.google.gwt.app.place.Activity Activity} that requests and
  * displays detailed information on a given report.
  */
-public class ReportDetailsActivity extends AbstractActivity {
-  class RequestCallBack implements TakesValueList<ReportRecord> {
-    public void setValueList(List<ReportRecord> listOfOne) {
-      ReportRecord record = listOfOne.get(0);
+public class ReportDetailsActivity extends AbstractActivity implements
+    RecordDetailsView.Delegate {
+  private static RecordDetailsView<ReportRecord> defaultView;
 
-      StringBuilder list = new StringBuilder("<h3>Employee " + record.getId()
-          + "</h3>");
-
-      String purpose = record.getPurpose();
-      list.append("<div>");
-      list.append("<label>").append("Purpose: ").append("</label>");
-      list.append("<span>").append(purpose).append("</span>");
-      list.append("</div>");
-
-      list.append("<div>");
-      String created = record.getCreated().toString();
-      list.append("<label>").append("Created: ").append("</label>");
-      list.append("<span>").append(created).append("</span>");
-      list.append("</div>");
-
-      callback.onStarted(new HTML(list.toString()));
+  private static RecordDetailsView<ReportRecord> getDefaultView() {
+    if (defaultView == null) {
+      defaultView = new ReportDetailsView();
     }
+    return defaultView;
   }
 
   private final ExpensesRequestFactory requests;
-
+  private final PlaceController<ScaffoldPlace> placeController;
+  private final RecordDetailsView<ReportRecord> view;
   private String id;
 
-  private Callback callback;
-
-  public ReportDetailsActivity(String id, ExpensesRequestFactory requests) {
-    this.requests = requests;
-    this.id = id;
+  /**
+   * Creates an activity that uses the default singleton view instance.
+   */
+  public ReportDetailsActivity(String id, ExpensesRequestFactory requests,
+      PlaceController<ScaffoldPlace> placeController) {
+    this(id, requests, placeController, getDefaultView());
   }
 
-  public void start(Callback callback) {
-    this.callback = callback;
-    requests.reportRequest().findReport(Value.of(id)).to(new RequestCallBack()).fire();
+  /**
+   * Creates an activity that uses its own view instance.
+   */
+  public ReportDetailsActivity(String id, ExpensesRequestFactory requests,
+      PlaceController<ScaffoldPlace> placeController,
+      RecordDetailsView<ReportRecord> view) {
+    this.placeController = placeController;
+    this.id = id;
+    this.requests = requests;
+    view.setDelegate(this);
+    this.view = view;
+  }
+
+  public void editClicked() {
+    placeController.goTo(new ReportScaffoldPlace(view.getValue(),
+        Operation.EDIT));
+  }
+
+  public void start(final Display display) {
+    Receiver<ReportRecord> callback = new Receiver<ReportRecord>() {
+      public void onSuccess(ReportRecord record) {
+        view.setValue(record);
+        display.showActivityWidget(view);
+      }
+    };
+    requests.reportRequest().findReport(Value.of(id)).to(callback).fire();
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsBuilder.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsBuilder.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsBuilder.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
new file mode 100644
index 0000000..c00fe3f
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.ui.report;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.valuestore.ui.RecordDetailsView;
+
+/**
+ * Details view for employee records.
+ */
+public class ReportDetailsView extends Composite implements RecordDetailsView<ReportRecord> {
+  interface Binder extends UiBinder<HTMLPanel, ReportDetailsView> {
+  }
+
+  private static final Binder BINDER = GWT.create(Binder.class);
+
+  ReportRecord record;
+
+  @UiField
+  SpanElement idSpan;
+  @UiField
+  SpanElement versionSpan;
+  @UiField
+  SpanElement notes;
+  @UiField
+  SpanElement purpose;
+  @UiField
+  SpanElement created;
+  @UiField
+  SpanElement reporterKey;
+  @UiField
+  SpanElement approvedSupervisorKey;
+  @UiField
+  Button edit;
+
+  private Delegate delegate;
+
+  public ReportDetailsView() {
+    initWidget(BINDER.createAndBindUi(this));
+  }
+
+  public Widget asWidget() {
+    return this;
+  }
+
+  public ReportRecord getValue() {
+    return record;
+  }
+
+  @UiHandler("edit")
+  public void onEditClicked(@SuppressWarnings("unused") ClickEvent e) {
+    delegate.editClicked();
+  }
+
+  public void setDelegate(Delegate delegate) {
+    this.delegate = delegate;
+  }
+
+  public void setValue(ReportRecord record) {
+    this.record = record;
+    purpose.setInnerText(record.getPurpose());
+    notes.setInnerText(record.getNotes());
+    created.setInnerText(DateTimeFormat.getShortDateFormat().format(record.getCreated()));
+    idSpan.setInnerText(record.getId());
+    versionSpan.setInnerText(record.getVersion().toString());
+    reporterKey.setInnerText(record.getReporterKey());
+    approvedSupervisorKey.setInnerText(record.getApprovedSupervisorKey());
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.ui.xml
new file mode 100644
index 0000000..e495c9b
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportDetailsView.ui.xml
@@ -0,0 +1,16 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+  <g:HTMLPanel>
+    <h3><ui:msg>Report <span ui:field='idSpan' /> (v<span ui:field='versionSpan'/>)</ui:msg></h3>
+
+  <div><span>Purpose:</span> <span ui:field='purpose'></span></div>
+  <div><span>Notes:</span> <span ui:field='notes'></span></div>
+  <div><span>Created:</span> <span ui:field='created'></span></div>
+  <div><span>Reporter Key:</span> <span ui:field='reporterKey'></span></div>
+  <div><span>Approved Supervisor Key:</span> <span ui:field='approvedSupervisorKey'></span></div>
+
+  <g:Button ui:field='edit'>Edit</g:Button>
+
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
index 10a9238..60dbea3 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java
@@ -15,25 +15,61 @@
  */
 package com.google.gwt.sample.expenses.gwt.ui.report;
 
-import com.google.gwt.app.place.AbstractActivity;
 import com.google.gwt.app.place.PlaceController;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.expenses.gwt.client.place.ReportScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldRecordPlace.Operation;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
-import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.valuestore.shared.Value;
+import com.google.gwt.valuestore.ui.AbstractRecordEditActivity;
+import com.google.gwt.valuestore.ui.RecordEditView;
 
 /**
  * An activity that requests all info on a report, allows the user to edit it,
  * and persists the results.
  */
-public class ReportEditActivity extends AbstractActivity {
+public class ReportEditActivity extends
+    AbstractRecordEditActivity<ReportRecord> {
+  private static RecordEditView<ReportRecord> defaultView;
 
+  private static RecordEditView<ReportRecord> getDefaultView() {
+    if (defaultView == null) {
+      defaultView = new ReportEditView();
+    }
+    return defaultView;
+  }
+
+  private final ExpensesRequestFactory requests;
+  private final PlaceController<ScaffoldPlace> placeController;
+
+  /**
+   * Creates an activity that uses the default singleton view instance.
+   */
   public ReportEditActivity(String id, ExpensesRequestFactory requests,
       PlaceController<ScaffoldPlace> placeController) {
-    // TODO Auto-generated constructor stub
+    this(id, getDefaultView(), requests, placeController);
   }
 
-  public void start(Callback callback) {
-    callback.onStarted(new Label("tbd"));
+  /**
+   * Creates an activity that uses its own view instance.
+   */
+  public ReportEditActivity(String id, RecordEditView<ReportRecord> view,
+      ExpensesRequestFactory requests,
+      PlaceController<ScaffoldPlace> placeController) {
+    super(view, id, requests);
+    this.requests = requests;
+    this.placeController = placeController;
   }
 
+  @Override
+  protected void fireFindRequest(Value<String> id,
+      Receiver<ReportRecord> callback) {
+    requests.reportRequest().findReport(id).to(callback).fire();
+  }
+
+  protected void exit() {
+    placeController.goTo(new ReportScaffoldPlace(getId(), Operation.DETAILS));
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.java
new file mode 100644
index 0000000..7380d56
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.gwt.ui.report;
+
+import com.google.gwt.app.client.EditorSupport;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.valuestore.shared.DeltaValueStore;
+import com.google.gwt.valuestore.shared.Property;
+import com.google.gwt.valuestore.ui.RecordEditView;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Edit view for employee records.
+ */
+public class ReportEditView extends Composite implements
+    RecordEditView<ReportRecord> {
+  interface Binder extends UiBinder<HTMLPanel, ReportEditView> {
+  }
+
+  interface DataBinder extends EditorSupport<ReportRecord, ReportEditView> {
+  }
+
+  private static final Binder BINDER = GWT.create(Binder.class);
+  private static final DataBinder DATA_BINDER = GWT.create(DataBinder.class);
+  @UiField
+  TextBox notes;
+  @UiField
+  TextBox purpose;
+  @UiField
+  TextBox reporterKey;
+  @UiField
+  TextBox approvedSupervisorKey;
+  @UiField
+  InlineLabel created; // TODO: use a DatePicker
+  @UiField
+  Button cancel;
+  @UiField
+  Button save;
+  @UiField
+  InlineLabel id;
+  @UiField
+  InlineLabel version;
+  @UiField
+  DivElement errors;
+
+  private Delegate delegate;
+  private DeltaValueStore deltas;
+
+  private ReportRecord record;
+
+  public ReportEditView() {
+    initWidget(BINDER.createAndBindUi(this));
+    DATA_BINDER.init(this);
+  }
+
+  public ReportEditView asWidget() {
+    return this;
+  }
+
+  public DeltaValueStore getDeltaValueStore() {
+    return deltas;
+  }
+
+  public Set<Property<?>> getProperties() {
+    return DATA_BINDER.getProperties();
+  }
+
+  public ReportRecord getValue() {
+    return record;
+  }
+
+  public boolean isChanged() {
+    return DATA_BINDER.isChanged(this);
+  }
+
+  public void setDelegate(Delegate delegate) {
+    this.delegate = delegate;
+  }
+
+  public void setDeltaValueStore(DeltaValueStore deltas) {
+    this.deltas = deltas;
+  }
+
+  public void setEnabled(boolean enabled) {
+    DATA_BINDER.setEnabled(this, enabled);
+    save.setEnabled(enabled);
+  }
+
+  public void setValue(ReportRecord value) {
+    this.record = value;
+    DATA_BINDER.setValue(this, value);
+  }
+
+  public void showErrors(Map<String, String> errorMap) {
+    DATA_BINDER.showErrors(this, errorMap);
+  }
+
+  @UiHandler("cancel")
+  void onCancel(@SuppressWarnings("unused") ClickEvent event) {
+    delegate.cancelClicked();
+  }
+
+  @UiHandler("save")
+  void onSave(@SuppressWarnings("unused") ClickEvent event) {
+    delegate.saveClicked();
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.ui.xml
new file mode 100644
index 0000000..6ae32d8
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditView.ui.xml
@@ -0,0 +1,17 @@
+<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui' >
+  <g:HTMLPanel>
+    <h3><ui:msg>Edit Report <g:InlineLabel ui:field='id' /> (v<g:InlineLabel ui:field='version'/>)</ui:msg></h3>
+    <div ui:field='errors' style='background-color: red; '></div>
+
+    <div><span>Purpose:</span> <g:TextBox ui:field='purpose'></g:TextBox></div>
+    <div><span>Notes:</span> <g:TextBox ui:field='notes'></g:TextBox></div>
+    <div><span>Created:</span> <g:InlineLabel ui:field='created'></g:InlineLabel></div>
+    <div><span>Reporter Key:</span> <g:TextBox ui:field='reporterKey'></g:TextBox></div>
+    <div><span>Approved Supervisor Key:</span> <g:TextBox ui:field='approvedSupervisorKey'></g:TextBox></div>
+
+    <g:Button ui:field='save'>Save</g:Button>
+    <g:Button ui:field='cancel'>Cancel</g:Button>
+  </g:HTMLPanel>
+</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListActivity.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListActivity.java
index 4e2014e..7c7cd52 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListActivity.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListActivity.java
@@ -16,14 +16,19 @@
 package com.google.gwt.sample.expenses.gwt.ui.report;
 
 import com.google.gwt.app.place.PlaceController;
-import com.google.gwt.requestfactory.shared.EntityListRequest;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RecordListRequest;
+import com.google.gwt.sample.expenses.gwt.client.place.ReportScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
+import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldRecordPlace.Operation;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ReportScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldPlace;
-import com.google.gwt.sample.expenses.gwt.scaffold.place.ScaffoldRecordPlace.Operation;
+import com.google.gwt.sample.expenses.gwt.request.ReportRecordChanged;
 import com.google.gwt.valuestore.ui.AbstractRecordListActivity;
 import com.google.gwt.valuestore.ui.RecordListView;
+import com.google.gwt.view.client.Range;
 
 /**
  * Activity that requests and displays all {@ReportRecord}
@@ -43,35 +48,57 @@
 
   private final ExpensesRequestFactory requests;
   private final PlaceController<ScaffoldPlace> placeController;
+  private final HandlerManager eventBus;
+  private HandlerRegistration registration;
 
   /**
    * Creates an activity that uses the default singleton view instance.
    */
-  public ReportListActivity(ExpensesRequestFactory requests,
+  public ReportListActivity(HandlerManager eventBus,
+      ExpensesRequestFactory requests,
       PlaceController<ScaffoldPlace> placeController) {
-    this(requests, getDefaultView(), placeController);
+    this(eventBus, requests, getDefaultView(), placeController);
   }
 
   /**
    * Creates an activity that uses its own view instance.
    */
-  public ReportListActivity(ExpensesRequestFactory requests,
-      RecordListView<ReportRecord> view,
+  public ReportListActivity(HandlerManager eventBus,
+      ExpensesRequestFactory requests, RecordListView<ReportRecord> view,
       PlaceController<ScaffoldPlace> placeController) {
     super(view);
+    this.eventBus = eventBus;
     this.requests = requests;
     this.placeController = placeController;
   }
 
-  public void edit(ReportRecord record) {
-    placeController.goTo(new ReportScaffoldPlace(record, Operation.EDIT));
+  @Override
+  public void onStop() {
+    registration.removeHandler();
   }
 
   public void showDetails(ReportRecord record) {
     placeController.goTo(new ReportScaffoldPlace(record, Operation.DETAILS));
   }
 
-  protected EntityListRequest<ReportRecord> createRequest() {
-    return requests.reportRequest().findAllReports();
+  
+  @Override
+  public void start(Display display) {
+    this.registration = eventBus.addHandler(ReportRecordChanged.TYPE, new ReportRecordChanged.Handler() {
+      public void onReportChanged(ReportRecordChanged event) {
+        update(event.getRecord());
+      }
+    });
+    super.start(display);
+  }
+
+  protected RecordListRequest<ReportRecord> createRangeRequest(Range range) {
+    return requests.reportRequest().findReportEntries(range.getStart(),
+        range.getLength());
+  }
+
+  @Override
+  protected void fireCountRequest(Receiver<Long> callback) {
+    requests.reportRequest().countReports().to(callback).fire();
   }
 }
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
index 13d9690..68e6c99 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.java
@@ -16,7 +16,7 @@
 package com.google.gwt.sample.expenses.gwt.ui.report;
 
 import com.google.gwt.app.util.DateTimeFormatRenderer;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.i18n.client.DateTimeFormat;
 import com.google.gwt.sample.expenses.gwt.request.ReportRecord;
@@ -41,13 +41,12 @@
 
   private static final Binder BINDER = GWT.create(Binder.class);
 
-  @UiField PagingTableListView<ReportRecord> table;
+  @UiField CellTable<ReportRecord> table;
 
   public ReportListView() {
     init(BINDER.createAndBindUi(this), table, getColumns());
   }
 
-
   protected List<PropertyColumn<ReportRecord, ?>> getColumns() {
     // TODO These should be <g:col> elements in a <g:table> in the ui.xml file
 
@@ -56,6 +55,8 @@
     columns.add(new PropertyColumn<ReportRecord, Date>(ReportRecord.created,
         new DateTimeFormatRenderer(DateTimeFormat.getShortDateFormat())));
     columns.add(PropertyColumn.<ReportRecord> getStringPropertyColumn(ReportRecord.purpose));
+    columns.add(PropertyColumn.<ReportRecord> getStringPropertyColumn(ReportRecord.reporterKey));
+    columns.add(PropertyColumn.<ReportRecord> getStringPropertyColumn(ReportRecord.approvedSupervisorKey));
 
     return columns;
   }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.ui.xml b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.ui.xml
index 82ec73d..802a2ec 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.ui.xml
+++ b/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportListView.ui.xml
@@ -4,6 +4,6 @@
   xmlns:b='urn:import:com.google.gwt.bikeshed.list.client'>
   <g:HTMLPanel>
     <h3><ui:msg>Reports</ui:msg></h3>
-    <b:PagingTableListView ui:field='table'/>
+    <b:CellTable ui:field='table'/>
   </g:HTMLPanel>
 </ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java b/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
index 1d9cdeb..4c50d4d 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/ExpensesDataServlet.java
@@ -17,9 +17,11 @@
 
 import com.google.gwt.requestfactory.server.RequestFactoryServlet;
 import com.google.gwt.sample.expenses.server.domain.Employee;
+import com.google.gwt.sample.expenses.server.domain.Expense;
 import com.google.gwt.sample.expenses.server.domain.Report;
 
 import java.util.Date;
+import java.util.Random;
 
 /**
  * Dwindling interim servlet that calls our mock storage backend directly
@@ -28,56 +30,124 @@
 @SuppressWarnings("serial")
 public class ExpensesDataServlet extends RequestFactoryServlet {
 
+  // Must be in sync with DESCRIPTIONS
+  private static final String[] CATEGORIES = {
+      "Dining", "Dining", "Dining", "Lodging", "Lodging",
+      "Local Transportation", "Local Transportation", "Local Transportation",
+      "Air Travel", "Air Travel", "Office Supplies", "Office Supplies",
+      "Office Supplies", "Office Supplies",};
+
+  private static final String[] DEPARTMENTS = {
+      "Sales", "Marketing", "Engineering", "Operations"};
+
+  // Must be in sync with CATEGORIES
+  private static final String[] DESCRIPTIONS = {
+      "Breakfast", "Lunch", "Dinner", "Hotel", "Bed & Breakfast", "Train fare",
+      "Taxi fare", "Bus ticket", "Flight from ATL to SFO",
+      "Flight from SFO to ATL", "Paperclips", "Stapler", "Scissors", "Paste",};
+
+  private static final String[] FIRST_NAMES = {
+      "Amy", "Bob", "Catherine", "Dave", "Earl", "Flin", "George", "Harriot",
+      "Ingrid", "John", "Katy", "Leo", "Mike", "Nancy", "Owen", "Paul",
+      "Reece", "Sally", "Terry", "Val", "Wes", "Xavier", "Zack"};
+
+  private static final String[] LAST_NAMES = {
+      "Awesome", "Bravo", "Cool", "Fantastic", "Great", "Happy",
+      "Ignoranomous", "Krazy", "Luminous", "Magnanimous", "Outstanding",
+      "Perfect", "Radical", "Stellar", "Terrific", "Wonderful"};
+
+  private static final String[] NOTES = {
+      // Some entries do not have notes.
+      "", "Need approval by Monday", "Show me the money",
+      "Please bill to the Widgets project", "High priority", "Review A.S.A.P."};
+
+  private static final String[] PURPOSES = {
+      "Spending lots of money", "Team building diamond cutting offsite",
+      "Visit to Istanbul", "ISDN modem for telecommuting", "Sushi offsite",
+      "Baseball card research", "Potato chip cooking offsite",
+      "Money laundering", "Donut day"};
+
+  Random rand = new Random();
+
   @Override
   protected void initDb() {
     long size = Employee.countEmployees();
     if (size > 1) {
       return;
     }
-    // initialize
+
+    // Initialize the database.
+    for (int i = 0; i < 100; i++) {
+      addEmployee();
+    }
+  }
+
+  /**
+   * Add a randomly generated employee.
+   */
+  private void addEmployee() {
     Employee abc = new Employee();
-    abc.setUserName("abc");
-    abc.setDisplayName("Able B. Charlie");
+    String firstName = nextValue(FIRST_NAMES);
+    String lastName = nextValue(LAST_NAMES);
+    String username = (firstName.charAt(0) + lastName).toLowerCase();
+    abc.setUserName(username);
+    abc.setDisplayName(firstName + " " + lastName);
+    abc.setDepartment(nextValue(DEPARTMENTS));
     abc.persist();
 
-    Employee def = new Employee();
-    def.setUserName("def");
-    def.setDisplayName("Delta E. Foxtrot");
-    def.setSupervisorKey(abc.getId());
-    def.persist();
+    addReports(abc.getId());
+  }
 
-    Employee ghi = new Employee();
-    ghi.setUserName("ghi");
-    ghi.setDisplayName("George H. Indigo");
-    ghi.setSupervisorKey(abc.getId());
-    ghi.persist();
-
-    for (String purpose : new String[] {
-        "Spending lots of money", "Team building diamond cutting offsite",
-        "Visit to Istanbul"}) {
-      Report report = new Report();
-      report.setReporterKey(abc.getId());
-      report.setCreated(new Date());
-      report.setPurpose(purpose);
-      report.persist();
+  private void addExpenses(Long reportId) {
+    int num = rand.nextInt(5) + 1;
+    for (int i = 0; i < num; i++) {
+      int index = rand.nextInt(DESCRIPTIONS.length);
+      Expense detail = new Expense();
+      detail.setReportId(reportId);
+      detail.setDescription(DESCRIPTIONS[index]);
+      detail.setDate(getDate());
+      detail.setAmount(rand.nextInt(25000) / 100.0);
+      detail.setCategory(CATEGORIES[index]);
+      detail.setApproval("");
+      detail.setReasonDenied("");
+      detail.persist();
     }
+  }
 
-    for (String purpose : new String[] {"Money laundering", "Donut day"}) {
+  /**
+   * Add a randomly generated report.
+   * 
+   * @param employeeId the id of the employee who created the report
+   */
+  private void addReports(Long employeeId) {
+    // Add 1-20 expense reports.
+    int reportCount = 1 + rand.nextInt(20);
+    for (int i = 0; i < reportCount; i++) {
       Report report = new Report();
-      report.setCreated(new Date());
-      report.setReporterKey(def.getId());
-      report.setPurpose(purpose);
+      report.setCreated(getDate());
+      report.setReporterKey(employeeId);
+      report.setPurpose(nextValue(PURPOSES));
+      report.setNotes(nextValue(NOTES));
       report.persist();
-    }
 
-    for (String purpose : new String[] {
-        "ISDN modem for telecommuting", "Sushi offsite",
-        "Baseball card research", "Potato chip cooking offsite"}) {
-      Report report = new Report();
-      report.setCreated(new Date());
-      report.setReporterKey(ghi.getId());
-      report.setPurpose(purpose);
-      report.persist();
+      addExpenses(report.getId());
     }
   }
+
+  private Date getDate() {
+    long now = new Date().getTime();
+    // Go back up to 90 days from the current date
+    long dateOffset = rand.nextInt(60 * 60 * 24 * 90) * 1000L;
+    return new Date(now - dateOffset);
+  }
+
+  /**
+   * Get the next random value from an array.
+   * 
+   * @param array the array
+   * @return a random value from the array
+   */
+  private String nextValue(String[] array) {
+    return array[rand.nextInt(array.length)];
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
index 5c96797..9b2b61a 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java
@@ -15,9 +15,6 @@
  */
 package com.google.gwt.sample.expenses.server.domain;
 
-import org.datanucleus.jpa.annotations.Extension;
-
-import java.util.Collections;
 import java.util.List;
 
 import javax.persistence.Column;
@@ -26,8 +23,10 @@
 import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
-import javax.persistence.JoinColumn;
+import javax.persistence.Query;
 import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
 
 /**
  * The Employee domain object.
@@ -38,7 +37,18 @@
   public static long countEmployees() {
     EntityManager em = entityManager();
     try {
-      return ((Integer) em.createQuery("select count(o) from Employee o").getSingleResult()).intValue();
+      return ((Number) em.createQuery("select count(o) from Employee o").getSingleResult()).longValue();
+    } finally {
+      em.close();
+    }
+  }
+
+  public static long countEmployeesByDepartment(String department) {
+    EntityManager em = entityManager();
+    try {
+      Query query = em.createQuery("select count(o) from Employee o where o.department=:department");
+      query.setParameter("department", department);
+      return ((Number) query.getSingleResult()).longValue();
     } finally {
       em.close();
     }
@@ -61,7 +71,7 @@
     }
   }
 
-  public static Employee findEmployee(String id) {
+  public static Employee findEmployee(Long id) {
     if (id == null) {
       return null;
     }
@@ -78,41 +88,65 @@
       int maxResults) {
     EntityManager em = entityManager();
     try {
-      return em.createQuery("select o from Employee o").setFirstResult(
+      List resultList = em.createQuery("select o from Employee o").setFirstResult(
           firstResult).setMaxResults(maxResults).getResultList();
+      // force it to materialize
+      resultList.size();
+      return resultList;
     } finally {
       em.close();
     }
   }
 
-  public static List<Employee> findListOfOneEmployee(String id) {
-    return Collections.singletonList(findEmployee(id));
+  @SuppressWarnings("unchecked")
+  public static List<Employee> findEmployeeEntriesByDepartment(
+      String department, int firstResult, int maxResults) {
+    EntityManager em = entityManager();
+    try {
+      Query query = em.createQuery("select o from Employee o WHERE o.department =:department");
+      query.setFirstResult(firstResult);
+      query.setMaxResults(maxResults);
+      query.setParameter("department", department);
+      List resultList = query.getResultList();
+      // force it to materialize
+      resultList.size();
+      return resultList;
+    } finally {
+      em.close();
+    }
   }
-  
+
+  @Size(min = 3, max = 30)
   private String userName;
 
+  private String department;
+
+  @NotNull
   private String displayName;
 
   private String password;
 
-  @JoinColumn
-  private String supervisorKey;
+  // @JoinColumn
+  private Long supervisorKey;
 
   @Id
   @Column(name = "id")
   @GeneratedValue(strategy = GenerationType.IDENTITY)
-  @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
-  private String id;
+  private Long id;
 
   @Version
   @Column(name = "version")
-  private Long version;
+  private Integer version;
+
+  public String getDepartment() {
+    return department;
+  }
 
   public String getDisplayName() {
     return this.displayName;
   }
 
-  public String getId() {
+  public Long getId() {
     return this.id;
   }
 
@@ -120,7 +154,7 @@
     return this.password;
   }
 
-  public String getSupervisor() {
+  public Long getSupervisorKey() {
     return supervisorKey;
   }
 
@@ -128,7 +162,7 @@
     return this.userName;
   }
 
-  public Long getVersion() {
+  public Integer getVersion() {
     return this.version;
   }
 
@@ -151,11 +185,15 @@
     }
   }
 
+  public void setDepartment(String department) {
+    this.department = department;
+  }
+
   public void setDisplayName(String displayName) {
     this.displayName = displayName;
   }
 
-  public void setId(String id) {
+  public void setId(Long id) {
     this.id = id;
   }
 
@@ -163,7 +201,7 @@
     this.password = password;
   }
 
-  public void setSupervisorKey(String supervisorKey) {
+  public void setSupervisorKey(Long supervisorKey) {
     this.supervisorKey = supervisorKey;
   }
 
@@ -171,7 +209,7 @@
     this.userName = userName;
   }
 
-  public void setVersion(Long version) {
+  public void setVersion(Integer version) {
     this.version = version;
   }
 
@@ -184,5 +222,4 @@
     sb.append("Password: ").append(getPassword()).append(", ");
     return sb.toString();
   }
-
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java
new file mode 100644
index 0000000..0db860c
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Expense.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.expenses.server.domain;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EntityManager;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Query;
+import javax.persistence.Version;
+
+/**
+ * Models a line item of an expense report.
+ */
+@Entity
+public class Expense {
+
+  public static final EntityManager entityManager() {
+    return EMF.get().createEntityManager();
+  }
+
+  @SuppressWarnings("unchecked")
+  public static List<Expense> findAllExpenses() {
+    EntityManager em = entityManager();
+    try {
+      List<Expense> expenseList = em.createQuery("select o from Expense o").getResultList();
+      // force it to materialize
+      expenseList.size();
+      return expenseList;
+    } finally {
+      em.close();
+    }
+  }
+  
+  public static Expense findExpense(Long id) {
+    if (id == null) {
+      return null;
+    }
+    EntityManager em = entityManager();
+    try {
+      return em.find(Expense.class, id);
+    } finally {
+      em.close();
+    }
+  }
+  
+  @SuppressWarnings("unchecked")
+  public static List<Expense> findExpensesByReport(Long reportId) {
+    EntityManager em = entityManager();
+    try {
+      Query query = em.createQuery("select o from Expense o where o.reportId =:reportId");
+      query.setParameter("reportId", reportId);
+      List<Expense> expenseList = query.getResultList();
+      // force it to materialize
+      expenseList.size();
+      return expenseList;
+    } finally {
+      em.close();
+    }
+  }
+
+  public static List<Expense> findListOfOneExpense(Long id) {
+    return Collections.singletonList(findExpense(id));
+  }
+
+  @Id
+  @Column(name = "id")
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  private Long id;
+
+  @Version
+  @Column(name = "version")
+  private Integer version;
+
+  private Double amount;
+
+  private String approval;
+  
+  private String category;
+  
+  private Date date;
+  
+  private String description;
+  
+  private String reasonDenied;
+
+  // @JoinColumn
+  private Long reportId;
+
+  public Double getAmount() {
+    return this.amount;
+  }
+
+  public String getApproval() {
+    return this.approval;
+  }
+  
+  public String getCategory() {
+    return this.category;
+  }
+
+  public Date getDate() {
+    return this.date;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+  
+  public Long getId() {
+    return this.id;
+  }
+  
+  public String getReasonDenied() {
+    return this.reasonDenied;
+  }
+
+  public Long getReportId() {
+    return this.reportId;
+  }
+
+  public Integer getVersion() {
+    return this.version;
+  }
+
+  public void persist() {
+    EntityManager em = entityManager();
+    try {
+      em.persist(this);
+    } finally {
+      em.close();
+    }
+  }
+
+  public void remove() {
+    EntityManager em = entityManager();
+    try {
+      Expense attached = em.find(Expense.class, this.id);
+      em.remove(attached);
+    } finally {
+      em.close();
+    }
+  }
+  
+  public void setAmount(Double amount) {
+    this.amount = amount;
+  }
+  
+  public void setApproval(String approval) {
+    this.approval = approval;
+  }
+
+  public void setCategory(String category) {
+    this.category = category;
+  }
+
+  public void setDate(Date date) {
+    this.date = date;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public void setReasonDenied(String reasonDenied) {
+    this.reasonDenied = reasonDenied;
+  }
+
+  public void setReportId(Long reportId) {
+    this.reportId = reportId; 
+  }
+
+  public void setVersion(Integer version) {
+    this.version = version;
+  }
+
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("Id: ").append(getId()).append(", ");
+    sb.append("Version: ").append(getVersion()).append(", ");
+    sb.append("ReportId: ").append(getReportId()).append(", ");
+    sb.append("Amount: ").append(getAmount()).append(", ");
+    sb.append("Approval: ").append(getApproval()).append(", ");
+    sb.append("Category: ").append(getCategory()).append(", ");
+    sb.append("Date: ").append(getDate()).append(", ");
+    sb.append("Description: ").append(getDescription());
+    return sb.toString();
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
index a1f2b1c..a34ba84 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Report.java
@@ -15,9 +15,6 @@
  */
 package com.google.gwt.sample.expenses.server.domain;
 
-import org.datanucleus.jpa.annotations.Extension;
-
-import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -27,7 +24,6 @@
 import javax.persistence.GeneratedValue;
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
-import javax.persistence.JoinColumn;
 import javax.persistence.Query;
 import javax.persistence.Version;
 
@@ -40,7 +36,17 @@
   public static long countReports() {
     EntityManager em = entityManager();
     try {
-      return ((Integer) em.createQuery("select count(o) from Report o").getSingleResult()).intValue();
+      return ((Number) em.createQuery("select count(o) from Report o").getSingleResult()).longValue();
+    } finally {
+      em.close();
+    }
+  }
+
+  public static long countReportsBySearch(Long employeeId, String startsWith) {
+    EntityManager em = entityManager();
+    try {
+      Query query = queryReportsBySearch(em, employeeId, startsWith, null, true);
+      return ((Number) query.getSingleResult()).longValue();
     } finally {
       em.close();
     }
@@ -63,11 +69,7 @@
     }
   }
 
-  public static List<Report> findListOfOneReport(String id) {
-    return Collections.singletonList(findReport(id));
-  }
-  
-  public static Report findReport(String id) {
+  public static Report findReport(Long id) {
     if (id == null) {
       return null;
     }
@@ -94,7 +96,26 @@
   }
 
   @SuppressWarnings("unchecked")
-  public static List<Report> findReportsByEmployee(String employeeId) {
+  public static List<Report> findReportEntriesBySearch(Long employeeId,
+      String startsWith, String orderBy, int firstResult, int maxResults) {
+    EntityManager em = entityManager();
+    try {
+      Query query = queryReportsBySearch(em, employeeId, startsWith, orderBy,
+          false);
+      query.setFirstResult(firstResult);
+      query.setMaxResults(maxResults);
+      List<Report> reportList = query.getResultList();
+      // force it to materialize
+      reportList.size();
+
+      return reportList;
+    } finally {
+      em.close();
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public static List<Report> findReportsByEmployee(Long employeeId) {
     EntityManager em = entityManager();
     try {
       Query query = em.createQuery("select o from Report o where o.reporterKey =:reporterKey");
@@ -108,33 +129,85 @@
     }
   }
 
+  /**
+   * Query for reports based on the search parameters. If startsWith is
+   * specified, the results will not be ordered.
+   * 
+   * @param em the {@link EntityManager} to use
+   * @param employeeId the employee id
+   * @param startsWith the starting string
+   * @param orderBy the order of the results
+   * @param isCount true to query on the count only
+   * @return the query
+   */
+  private static Query queryReportsBySearch(EntityManager em, Long employeeId,
+      String startsWith, String orderBy, boolean isCount) {
+    // Construct a query string.
+    boolean isFirstStatement = true;
+    boolean hasEmployee = employeeId != null && employeeId >= 0;
+    boolean hasStartsWith = startsWith != null && startsWith.length() > 0;
+    String retValue = isCount ? "count(o)" : "o";
+    String queryString = "select " + retValue + " from Report o";
+    if (hasEmployee) {
+      queryString += isFirstStatement ? " WHERE" : " AND";
+      isFirstStatement = false;
+      queryString += " o.reporterKey =:reporterKey";
+    }
+    if (hasStartsWith) {
+      queryString += isFirstStatement ? " WHERE" : " AND";
+      isFirstStatement = false;
+      queryString += " o.purposeLowerCase >=:startsWith";
+      queryString += " AND o.purposeLowerCase <=:startsWithZ";
+    }
+    if (!hasStartsWith && orderBy != null && orderBy.length() >= 0) {
+      queryString += " ORDER BY " + orderBy;
+    }
+
+    // Construct the query;
+    Query query = em.createQuery(queryString);
+    if (hasEmployee) {
+      query.setParameter("reporterKey", employeeId);
+    }
+    if (hasStartsWith) {
+      query.setParameter("startsWith", startsWith);
+      query.setParameter("startsWithZ", startsWith + "zzzzzz");
+    }
+    return query;
+  }
+  
   @Id
   @Column(name = "id")
   @GeneratedValue(strategy = GenerationType.IDENTITY)
-  @Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
-  private String id;
+  private Long id;
 
   @Version
   @Column(name = "version")
-  private Long version;
+  private Integer version;
 
   private Date created;
+  
+  private String notes;
 
   private String purpose;
 
   /**
+   * Store a lower case version of the purpose for searching.
+   */
+  @SuppressWarnings("unused")
+  private String purposeLowerCase;
+
+  /**
    * Store reporter's key instead of reporter.  See:
    * http://code.google.com/appengine
    * /docs/java/datastore/relationships.html#Unowned_Relationships
    */
-  @JoinColumn
-  @Column(name = "reporter")
-  private String reporterKey;
+  // @JoinColumn
+  private Long reporterKey;
 
-  @JoinColumn
-  private String approvedSupervisorKey;
+  // @JoinColumn
+  private Long approvedSupervisorKey;
 
-  public String getApprovedSupervisorKey() {
+  public Long getApprovedSupervisorKey() {
     return approvedSupervisorKey;
   }
 
@@ -142,19 +215,23 @@
     return this.created;
   }
 
-  public String getId() {
+  public Long getId() {
     return this.id;
   }
+  
+  public String getNotes() {
+    return this.notes;
+  }
 
   public String getPurpose() {
     return this.purpose;
   }
 
-  public String getReporterKey() {
+  public Long getReporterKey() {
     return this.reporterKey;
   }
 
-  public Long getVersion() {
+  public Integer getVersion() {
     return this.version;
   }
 
@@ -177,7 +254,7 @@
     }
   }
 
-  public void setApprovedSupervisorKey(String approvedSupervisorKey) {
+  public void setApprovedSupervisorKey(Long approvedSupervisorKey) {
     this.approvedSupervisorKey = approvedSupervisorKey;
   }
 
@@ -185,19 +262,24 @@
     this.created = created;
   }
 
-  public void setId(String id) {
+  public void setId(Long id) {
     this.id = id;
   }
+  
+  public void setNotes(String notes) {
+    this.notes = notes;
+  }
 
   public void setPurpose(String purpose) {
     this.purpose = purpose;
+    this.purposeLowerCase = purpose == null ? "" : purpose.toLowerCase();
   }
 
-  public void setReporterKey(String reporter) {
+  public void setReporterKey(Long reporter) {
     this.reporterKey = reporter;
   }
 
-  public void setVersion(Long version) {
+  public void setVersion(Integer version) {
     this.version = version;
   }
 
@@ -206,8 +288,9 @@
     sb.append("Id: ").append(getId()).append(", ");
     sb.append("Version: ").append(getVersion()).append(", ");
     sb.append("Created: ").append(getCreated()).append(", ");
+    sb.append("Notes: ").append(getNotes()).append(", ");
     sb.append("Purpose: ").append(getPurpose()).append(", ");
-    sb.append("Reporter: ").append(getReporterKey());
+    sb.append("Reporter: ").append(getReporterKey()).append(", ");
     sb.append("ApprovedSupervisor: ").append(getApprovedSupervisorKey());
     return sb.toString();
   }
diff --git a/bikeshed/src/com/google/gwt/user/client/ui/HasErrors.java b/bikeshed/src/com/google/gwt/user/client/ui/HasErrors.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/user/client/ui/HasErrors.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/user/client/ui/Renderer.java b/bikeshed/src/com/google/gwt/user/client/ui/Renderer.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/user/client/ui/Renderer.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/user/client/ui/TakesValue.java b/bikeshed/src/com/google/gwt/user/client/ui/TakesValue.java
index 2b91a1b..c64e17f 100644
--- a/bikeshed/src/com/google/gwt/user/client/ui/TakesValue.java
+++ b/bikeshed/src/com/google/gwt/user/client/ui/TakesValue.java
@@ -25,4 +25,10 @@
    * @param value the new value
    */
   void setValue(V value);
+
+  /**
+   * return the current value.
+   */
+  V getValue();
+
 }
diff --git a/bikeshed/src/com/google/gwt/user/client/ui/ValidationError.java b/bikeshed/src/com/google/gwt/user/client/ui/ValidationError.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/user/client/ui/ValidationError.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java b/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java
index ad18591..508f455 100644
--- a/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java
+++ b/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
+import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.requestfactory.shared.RequestFactory.WriteOperation;
 import com.google.gwt.valuestore.shared.DeltaValueStore;
 import com.google.gwt.valuestore.shared.Property;
@@ -58,6 +59,14 @@
     protected ReturnRecord() {
     }
 
+    public final native void fillViolations(HashMap<String, String> s) /*-{
+      for (key in this.violations) {
+        if (this.violations.hasOwnProperty(key)) {
+          s.@java.util.HashMap::put(Ljava/lang/Object;Ljava/lang/Object;)(key, this.violations[key]);
+        }
+      }
+    }-*/;
+
     public final native String getFutureId()/*-{
       return this.futureId;
     }-*/;
@@ -73,6 +82,14 @@
     public final native boolean hasFutureId()/*-{
       return 'futureId' in this;
     }-*/;
+
+    public final native boolean hasId()/*-{
+      return 'id' in this;
+    }-*/;
+
+    public final native boolean hasViolations()/*-{
+      return 'violations' in this;
+    }-*/;
   }
 
   private static class FutureIdGenerator {
@@ -93,7 +110,9 @@
     }
   }
 
-  private static final String INITIAL_VERSION = "1";
+  private static final HashMap<String, String> NULL_VIOLATIONS = new HashMap<String, String>();
+
+  private static final Integer INITIAL_VERSION = 1;
 
   private boolean used = false;
   private final FutureIdGenerator futureIdGenerator = new FutureIdGenerator();
@@ -115,78 +134,125 @@
     throw new UnsupportedOperationException("Auto-generated method stub");
   }
 
-  public void commit(String response) {
+  public void clearUsed() {
+    used = false;
+  }
+
+  public Set<SyncResult> commit(String response) {
+    Set<SyncResult> syncResults = new HashSet<SyncResult>();
     JavaScriptObject returnedJso = ReturnRecord.getJsoResponse(response);
     HashSet<String> keys = new HashSet<String>();
     ReturnRecord.fillKeys(returnedJso, keys);
 
+    Set<RecordKey> toRemove = new HashSet<RecordKey>();
     if (keys.contains(WriteOperation.CREATE.name())) {
       JsArray<ReturnRecord> newRecords = ReturnRecord.getRecords(returnedJso,
           WriteOperation.CREATE.name());
-      // construct a map from futureId to the datastore Id
+      /*
+       * construct 2 maps: (i) futureId to the datastore Id, (ii) futureId to
+       * violationsMap
+       */
       Map<Object, Object> futureToDatastoreId = new HashMap<Object, Object>();
+      Map<String, Map<String, String>> violationsMap = new HashMap<String, Map<String, String>>();
       int length = newRecords.length();
       for (int i = 0; i < length; i++) {
         ReturnRecord sync = newRecords.get(i);
-        futureToDatastoreId.put(sync.getFutureId(), sync.getId());
+        if (sync.hasViolations()) {
+          // does not have an id.
+          assert !sync.hasId();
+          HashMap<String, String> violations = new HashMap<String, String>();
+          sync.fillViolations(violations);
+          violationsMap.put(sync.getFutureId(), violations);
+        } else {
+          violationsMap.put(sync.getFutureId(), NULL_VIOLATIONS);
+          futureToDatastoreId.put(sync.getFutureId(), sync.getId());
+        }
       }
 
       for (Map.Entry<RecordKey, RecordJsoImpl> entry : creates.entrySet()) {
         final RecordKey futureKey = entry.getKey();
-        Object datastoreId = futureToDatastoreId.get(futureKey.id);
-        assert datastoreId != null;
-        futureIdGenerator.delete(futureKey.id.toString());
-
-        final RecordKey key = new RecordKey(datastoreId, futureKey.schema);
-        RecordJsoImpl value = entry.getValue();
-        value.set(Record.id, datastoreId.toString());
-        RecordJsoImpl masterRecord = master.records.get(key);
-        assert masterRecord == null;
-        master.records.put(key, value);
-        masterRecord = value;
-        master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-            masterRecord, WriteOperation.CREATE));
+        Map<String, String> violations = violationsMap.get(futureKey.id);
+        assert violations != null;
+        if (violations == NULL_VIOLATIONS) {
+          Object datastoreId = futureToDatastoreId.get(futureKey.id);
+          assert datastoreId != null;
+          futureIdGenerator.delete(futureKey.id.toString());
+          final RecordKey key = new RecordKey(datastoreId, futureKey.schema);
+          RecordJsoImpl value = entry.getValue();
+          value.set(Record.id, datastoreId.toString());
+          RecordJsoImpl masterRecord = master.records.get(key);
+          assert masterRecord == null;
+          master.records.put(key, value);
+          masterRecord = value;
+          toRemove.add(key);
+          master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
+              masterRecord, WriteOperation.CREATE));
+          syncResults.add(new SyncResultImpl(masterRecord, null));
+        } else {
+          // do not change the masterRecord or fire event
+          syncResults.add(new SyncResultImpl(entry.getValue(), violations));
+        }
       }
     }
+    processToRemove(toRemove, WriteOperation.CREATE);
 
+    toRemove.clear();
     if (keys.contains(WriteOperation.DELETE.name())) {
       JsArray<ReturnRecord> deletedRecords = ReturnRecord.getRecords(
           returnedJso, WriteOperation.DELETE.name());
-      Set<String> returnedKeys = getKeySet(deletedRecords);
+      Map<String, Map<String, String>> violationsMap = getViolationsMap(deletedRecords);
       for (Map.Entry<RecordKey, RecordJsoImpl> entry : deletes.entrySet()) {
         final RecordKey key = entry.getKey();
-        assert returnedKeys.contains(key.id);
-        RecordJsoImpl masterRecord = master.records.get(key);
-        assert masterRecord != null;
-        master.records.remove(key);
-        master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-            masterRecord, WriteOperation.DELETE));
+        Map<String, String> violations = violationsMap.get(key.id);
+        assert violations != null;
+        if (violations == NULL_VIOLATIONS) {
+          RecordJsoImpl masterRecord = master.records.get(key);
+          assert masterRecord != null;
+          master.records.remove(key);
+          toRemove.add(key);
+          master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
+              masterRecord, WriteOperation.DELETE));
+          syncResults.add(new SyncResultImpl(masterRecord, null));
+        } else {
+          // do not change the masterRecord or fire event
+          syncResults.add(new SyncResultImpl(entry.getValue(), violations));
+        }
       }
     }
+    processToRemove(toRemove, WriteOperation.DELETE);
 
+    toRemove.clear();
     if (keys.contains(WriteOperation.UPDATE.name())) {
       JsArray<ReturnRecord> updatedRecords = ReturnRecord.getRecords(
           returnedJso, WriteOperation.UPDATE.name());
-      Set<String> returnedKeys = getKeySet(updatedRecords);
+      Map<String, Map<String, String>> violationsMap = getViolationsMap(updatedRecords);
       for (Map.Entry<RecordKey, RecordJsoImpl> entry : updates.entrySet()) {
         final RecordKey key = entry.getKey();
-        assert returnedKeys.contains(key.id.toString());
-        RecordJsoImpl masterRecord = master.records.get(key);
-        assert masterRecord != null;
-        masterRecord.merge(entry.getValue());
-        master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-            masterRecord, WriteOperation.UPDATE));
+        Map<String, String> violations = violationsMap.get(key.id);
+        assert violations != null;
+        if (violations == NULL_VIOLATIONS) {
+          RecordJsoImpl masterRecord = master.records.get(key);
+          assert masterRecord != null;
+          masterRecord.merge(entry.getValue());
+          toRemove.add(key);
+          master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
+              masterRecord, WriteOperation.UPDATE));
+          syncResults.add(new SyncResultImpl(masterRecord, null));
+        } else {
+          // do not change the masterRecord or fire event
+          syncResults.add(new SyncResultImpl(entry.getValue(), violations));
+        }
       }
     }
+    processToRemove(toRemove, WriteOperation.UPDATE);
+    return syncResults;
   }
 
-  // TODO: don't use RecordSchema
-  public Record create(Record record) {
+  public Record create(String token) {
     assert !used;
-    assert record instanceof RecordImpl;
-    RecordImpl recordImpl = (RecordImpl) record;
     String futureId = futureIdGenerator.getFutureId();
-    RecordJsoImpl newRecord = RecordJsoImpl.newCopy(recordImpl.getSchema(),
+    // TODO: get schema from token
+    RecordJsoImpl newRecord = RecordJsoImpl.newCopy(null,
         futureId, INITIAL_VERSION);
     RecordKey recordKey = new RecordKey(newRecord);
     assert operations.get(recordKey) == null;
@@ -299,6 +365,22 @@
 
   public String toJson() {
     used = true;
+    if (operations.size() > 1) {
+      throw new UnsupportedOperationException(
+          "Currently, only one entity can be saved/persisted at a time");
+      /*
+       * TODO: Short-term todo is to allow multiple entities belonging to the
+       * same class to be persisted at the same time. The client side support
+       * for this operation is already in place. On the server side, this will
+       * entail persisting all entities as part of a single transaction. In
+       * particular, the transaction should fail if the validation check on any
+       * of the entities fail.
+       *
+       * Multiple entities belonging to different records can not be persisted
+       * at present due to the appEngine limitation of a transaction not being
+       * allowed to span multiple entity groups.
+       */
+    }
     StringBuffer jsonData = new StringBuffer("{");
     for (WriteOperation writeOperation : WriteOperation.values()) {
       String jsonDataForOperation = getJsonForOperation(writeOperation);
@@ -362,15 +444,6 @@
     return requestData.toString();
   }
 
-  private Set<String> getKeySet(JsArray<ReturnRecord> records) {
-    Set<String> returnSet = new HashSet<String>();
-    int length = records.length();
-    for (int i = 0; i < length; i++) {
-      returnSet.add(records.get(i).getId());
-    }
-    return returnSet;
-  }
-
   private Map<RecordKey, RecordJsoImpl> getRecordsMap(
       WriteOperation writeOperation) {
     switch (writeOperation) {
@@ -386,6 +459,24 @@
     }
   }
 
+  private Map<String, Map<String, String>> getViolationsMap(
+      JsArray<ReturnRecord> records) {
+    Map<String, Map<String, String>> violationsMap = new HashMap<String, Map<String, String>>();
+    int length = records.length();
+    for (int i = 0; i < length; i++) {
+      ReturnRecord record = records.get(i);
+      HashMap<String, String> violations = null;
+      if (record.hasViolations()) {
+        violations = new HashMap<String, String>();
+        record.fillViolations(violations);
+      } else {
+        violations = NULL_VIOLATIONS;
+      }
+      violationsMap.put(record.getId(), violations);
+    }
+    return violationsMap;
+  }
+
   private <V> boolean isRealChange(Property<V> property, V value,
       RecordJsoImpl rawMasterRecord) {
     RecordJsoImpl masterRecord = null;
@@ -416,4 +507,22 @@
   private RecordJsoImpl newChangeRecord(RecordImpl fromRecord) {
     return RecordJsoImpl.emptyCopy(fromRecord);
   }
+
+  private void processToRemove(Set<RecordKey> toRemove,
+      WriteOperation writeOperation) {
+    for (RecordKey recordKey : toRemove) {
+      operations.remove(recordKey);
+      switch (writeOperation) {
+        case CREATE:
+          creates.remove(recordKey);
+          break;
+        case DELETE:
+          deletes.remove(recordKey);
+          break;
+        case UPDATE:
+          updates.remove(recordKey);
+          break;
+      }
+    }
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/RecordKey.java b/bikeshed/src/com/google/gwt/valuestore/client/RecordKey.java
index 8d365b3..0f10975 100644
--- a/bikeshed/src/com/google/gwt/valuestore/client/RecordKey.java
+++ b/bikeshed/src/com/google/gwt/valuestore/client/RecordKey.java
@@ -19,7 +19,6 @@
 import com.google.gwt.valuestore.shared.impl.RecordJsoImpl;
 import com.google.gwt.valuestore.shared.impl.RecordSchema;
 
-
 /**
  * The key used to store {@link com.google.gwt.valuestore.shared.Record Record}s
  * in {@link com.google.gwt.valuestore.shared.ValueStore ValueStore}.
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/SyncResultImpl.java b/bikeshed/src/com/google/gwt/valuestore/client/SyncResultImpl.java
new file mode 100644
index 0000000..1fa8a95
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/valuestore/client/SyncResultImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.valuestore.client;
+
+import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.util.Map;
+
+/**
+ * Concrete implementation of SyncResult.
+ */
+public class SyncResultImpl implements SyncResult {
+
+  private final Record record;
+  private final Map<String, String> violations;
+  
+  public SyncResultImpl(Record record, Map<String, String> violations) {
+    this.record = record;
+    this.violations = violations;
+  }
+
+  public Record getRecord() {
+    return record;
+  }
+  
+  public Map<String, String> getViolations() {
+    return violations;
+  }
+
+  public boolean hasViolations() {
+    return violations != null && violations.size() > 0;
+  }
+  
+}
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/ValueStoreJsonImpl.java b/bikeshed/src/com/google/gwt/valuestore/client/ValueStoreJsonImpl.java
index 341717f..9d5ff27 100644
--- a/bikeshed/src/com/google/gwt/valuestore/client/ValueStoreJsonImpl.java
+++ b/bikeshed/src/com/google/gwt/valuestore/client/ValueStoreJsonImpl.java
@@ -42,24 +42,14 @@
     throw new UnsupportedOperationException("Auto-generated method stub");
   }
 
-  public void setRecords(JsArray<RecordJsoImpl> newRecords) {
+  public void setRecord(RecordJsoImpl newRecord) {
+    setRecordInList(newRecord, 0, null);
+  }
 
+  public void setRecords(JsArray<RecordJsoImpl> newRecords) {
     for (int i = 0, l = newRecords.length(); i < l; i++) {
       RecordJsoImpl newRecord = newRecords.get(i);
-      RecordKey recordKey = new RecordKey(newRecord);
-
-      RecordJsoImpl oldRecord = records.get(recordKey);
-      if (oldRecord == null) {
-        records.put(recordKey, newRecord);
-      } else {
-        boolean changed = oldRecord.merge(newRecord);
-        newRecord = oldRecord.cast();
-        newRecords.set(i, newRecord);
-        if (changed) {
-          eventBus.fireEvent(newRecord.getSchema().createChangeEvent(newRecord,
-              WriteOperation.UPDATE));
-        }
-      }
+      setRecordInList(newRecord, i, newRecords);
     }
   }
 
@@ -69,4 +59,33 @@
   public DeltaValueStoreJsonImpl spawnDeltaView() {
     return new DeltaValueStoreJsonImpl(this);
   }
+
+  /**
+   * @param newRecord
+   * @param i
+   * @param array
+   */
+  private void setRecordInList(RecordJsoImpl newRecord, int i,
+      JsArray<RecordJsoImpl> array) {
+    RecordKey recordKey = new RecordKey(newRecord);
+
+    RecordJsoImpl oldRecord = records.get(recordKey);
+    if (oldRecord == null) {
+      records.put(recordKey, newRecord);
+      // TODO: need to fire a create event.
+    } else {
+      // TODO: Merging is not the correct thing to do but it works as long as we
+      // don't have filtering by properties. Need to revisit this once response
+      // only has a subset of all properties.
+      boolean changed = oldRecord.merge(newRecord);
+      newRecord = oldRecord.cast();
+      if (array != null) {
+        array.set(i, newRecord);
+      }
+      if (changed) {
+        eventBus.fireEvent(newRecord.getSchema().createChangeEvent(newRecord,
+            WriteOperation.UPDATE));
+      }
+    }
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/ValueStoreListViewAdapter.java b/bikeshed/src/com/google/gwt/valuestore/client/ValueStoreListViewAdapter.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/valuestore/client/ValueStoreListViewAdapter.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.java b/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.ui.xml b/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.ui.xml
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/valuestore/client/ValuesListViewTable.ui.xml
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java b/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java
index 5e8fe51..93b6ddf 100644
--- a/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java
@@ -19,7 +19,14 @@
  * Set of changes to a ValueStore.
  */
 public interface DeltaValueStore extends ValueStore {
-  Record create(Record existingRecord);
+
+  /**
+   * Enable a DeltaValueStore to be reused again. For example, when the edit
+   * fails on the server.
+   */
+  void clearUsed();
+
+  Record create(String token);
 
   void delete(Record record);
 
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/Record.java b/bikeshed/src/com/google/gwt/valuestore/shared/Record.java
index d9fa2d6..72779fb 100644
--- a/bikeshed/src/com/google/gwt/valuestore/shared/Record.java
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/Record.java
@@ -20,16 +20,12 @@
  */
 public interface Record {
   Property<String> id = new Property<String>("id", String.class);
-  /*
-   * TODO: because of possible appEngine/dataNucleus bug, the version has to be
-   * a long instead of an int on the server side. The choice results in version
-   * being string on the client side. Temporary workaround.
-   */
-  Property<String> version = new Property<String>("version", String.class);
+
+  Property<Integer> version = new Property<Integer>("version", Integer.class);
 
   /**
    * Get this record's value for the given property. Behavior is undefined if
-   * the record has no such property, or if the property have never been set. It
+   * the record has no such property, or if the property has never been set. It
    * is unusual to call this method directly. Rather it is expected to be called
    * by bean-style getter methods provided by implementing classes.
    * 
@@ -57,5 +53,5 @@
   /**
    * @return the version of this Record
    */
-  String getVersion();
+  Integer getVersion();
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/ValuesListView.java b/bikeshed/src/com/google/gwt/valuestore/shared/ValuesListView.java
deleted file mode 100644
index e69de29..0000000
--- a/bikeshed/src/com/google/gwt/valuestore/shared/ValuesListView.java
+++ /dev/null
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordImpl.java b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordImpl.java
index 090ceeb..3c8f163 100644
--- a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordImpl.java
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordImpl.java
@@ -54,7 +54,7 @@
     return jso.getSchema();
   }
 
-  public String getVersion() {
+  public Integer getVersion() {
     return jso.getVersion();
   }
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordJsoImpl.java b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordJsoImpl.java
index 43cdb32..51dfe9a 100644
--- a/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordJsoImpl.java
+++ b/bikeshed/src/com/google/gwt/valuestore/shared/impl/RecordJsoImpl.java
@@ -42,8 +42,12 @@
     return copy;
   }
 
+  public static native RecordJsoImpl fromJson(String json) /*-{
+    return eval(json);
+  }-*/;
+
   public static RecordJsoImpl newCopy(RecordSchema<?> schema, String id,
-      String version) {
+      Integer version) {
     RecordJsoImpl newCopy = create();
     newCopy.setSchema(schema);
     newCopy.set(Record.id, id);
@@ -64,13 +68,18 @@
 
   @SuppressWarnings("unchecked")
   public final <V> V get(Property<V> property) {
-    // TODO lax for the moment b/c client code can't yet reasonably make the request
-//     assert isDefined(property.getName()) : "Cannot ask for a property before setting it: "
-//         + property.getName();
+    // TODO lax for the moment b/c client code can't yet reasonably make the
+    // request
+    // assert isDefined(property.getName()) :
+    // "Cannot ask for a property before setting it: "
+    // + property.getName();
 
     if (Integer.class.equals(property.getType())) {
       return (V) Integer.valueOf(getInt(property.getName()));
     }
+    if (Double.class.equals(property.getType())) {
+      return (V) Double.valueOf(getDouble(property.getName()));
+    }
     if (Date.class.equals(property.getType())) {
       double millis = getDouble(property.getName());
       if (GWT.isScript()) {
@@ -101,7 +110,7 @@
     return this['__key'];
   }-*/;
 
-  public final String getVersion() {
+  public final Integer getVersion() {
     return this.get(version);
   }
 
@@ -158,29 +167,41 @@
    * @return returned string.
    */
   public final native String toJson() /*-{
-    var replacer = function(key, value) {
-      if (key == '__key') {
-        return;
-      }
-      return value;
-    }
-    return JSON.stringify(this, replacer);
+    // Safari 4.0.5 appears not to honor the replacer argument, so we can't do this:
+
+    //    var replacer = function(key, value) {
+    //      if (key == '__key') {
+    //        return;
+    //      }
+    //      return value;
+    //    }
+    // return $wnd.JSON.stringify(this, replacer);
+
+    var key = this.__key;
+    delete this.__key;
+    // TODO verify that the stringify() from json2.js works on IE
+    var rtn = $wnd.JSON.stringify(this);
+    this.__key = key;
+    return rtn;
   }-*/;
 
   /**
    * Return JSON representation of just id and version fields, using org.json
    * library.
-   *
+   * 
    * @return returned string.
    */
   public final native String toJsonIdVersion() /*-{
-    var replacer = function(key, value) {
-      if (key == 'id' || key == 'version') {
-        return value;
-      }
-      return;
-    }
-    return JSON.stringify(this, replacer);
+    // Safari 4.0.5 appears not to honor the replacer argument, so we can't do this:
+    //    var replacer = function(key, value) {
+    //      if (key == 'id' || key == 'version') {
+    //        return value;
+    //      }
+    //      return;
+    //    }
+    //    return $wnd.JSON.stringify(this, replacer);
+    var object = { id: this.id, version: this.version };
+    return $wnd.JSON.stringify(object);
   }-*/;
 
   private native boolean copyPropertyIfDifferent(String name, RecordJsoImpl from) /*-{
diff --git a/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordEditActivity.java b/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordEditActivity.java
new file mode 100644
index 0000000..9599af6
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordEditActivity.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.valuestore.ui;
+
+import com.google.gwt.app.place.Activity;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.valuestore.shared.DeltaValueStore;
+import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.valuestore.shared.Value;
+
+import java.util.Set;
+
+/**
+ * Abstract activity for editing a record.
+ * 
+ * @param <R> the type of Record being edited
+ */
+public abstract class AbstractRecordEditActivity<R extends Record> implements
+    Activity, RecordEditView.Delegate {
+
+  private final RecordEditView<R> view;
+  private final String id;
+  private DeltaValueStore deltas;
+  private final RequestFactory requests;
+  private boolean dead = false;
+
+  public AbstractRecordEditActivity(RecordEditView<R> view, String id,
+      RequestFactory requests) {
+    this.view = view;
+    this.id = id;
+    this.requests = requests;
+    this.deltas = requests.getValueStore().spawnDeltaView();
+    view.setDelegate(this);
+    view.setDeltaValueStore(deltas);
+  }
+
+  public void cancelClicked() {
+    if (willStop()) {
+      exit();
+    }
+  }
+
+  public void onCancel() {
+    this.dead = true;
+  }
+
+  public void onStop() {
+    this.dead = true;
+  }
+
+  public void saveClicked() {
+    if (deltas.isChanged()) {
+      view.setEnabled(false);
+
+      final DeltaValueStore toCommit = deltas;
+      deltas = null;
+
+      Receiver<Set<SyncResult>> receiver = new Receiver<Set<SyncResult>>() {
+        public void onSuccess(Set<SyncResult> response) {
+          if (dead) {
+            return;
+          }
+          boolean hasViolations = false;
+          for (SyncResult syncResult : response) {
+            if (syncResult.getRecord().getId().equals(id)) {
+              if (syncResult.hasViolations()) {
+                hasViolations = true;
+                view.showErrors(syncResult.getViolations());
+              }
+            }
+          }
+          if (!hasViolations) {
+            exit();
+          } else {
+            deltas = toCommit;
+            deltas.clearUsed();
+            view.setEnabled(true);
+            deltas.clearUsed();
+          }
+        }
+
+      };
+      requests.syncRequest(toCommit).to(receiver).fire();
+    }
+  }
+
+  public void start(final Display display) {
+    Receiver<R> callback = new Receiver<R>() {
+      public void onSuccess(R record) {
+        if (dead) {
+          return;
+        }
+        view.setEnabled(true);
+        view.setValue(record);
+        view.showErrors(null);
+        display.showActivityWidget(view);
+      }
+    };
+
+    fireFindRequest(Value.of(id), callback);
+  }
+
+  public boolean willStop() {
+    return deltas == null || !deltas.isChanged()
+        || Window.confirm("Are you sure you want to abandon your changes?");
+  }
+
+  /**
+   * Called when the user has clicked Cancel or has successfully saved.
+   */
+  protected abstract void exit();
+
+  /**
+   * Called to fetch the details of the edited record.
+   */
+  protected abstract void fireFindRequest(Value<String> id, Receiver<R> callback);
+
+  protected String getId() {
+    return id;
+  }
+
+}
diff --git a/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListActivity.java b/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListActivity.java
index eef1fc3..126da5c 100644
--- a/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListActivity.java
+++ b/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListActivity.java
@@ -16,12 +16,16 @@
 package com.google.gwt.valuestore.ui;
 
 import com.google.gwt.app.place.Activity;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.requestfactory.shared.EntityListRequest;
-import com.google.gwt.user.client.ui.TakesValueList;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RecordListRequest;
 import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.view.client.ListView;
+import com.google.gwt.view.client.Range;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Abstract activity for requesting and displaying a list of {@Record}.
@@ -39,9 +43,11 @@
  * @param <R> the type of {@link Record} listed
  */
 public abstract class AbstractRecordListActivity<R extends Record> implements
-    Activity, RecordListView.Delegate<R>, TakesValueList<R> {
+    Activity, RecordListView.Delegate<R> {
+  private final Map<String, Integer> recordToRow = new HashMap<String, Integer>();
+
   private RecordListView<R> view;
-  private Callback callback;
+  private Display display;
 
   public AbstractRecordListActivity(RecordListView<R> view) {
     this.view = view;
@@ -60,41 +66,54 @@
    * Called by the table as it needs data.
    */
   public void onRangeChanged(ListView<R> listView) {
-    // TODO use listview.getRange()
-    getData();
+    final Range range = listView.getRange();
+
+    final Receiver<List<R>> callback = new Receiver<List<R>>() {
+      public void onSuccess(List<R> values) {
+        recordToRow.clear();
+        for (int i = 0, r = range.getStart(); i < values.size(); i++, r++) {
+          recordToRow.put(values.get(i).getId(), r);
+        }
+        getView().setData(range.getStart(), range.getLength(), values);
+        if (display != null) {
+          display.showActivityWidget(getView());
+        }
+      }
+    };
+
+    createRangeRequest(range).forProperties(getView().getProperties()).to(
+        callback).fire();
   }
 
   public void onStop() {
     view.setDelegate((RecordListView.Delegate<R>) null);
   }
 
-  /**
-   * When the request returns it calls this method.
-   * <p>
-   * TODO this was supposed to be a convenience but it's just confusing. Get
-   * real callbacks into request factory
-   */
-  public void setValueList(List<R> values) {
-    getView().setDataSize(values.size(), true);
-    getView().setData(0, values.size(), values);
-    if (callback != null) {
-      callback.onStarted(getView().asWidget());
-    }
+  public void start(Display display) {
+    this.display = display;
+    init();
   }
 
-  public void start(Callback callback) {
-    this.callback = callback;
-    getData();
+  public void update(R record) {
+    // TODO Must handle delete, new
+    Integer row = recordToRow.get(record.getId());
+    getView().setData(row, 1, Collections.singletonList(record));
   }
 
   public boolean willStop() {
     return true;
   }
 
-  protected abstract EntityListRequest<R> createRequest();
+  protected abstract RecordListRequest<R> createRangeRequest(Range range);
 
-  private void getData() {
-    createRequest().forProperties(getView().getProperties()).to(this).fire();
+  protected abstract void fireCountRequest(Receiver<Long> callback);
+
+  private void init() {
+    fireCountRequest(new Receiver<Long>() {
+      public void onSuccess(Long response) {
+        getView().setDataSize(response.intValue(), true);
+        onRangeChanged(view);
+      }
+    });
   }
-
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListView.java b/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListView.java
index 64ac28b..d3492d0 100644
--- a/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListView.java
+++ b/bikeshed/src/com/google/gwt/valuestore/ui/AbstractRecordListView.java
@@ -15,15 +15,14 @@
  */
 package com.google.gwt.valuestore.ui;
 
-import com.google.gwt.bikeshed.cells.client.ActionCell;
-import com.google.gwt.bikeshed.list.client.IdentityColumn;
-import com.google.gwt.bikeshed.list.client.PagingTableListView;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
+import com.google.gwt.bikeshed.list.client.CellTable;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwt.valuestore.shared.Property;
 import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.view.client.Range;
+import com.google.gwt.view.client.SelectionModel;
+import com.google.gwt.view.client.SingleSelectionModel;
 
 import java.util.HashSet;
 import java.util.List;
@@ -31,7 +30,7 @@
 
 /**
  * Abstract implementation of RecordListView. Subclasses must call {@link #init}
- * with the root widget, its {@link PagingTableListView}, and a list of
+ * with the root widget, its {@link CellTable}, and a list of
  * {@link PropertyColumn}.
  * 
  * @param <R> the type of the records
@@ -39,8 +38,7 @@
 public abstract class AbstractRecordListView<R extends Record> extends
     Composite implements RecordListView<R> {
 
-  private PagingTableListView<R> table;
-  private Delegate<R> delegate;
+  private CellTable<R> table;
   private Set<Property<?>> properties = new HashSet<Property<?>>();
 
   public AbstractRecordListView<R> asWidget() {
@@ -64,49 +62,43 @@
   }
   
   public void setDelegate(
-      com.google.gwt.bikeshed.list.client.ListView.Delegate<R> delegate) {
+      com.google.gwt.view.client.ListView.Delegate<R> delegate) {
     throw new UnsupportedOperationException(
         "A RecordListView requires a RecordListView.Delegate");
   }
 
-  public void setDelegate(Delegate<R> delegate) {
-    this.delegate = delegate;
+  public void setDelegate(final Delegate<R> delegate) {
     table.setDelegate(delegate);
+    
+    table.setSelectionModel(new SingleSelectionModel<R>() {
+      @Override
+      public void setSelected(R object, boolean selected) {
+        super.setSelected(object, selected);
+        delegate.showDetails(object);
+      }
+    });
   }
 
   public void setSelectionModel(SelectionModel<? super R> selectionModel) {
     table.setSelectionModel(selectionModel);
   }
 
-  protected void init(Widget root, PagingTableListView<R> table,
+  protected void init(Widget root, CellTable<R> table,
       List<PropertyColumn<R, ?>> columns) {
     super.initWidget(root);
     this.table = table;
-
+    table.setSelectionEnabled(true);
+    
     for (PropertyColumn<R, ?> column : columns) {
       table.addColumn(column, column.getProperty().getName());
       properties.add(column.getProperty());
     }
-
-    table.addColumn(new IdentityColumn<R>(new ActionCell<R>("Show",
-        new ActionCell.Delegate<R>() {
-          public void execute(R object) {
-            delegate.showDetails(object);
-          }
-        })));
-
-    table.addColumn(new IdentityColumn<R>(new ActionCell<R>("Edit",
-        new ActionCell.Delegate<R>() {
-          public void execute(R object) {
-            delegate.edit(object);
-          }
-        })));
   }
 
   @Override
   protected void initWidget(Widget widget) {
     throw new UnsupportedOperationException(
         "AbstractRecordListView must be initialized via "
-            + "init(Widget PagingTableListView<R> List<PropertyColumn<R, ?>> ) ");
+            + "init(Widget CellTable<R> List<PropertyColumn<R, ?>> ) ");
   }
 }
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/EditorView.java b/bikeshed/src/com/google/gwt/valuestore/ui/RecordDetailsView.java
similarity index 63%
rename from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/EditorView.java
rename to bikeshed/src/com/google/gwt/valuestore/ui/RecordDetailsView.java
index de71e8a..88948cf 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/EditorView.java
+++ b/bikeshed/src/com/google/gwt/valuestore/ui/RecordDetailsView.java
@@ -1,31 +1,36 @@
 /*
  * Copyright 2010 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold;
+package com.google.gwt.valuestore.ui;
 
 import com.google.gwt.app.util.IsWidget;
 import com.google.gwt.user.client.ui.TakesValue;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.valuestore.shared.Record;
 
 /**
- * Implemented by a widget that edit sthe properties of a bean-style object.
- * 
- * @param <T> the type of the object being edited
+ * Implemented by views that show the details of an object.
+ *
+ * @param <R> the type of object to show
  */
-public interface EditorView<T extends Record> extends IsWidget, TakesValue<T> {
+public interface RecordDetailsView<R> extends TakesValue<R>, IsWidget {
 
-  Widget asWidget();
-}
+  /**
+   * Implemented by the owner of the view.
+   */
+  interface Delegate {
+    void editClicked();
+  }
+  
+  void setDelegate(Delegate delegate);
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/valuestore/ui/RecordEditView.java b/bikeshed/src/com/google/gwt/valuestore/ui/RecordEditView.java
index 4523efb..6c74371 100644
--- a/bikeshed/src/com/google/gwt/valuestore/ui/RecordEditView.java
+++ b/bikeshed/src/com/google/gwt/valuestore/ui/RecordEditView.java
@@ -20,6 +20,8 @@
 import com.google.gwt.valuestore.shared.DeltaValueStore;
 import com.google.gwt.valuestore.shared.Record;
 
+import java.util.Map;
+
 /**
  * Implemented by views that edit {@link Record}s.
  * 
@@ -32,10 +34,16 @@
    * Implemented by the owner of the view.
    */
   interface Delegate {
+    void cancelClicked();
     void saveClicked();
   }
   
+  DeltaValueStore getDeltaValueStore();
+  boolean isChanged();
   void setDelegate(Delegate delegate);
   void setDeltaValueStore(DeltaValueStore deltas);
   void setEnabled(boolean b);
+  
+  // TODO needs to be Map<Property<?>, String> errors
+  void showErrors(Map<String, String> errors);
 }
diff --git a/bikeshed/src/com/google/gwt/valuestore/ui/RecordListView.java b/bikeshed/src/com/google/gwt/valuestore/ui/RecordListView.java
index 5c0246b..c8a9158 100644
--- a/bikeshed/src/com/google/gwt/valuestore/ui/RecordListView.java
+++ b/bikeshed/src/com/google/gwt/valuestore/ui/RecordListView.java
@@ -16,8 +16,8 @@
 package com.google.gwt.valuestore.ui;
 
 import com.google.gwt.app.util.IsWidget;
-import com.google.gwt.bikeshed.list.client.ListView;
 import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.view.client.ListView;
 
 /**
  * A view of a list of {@link Records}, which declares which properties it is
@@ -38,11 +38,6 @@
    */
   interface Delegate<R extends Record> extends ListView.Delegate<R> {
     /**
-     * @param record the record the user wants to edit
-     */
-    void edit(R record);
-
-    /**
      * @param record the record whose details the user wants to see
      */
     void showDetails(R record);
@@ -52,7 +47,7 @@
    * A RecordListView requires a RecordListView.Delegate.
    */
   void setDelegate(
-      com.google.gwt.bikeshed.list.client.ListView.Delegate<R> delegate)
+      com.google.gwt.view.client.ListView.Delegate<R> delegate)
       throws UnsupportedOperationException;
 
   /**
diff --git a/bikeshed/src/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayImpl.java b/bikeshed/super/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayImpl.java
similarity index 100%
rename from bikeshed/src/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayImpl.java
rename to bikeshed/super/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayImpl.java
diff --git a/bikeshed/src/com/google/gwt/collections/super/com/google/gwt/collections/MutableArray.java b/bikeshed/super/com/google/gwt/collections/super/com/google/gwt/collections/MutableArray.java
similarity index 100%
rename from bikeshed/src/com/google/gwt/collections/super/com/google/gwt/collections/MutableArray.java
rename to bikeshed/super/com/google/gwt/collections/super/com/google/gwt/collections/MutableArray.java
diff --git a/bikeshed/test/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayInternalTest.java b/bikeshed/test-super/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayInternalTest.java
similarity index 100%
rename from bikeshed/test/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayInternalTest.java
rename to bikeshed/test-super/com/google/gwt/collections/super/com/google/gwt/collections/ImmutableArrayInternalTest.java
diff --git a/bikeshed/test/com/google/gwt/collections/super/com/google/gwt/collections/MutableArrayInternalTest.java b/bikeshed/test-super/com/google/gwt/collections/super/com/google/gwt/collections/MutableArrayInternalTest.java
similarity index 99%
rename from bikeshed/test/com/google/gwt/collections/super/com/google/gwt/collections/MutableArrayInternalTest.java
rename to bikeshed/test-super/com/google/gwt/collections/super/com/google/gwt/collections/MutableArrayInternalTest.java
index 6f5fca2..b6e6f0e 100644
--- a/bikeshed/test/com/google/gwt/collections/super/com/google/gwt/collections/MutableArrayInternalTest.java
+++ b/bikeshed/test-super/com/google/gwt/collections/super/com/google/gwt/collections/MutableArrayInternalTest.java
@@ -29,6 +29,10 @@
     return null;
   }
 
+  public native boolean hasElems(MutableArray ma) /*-{
+    return !(ma.elems === undefined) 
+  }-*/;
+
   public void testSetSizeNullElems() {
     MutableArray<String> b = createMutableArray();
     
@@ -38,9 +42,5 @@
     b.setSize(0, null);
     assertFalse(hasElems(b));
   }
-  
-  public native boolean hasElems(MutableArray ma) /*-{
-    return !(ma.elems === undefined) 
-  }-*/;
 
 }
diff --git a/bikeshed/test/com/google/gwt/app/place/ActivityManagerTest.java b/bikeshed/test/com/google/gwt/app/place/ActivityManagerTest.java
new file mode 100644
index 0000000..5e034c5
--- /dev/null
+++ b/bikeshed/test/com/google/gwt/app/place/ActivityManagerTest.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.app.place;
+
+import com.google.gwt.app.util.IsWidget;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.user.client.ui.Widget;
+
+import junit.framework.TestCase;
+
+/**
+ * Eponymous unit test.
+ */
+public class ActivityManagerTest extends TestCase {
+  private static class AsyncActivity extends SyncActivity {
+    AsyncActivity(MyView view) {
+      super(view);
+    }
+
+    @Override
+    public void start(Display display) {
+      this.display = display;
+    }
+
+    void finish() {
+      display.showActivityWidget(view);
+    }
+  }
+
+  private static class MyDisplay implements Activity.Display {
+    IsWidget widget = null;
+
+    public void showActivityWidget(IsWidget widget) {
+      this.widget = widget;
+    }
+  }
+
+  private static class MyPlace extends Place {
+  };
+
+  private static class MyView implements IsWidget {
+    public Widget asWidget() {
+      return null;
+    }
+  }
+
+  private static class SyncActivity implements Activity {
+    boolean canceled = false;
+    boolean stopped = false;
+    Display display = null;
+    boolean willStop = true;
+    MyView view;
+
+    SyncActivity(MyView view) {
+      this.view = view;
+    }
+
+    public void onCancel() {
+      canceled = true;
+    }
+
+    public void onStop() {
+      stopped = true;
+    }
+
+    public void start(Display display) {
+      this.display = display;
+      display.showActivityWidget(view);
+    }
+
+    public boolean willStop() {
+      return willStop;
+    }
+  }
+
+  private final MyPlace place1 = new MyPlace();
+  private final MyPlace place2 = new MyPlace();
+
+  private final SyncActivity activity1 = new SyncActivity(new MyView());
+  private final SyncActivity activity2 = new SyncActivity(new MyView());
+
+  private final MyDisplay realDisplay = new MyDisplay();
+
+  private final ActivityMapper<MyPlace> myMap = new ActivityMapper<MyPlace>() {
+    public Activity getActivity(MyPlace place) {
+      if (place.equals(place1)) {
+        return activity1;
+      }
+      if (place.equals(place2)) {
+        return activity2;
+      }
+
+      return null;
+    }
+  };
+
+  private HandlerManager eventBus = new HandlerManager(null);
+  private ActivityManager<MyPlace> manager = new ActivityManager<MyPlace>(
+      myMap, eventBus);
+
+  public void testAsyncDispatch() {
+    final AsyncActivity asyncActivity1 = new AsyncActivity(new MyView());
+    final AsyncActivity asyncActivity2 = new AsyncActivity(new MyView());
+
+    ActivityMapper<MyPlace> map = new ActivityMapper<MyPlace>() {
+      public Activity getActivity(MyPlace place) {
+        if (place.equals(place1)) {
+          return asyncActivity1;
+        }
+        if (place.equals(place2)) {
+          return asyncActivity2;
+        }
+
+        return null;
+      }
+    };
+
+    manager = new ActivityManager<MyPlace>(map, eventBus);
+    manager.setDisplay(realDisplay);
+
+    PlaceChangeRequestedEvent<MyPlace> event = new PlaceChangeRequestedEvent<MyPlace>(
+        place1);
+    eventBus.fireEvent(event);
+    assertFalse(event.isRejected());
+    assertNull(realDisplay.widget);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(asyncActivity1.canceled);
+    assertNull(asyncActivity1.display);
+
+    eventBus.fireEvent(new PlaceChangeEvent<Place>(place1));
+    assertNull(realDisplay.widget);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(asyncActivity1.canceled);
+    assertNotNull(asyncActivity1.display);
+
+    asyncActivity1.finish();
+    assertEquals(asyncActivity1.view, realDisplay.widget);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(asyncActivity1.canceled);
+
+    event = new PlaceChangeRequestedEvent<MyPlace>(place2);
+    eventBus.fireEvent(event);
+    assertFalse(event.isRejected());
+    assertEquals(asyncActivity1.view, realDisplay.widget);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(asyncActivity1.canceled);
+    assertFalse(asyncActivity2.stopped);
+    assertFalse(asyncActivity2.canceled);
+    assertNull(asyncActivity2.display);
+
+    eventBus.fireEvent(new PlaceChangeEvent<Place>(place2));
+    assertNull(realDisplay.widget);
+    assertFalse(asyncActivity1.canceled);
+    assertTrue(asyncActivity1.stopped);
+    assertFalse(asyncActivity2.stopped);
+    assertFalse(asyncActivity2.canceled);
+    assertNotNull(asyncActivity2.display);
+
+    asyncActivity2.finish();
+    assertEquals(asyncActivity2.view, realDisplay.widget);
+  }
+
+  public void testCancel() {
+    final AsyncActivity asyncActivity1 = new AsyncActivity(new MyView());
+    final AsyncActivity ayncActivity2 = new AsyncActivity(new MyView());
+
+    ActivityMapper<MyPlace> map = new ActivityMapper<MyPlace>() {
+      public Activity getActivity(MyPlace place) {
+        if (place.equals(place1)) {
+          return asyncActivity1;
+        }
+        if (place.equals(place2)) {
+          return ayncActivity2;
+        }
+
+        return null;
+      }
+    };
+
+    manager = new ActivityManager<MyPlace>(map, eventBus);
+    manager.setDisplay(realDisplay);
+
+    PlaceChangeRequestedEvent<MyPlace> event = new PlaceChangeRequestedEvent<MyPlace>(
+        place1);
+    eventBus.fireEvent(event);
+    assertFalse(event.isRejected());
+    assertNull(realDisplay.widget);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(asyncActivity1.canceled);
+    assertNull(asyncActivity1.display);
+
+    eventBus.fireEvent(new PlaceChangeEvent<Place>(place1));
+    assertNull(realDisplay.widget);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(asyncActivity1.canceled);
+    assertNotNull(asyncActivity1.display);
+
+    event = new PlaceChangeRequestedEvent<MyPlace>(place2);
+    eventBus.fireEvent(event);
+    assertFalse(event.isRejected());
+    assertNull(realDisplay.widget);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(asyncActivity1.canceled);
+
+    eventBus.fireEvent(new PlaceChangeEvent<Place>(place2));
+    assertNull(realDisplay.widget);
+    assertTrue(asyncActivity1.canceled);
+    assertFalse(asyncActivity1.stopped);
+    assertFalse(ayncActivity2.stopped);
+    assertFalse(ayncActivity2.canceled);
+    assertNotNull(ayncActivity2.display);
+
+    ayncActivity2.finish();
+    assertEquals(ayncActivity2.view, realDisplay.widget);
+
+    asyncActivity1.finish();
+    assertEquals(ayncActivity2.view, realDisplay.widget);
+  }
+
+  public void testEventSetupAndTeardown() {
+    assertEquals(0, eventBus.getHandlerCount(PlaceChangeEvent.TYPE));
+    assertEquals(0, eventBus.getHandlerCount(PlaceChangeRequestedEvent.TYPE));
+
+    manager.setDisplay(realDisplay);
+
+    assertEquals(1, eventBus.getHandlerCount(PlaceChangeEvent.TYPE));
+    assertEquals(1, eventBus.getHandlerCount(PlaceChangeRequestedEvent.TYPE));
+
+    manager.setDisplay(null);
+
+    assertEquals(0, eventBus.getHandlerCount(PlaceChangeEvent.TYPE));
+    assertEquals(0, eventBus.getHandlerCount(PlaceChangeRequestedEvent.TYPE));
+  }
+
+  public void testRejected() {
+    manager.setDisplay(realDisplay);
+
+    activity1.willStop = false;
+
+    PlaceChangeRequestedEvent<MyPlace> event = new PlaceChangeRequestedEvent<MyPlace>(
+        place1);
+    eventBus.fireEvent(event);
+    assertFalse(event.isRejected());
+    assertNull(realDisplay.widget);
+
+    eventBus.fireEvent(new PlaceChangeEvent<Place>(place1));
+    assertEquals(activity1.view, realDisplay.widget);
+
+    event = new PlaceChangeRequestedEvent<MyPlace>(place2);
+    eventBus.fireEvent(event);
+    assertTrue(event.isRejected());
+    assertEquals(activity1.view, realDisplay.widget);
+    assertFalse(activity1.stopped);
+    assertFalse(activity1.canceled);
+  }
+
+  public void testSyncDispatch() {
+    manager.setDisplay(realDisplay);
+
+    PlaceChangeRequestedEvent<MyPlace> event = new PlaceChangeRequestedEvent<MyPlace>(
+        place1);
+    eventBus.fireEvent(event);
+    assertFalse(event.isRejected());
+    assertNull(realDisplay.widget);
+    assertFalse(activity1.stopped);
+    assertFalse(activity1.canceled);
+
+    eventBus.fireEvent(new PlaceChangeEvent<Place>(place1));
+    assertEquals(activity1.view, realDisplay.widget);
+    assertFalse(activity1.stopped);
+    assertFalse(activity1.canceled);
+
+    event = new PlaceChangeRequestedEvent<MyPlace>(place2);
+    eventBus.fireEvent(event);
+    assertFalse(event.isRejected());
+    assertEquals(activity1.view, realDisplay.widget);
+    assertFalse(activity1.stopped);
+    assertFalse(activity1.canceled);
+
+    eventBus.fireEvent(new PlaceChangeEvent<Place>(place2));
+    assertEquals(activity2.view, realDisplay.widget);
+    assertTrue(activity1.stopped);
+    assertFalse(activity1.canceled);
+  }
+}
diff --git a/bikeshed/test/com/google/gwt/collections/ImmutableArrayTest.java b/bikeshed/test/com/google/gwt/collections/ImmutableArrayTest.java
index 0ce5628..b1053c1 100644
--- a/bikeshed/test/com/google/gwt/collections/ImmutableArrayTest.java
+++ b/bikeshed/test/com/google/gwt/collections/ImmutableArrayTest.java
@@ -110,7 +110,6 @@
     
     assertTrue(ia1.elems == ia2.elems);
   }
-
   
   public void testModifyFrozenMutable() {    
     // Do not test undefined behavior with assertions disabled
diff --git a/bikeshed/test/com/google/gwt/collections/MutableMapTest.java b/bikeshed/test/com/google/gwt/collections/MutableMapTest.java
new file mode 100644
index 0000000..b37bbd6
--- /dev/null
+++ b/bikeshed/test/com/google/gwt/collections/MutableMapTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.collections;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Common tests for all classes inheriting from {@link MutableMap}. 
+ * Override {@link #gwtSetUp} to initialize {@code keyA} and {@code keyB},
+ * {@link #getMap()} with the map constructor.
+ * Optionally override {@link #testNullKey()} to test special behavior
+ * related to null keys as this test case assumes null keys are a valid case.
+ * 
+ * @param <K> type of keys to test.
+ */
+public abstract class MutableMapTest<K> extends GWTTestCase {
+  
+  protected K keyA;
+
+  protected K keyB;
+
+  @Override
+  public String getModuleName() {
+    return null;
+  }
+
+  public void testClear() {
+    MutableMap<K, Integer> msm = getMap();
+    
+    msm.put(keyA, 1);
+    msm.put(keyB, 2);
+    
+    msm.clear();
+    assertFalse(msm.containsKey(keyA));
+    assertFalse(msm.containsKey(keyB));
+    assertTrue(msm.isEmpty());
+  }
+
+  public void testContainsKey() {
+    MutableMap<K, Integer> msm = getMap();
+    
+    assertFalse(msm.containsKey(keyA));
+    
+    msm.put(keyA, 1);
+    assertTrue(msm.containsKey(keyA));
+    
+    msm.remove(keyA);
+    assertFalse(msm.containsKey(keyA));
+  }
+
+  public void testGet() {
+    MutableMap<K, Integer> msm = getMap();
+    
+    assertNull(msm.get(keyA));
+    
+    msm.put(keyA, 1);
+    assertEquals(1, (int) msm.get(keyA));
+    
+    msm.put(keyB, null);
+    assertNull(msm.get(keyB));
+  }
+  
+  public void testIsEmpty() {
+    MutableMap<K, Integer> msm = getMap();
+    assertTrue(msm.isEmpty());
+    
+    msm.put(keyA, 1);
+    assertFalse(msm.isEmpty());
+    
+    msm.remove(keyA);
+    assertTrue(msm.isEmpty());    
+  }
+
+  /**
+   * Tests expected behavior for <code>null</code> keys. Override this test
+   * for maps not supporting null keys.
+   */
+  public void testNullKey() {
+    MutableMap<K, Integer> msm = getMap();
+
+    assertFalse(msm.containsKey(null));
+    assertNull(msm.get(null));
+    
+    msm.put(null, 1);
+    assertFalse(msm.isEmpty());
+    assertTrue(msm.containsKey(null));
+    assertEquals(1, (int) msm.get(null));
+    
+    msm.remove(null);
+    assertTrue(msm.isEmpty());
+    assertFalse(msm.containsKey(null));
+    assertNull(msm.get(null));    
+  }
+  
+  public void testPut() {
+    MutableMap<K, Integer> msm = getMap();
+    
+    msm.put(keyA, 1);
+    assertEquals(1, (int) msm.get(keyA));
+    
+    msm.put(keyA, 2);
+    assertEquals(2, (int) msm.get(keyA));
+    
+    msm.put(keyB, 3);
+    assertEquals(2, (int) msm.get(keyA));
+    assertEquals(3, (int) msm.get(keyB));    
+  }
+  
+  public void testRemove() {
+    MutableMap<K, Integer> msm = getMap();
+    
+    msm.put(keyA, 1);    
+    msm.remove(keyA);
+    assertTrue(msm.isEmpty());
+    assertNull(msm.get(keyA));
+  }
+  
+  protected abstract MutableMap<K, Integer> getMap();
+
+}
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceFilter.java b/bikeshed/test/com/google/gwt/collections/MutableStringMapTest.java
similarity index 60%
copy from bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceFilter.java
copy to bikeshed/test/com/google/gwt/collections/MutableStringMapTest.java
index 903a6fb..2a133fe 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/gwt/scaffold/place/ScaffoldPlaceFilter.java
+++ b/bikeshed/test/com/google/gwt/collections/MutableStringMapTest.java
@@ -13,16 +13,23 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.sample.expenses.gwt.scaffold.place;
+package com.google.gwt.collections;
 
 /**
- * Implemented by objects that filter {@link ScaffoldPlace} subtypes to other
- * objects.
- * 
- * @param <T> the type to filter to 
+ * Tests {@link MutableStringMap} behavior.
  */
-public interface ScaffoldPlaceFilter<T> {
-  T filter(EmployeeScaffoldPlace place);
-  T filter(ListScaffoldPlace place);
-  T filter(ReportScaffoldPlace place);
+public class MutableStringMapTest extends MutableMapTest<String> {
+  
+  @Override
+  protected void gwtSetUp() throws Exception {
+    super.gwtSetUp();
+    keyA = "foo";
+    keyB = "";
+  }
+
+  @Override
+  protected MutableMap<String, Integer> getMap() {
+    return CollectionFactory.createMutableStringMap();
+  }
+
 }
diff --git a/bikeshed/war/ExpensesCustomized.html b/bikeshed/war/ExpensesMobile.html
similarity index 89%
rename from bikeshed/war/ExpensesCustomized.html
rename to bikeshed/war/ExpensesMobile.html
index 04e5c6b..c5dec7b 100644
--- a/bikeshed/war/ExpensesCustomized.html
+++ b/bikeshed/war/ExpensesMobile.html
@@ -16,8 +16,11 @@
 <html>
   <head>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <meta name="viewport" content="width=device-width, user-scalable=no">
+
     <title>Expenses</title>
-    <script type="text/javascript" language="javascript" src="expensesCustomized/expensesCustomized.nocache.js"></script>
+    <script type="text/javascript" language="javascript" src="expensesMobile/expensesMobile.nocache.js"></script>
     <script type="text/javascript" language="javascript" src="json2.js"></script>
   </head>
 
diff --git a/bikeshed/war/ExpensesScaffold.html b/bikeshed/war/Scaffold.html
similarity index 87%
rename from bikeshed/war/ExpensesScaffold.html
rename to bikeshed/war/Scaffold.html
index ec58934..b27e41f 100644
--- a/bikeshed/war/ExpensesScaffold.html
+++ b/bikeshed/war/Scaffold.html
@@ -17,7 +17,8 @@
   <head>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
     <title>Expenses Entity Browser</title>
-    <script type="text/javascript" language="javascript" src="expensesScaffold/expensesScaffold.nocache.js"></script>
+    <script type="text/javascript" language="javascript" src="json2.js"></script>
+    <script type="text/javascript" language="javascript" src="scaffold/scaffold.nocache.js"></script>
   </head>
 
   <body>
diff --git a/bikeshed/war/ExpensesScaffold.html b/bikeshed/war/ScaffoldMobile.html
similarity index 78%
copy from bikeshed/war/ExpensesScaffold.html
copy to bikeshed/war/ScaffoldMobile.html
index ec58934..227c934 100644
--- a/bikeshed/war/ExpensesScaffold.html
+++ b/bikeshed/war/ScaffoldMobile.html
@@ -16,12 +16,15 @@
 <html>
   <head>
     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
-    <title>Expenses Entity Browser</title>
-    <script type="text/javascript" language="javascript" src="expensesScaffold/expensesScaffold.nocache.js"></script>
+    <meta name="apple-mobile-web-app-capable" content="yes">
+    <meta name="viewport" content="width=device-width, user-scalable=no">
+
+    <title>Scaffold Entity Browser</title>
+    <script type="text/javascript" language="javascript" src="scaffoldMobile/scaffoldMobile.nocache.js"></script>
   </head>
 
   <body>
     <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
-  <span id='loading'><i>…loading…</i></span>
+    <span id='loading'><i>…loading…</i></span>
   </body>
 </html>
diff --git a/bikeshed/war/WEB-INF/web.xml b/bikeshed/war/WEB-INF/web.xml
index 2a3f9e8..e3bd733 100644
--- a/bikeshed/war/WEB-INF/web.xml
+++ b/bikeshed/war/WEB-INF/web.xml
@@ -47,6 +47,37 @@
     <url-pattern>/cookbook/tree</url-pattern>
   </servlet-mapping>
 
+  <!-- AppStats -->
+  <servlet>
+    <servlet-name>appstats</servlet-name>
+    <servlet-class>com.google.appengine.tools.appstats.AppstatsServlet</servlet-class>
+    <init-param>
+      <param-name>requireAdminAuthentication</param-name>
+      <param-value>false</param-value>
+    </init-param>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>appstats</servlet-name>
+    <url-pattern>/appstats/*</url-pattern>
+  </servlet-mapping>
+  <filter>
+    <filter-name>appstats</filter-name>
+    <filter-class>com.google.appengine.tools.appstats.AppstatsFilter</filter-class>
+    <init-param>
+      <param-name>logMessage</param-name>
+      <param-value>Appstats available: /appstats/details?time={ID}</param-value>
+    </init-param>
+    <init-param>
+      <param-name>basePath</param-name>
+      <param-value>/appstats/</param-value>
+    </init-param>
+  </filter>
+  <filter-mapping>
+    <filter-name>appstats</filter-name>
+    <url-pattern>/*</url-pattern>
+  </filter-mapping>
+
+
   <!-- Require login. -->
   <security-constraint>
     <web-resource-collection>
diff --git a/bikeshed/war/temp-libs/com.springsource.org.apache.log4j-1.2.15.jar b/bikeshed/war/temp-libs/com.springsource.org.apache.log4j-1.2.15.jar
new file mode 100644
index 0000000..8043a9b
--- /dev/null
+++ b/bikeshed/war/temp-libs/com.springsource.org.apache.log4j-1.2.15.jar
Binary files differ
diff --git a/bikeshed/war/temp-libs/hibernate-validator-4.0.2.GA.jar b/bikeshed/war/temp-libs/hibernate-validator-4.0.2.GA.jar
new file mode 100644
index 0000000..57e94bf
--- /dev/null
+++ b/bikeshed/war/temp-libs/hibernate-validator-4.0.2.GA.jar
Binary files differ
diff --git a/bikeshed/war/temp-libs/slf4j-api-1.5.11.jar b/bikeshed/war/temp-libs/slf4j-api-1.5.11.jar
new file mode 100644
index 0000000..ccb8c72
--- /dev/null
+++ b/bikeshed/war/temp-libs/slf4j-api-1.5.11.jar
Binary files differ
diff --git a/bikeshed/war/temp-libs/slf4j-log4j12-1.5.11.jar b/bikeshed/war/temp-libs/slf4j-log4j12-1.5.11.jar
new file mode 100644
index 0000000..ea7225b
--- /dev/null
+++ b/bikeshed/war/temp-libs/slf4j-log4j12-1.5.11.jar
Binary files differ
diff --git a/bikeshed/war/temp-libs/validation-api-1.0.0.GA-sources.jar b/bikeshed/war/temp-libs/validation-api-1.0.0.GA-sources.jar
new file mode 100644
index 0000000..43611a2
--- /dev/null
+++ b/bikeshed/war/temp-libs/validation-api-1.0.0.GA-sources.jar
Binary files differ
diff --git a/bikeshed/war/temp-libs/validation-api-1.0.0.GA.jar b/bikeshed/war/temp-libs/validation-api-1.0.0.GA.jar
new file mode 100644
index 0000000..1ff2dd7
--- /dev/null
+++ b/bikeshed/war/temp-libs/validation-api-1.0.0.GA.jar
Binary files differ
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index 2d1f2a7..3d21bc9 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -709,6 +709,19 @@
     }
     return buf.toString();
   }
+  
+  /**
+   * Set up the system to use a DevModeLogManager, which will delegate to
+   * different LogManager instances for client and server code.
+   */
+  protected static void setLogManager() {
+    String oldLogManager = System.getProperty("java.util.logging.manager");
+    if (oldLogManager != null) {
+      System.setProperty("java.util.logging.oldLogManager", oldLogManager);
+    }
+    System.setProperty("java.util.logging.manager",
+        "com.google.gwt.dev.shell.DevModeLogManager");
+  }
 
   protected TreeLogger.Type baseLogLevelForUI = null;
 
@@ -785,6 +798,9 @@
       // Eager AWT init for OS X to ensure safe coexistence with SWT.
       BootStrapPlatform.initGui();
 
+      // Ensure that client and server logging does not share a root logger
+      setLogManager();
+
       boolean success = startUp();
 
       // The web server is running now, so launch browsers for startup urls.
@@ -1133,7 +1149,7 @@
         newlyGeneratedArtifacts);
     produceOutput(linkLogger, linkerContext, artifacts, module, true);
   }
-
+  
   /**
    * Set the set of startup URLs. This is done before launching to allow the UI
    * to better present the options to the user, but note that the UI should not
diff --git a/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java b/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
index f81101b..85ac368 100644
--- a/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
+++ b/dev/core/src/com/google/gwt/dev/shell/BrowserChannelServer.java
@@ -37,6 +37,16 @@
     implements Runnable {
   
   /**
+   * Does not extend the Thread functionality in any way. We use instanceof
+   * checks elsewhere in the code to check for this particular thread.
+   */
+  public static class CodeServerThread extends Thread {
+    private CodeServerThread(BrowserChannelServer browserChannelServer) {
+      super(browserChannelServer);
+    }
+  }
+  
+  /**
    * Hook interface for responding to messages from the client.
    */
   public abstract static class SessionHandlerServer extends SessionHandler<BrowserChannelServer> {
@@ -670,7 +680,7 @@
 
   private void init(TreeLogger initialLogger) {
     this.logger = initialLogger;
-    Thread thread = new Thread(this);
+    Thread thread = new CodeServerThread(this);
     thread.setDaemon(true);
     thread.setName("Code server (initializing)");
     thread.start();
diff --git a/dev/core/src/com/google/gwt/dev/shell/DevModeLogManager.java b/dev/core/src/com/google/gwt/dev/shell/DevModeLogManager.java
new file mode 100644
index 0000000..860af85
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/DevModeLogManager.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.shell;
+
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+/**
+ *  A log manager which delegates to different instances for client
+ *  code so that the client and server code do not share a root logger and try
+ *  to log to each other's handlers.
+ */
+public class DevModeLogManager extends LogManager {
+  static class LogManagerWithExposedConstructor extends LogManager {
+    public LogManagerWithExposedConstructor() {
+      super();
+    }
+  }
+  
+  protected ThreadLocal<LogManager> clientLogManager =
+    new ThreadLocal<LogManager>() {
+      public LogManager initialValue() {
+        return new LogManagerWithExposedConstructor();
+      }
+    };
+  
+  public DevModeLogManager() {
+    if (System.getProperty("java.util.logging.oldLogManager") != null) {
+      // TODO(unnurg): Instantiate the class stored in oldLogManager and
+      // delegate calls to there.
+      System.err.println(
+          "[WARN] ignoring user-specified value '" +
+          System.getProperty("java.util.logging.oldLogManager") +
+          "' for java.util.logging.manager");
+    }
+  }
+  
+  @Override
+  public boolean addLogger(Logger logger) {
+    if (isClientCode()) {
+      return clientLogManager.get().addLogger(logger);
+    }
+    return super.addLogger(logger);
+  }
+  
+  @Override
+  public void addPropertyChangeListener(PropertyChangeListener l) {
+    if (isClientCode()) {
+      clientLogManager.get().addPropertyChangeListener(l);
+    }
+    super.addPropertyChangeListener(l);
+  }
+  
+  @Override
+  public void checkAccess() {
+    if (isClientCode()) {
+      clientLogManager.get().checkAccess();
+    }
+    super.checkAccess();
+  }
+  
+  @Override
+  public Logger getLogger(String name) {
+    if (isClientCode()) {
+      return clientLogManager.get().getLogger(name);
+    }
+    return super.getLogger(name);
+  }
+
+  @Override
+  public Enumeration<String> getLoggerNames() {
+    if (isClientCode()) {
+      return clientLogManager.get().getLoggerNames();
+    }
+    return super.getLoggerNames();
+  }
+  
+  @Override
+  public String getProperty(String name) {
+    if (isClientCode()) {
+      return clientLogManager.get().getProperty(name);
+    }
+    return super.getProperty(name);
+  }
+  
+  @Override
+  public void readConfiguration() throws IOException, SecurityException {
+    if (isClientCode()) {
+      clientLogManager.get().readConfiguration();
+    }
+    super.readConfiguration();
+  }
+  
+  @Override
+  public void readConfiguration(InputStream ins) throws IOException, 
+  SecurityException {
+    if (isClientCode()) {
+      clientLogManager.get().readConfiguration(ins);
+    }
+    super.readConfiguration(ins);
+  }
+
+  @Override
+  public void removePropertyChangeListener(PropertyChangeListener l) {
+    if (isClientCode()) {
+      clientLogManager.get().removePropertyChangeListener(l);
+    }
+    super.removePropertyChangeListener(l);
+  }
+  
+  @Override
+  public void reset() {
+    if (isClientCode()) {
+      clientLogManager.get().reset();
+    }
+    super.reset();
+  }
+    
+  protected boolean isClientCode() {
+    return (Thread.currentThread() instanceof
+        BrowserChannelServer.CodeServerThread);
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/DevModeBaseTest.java b/dev/core/test/com/google/gwt/dev/DevModeBaseTest.java
new file mode 100644
index 0000000..bcc0303
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/DevModeBaseTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev;
+
+import junit.framework.TestCase;
+
+public class DevModeBaseTest extends TestCase {
+  static final String MANAGER_PROPERTY = "java.util.logging.manager";
+  static final String NEW_MANAGER = "com.google.gwt.dev.shell.DevModeLogManager";
+  static final String OLD_MANAGER_PROPERTY = "java.util.logging.oldLogManager";
+  static final String USERS_MANAGER = "UsersLogManager";
+  
+  public void testSetLogManager() {
+    assertEquals(null, System.getProperty(MANAGER_PROPERTY));
+    assertEquals(null, System.getProperty(OLD_MANAGER_PROPERTY));
+    
+    DevModeBase.setLogManager();
+    assertEquals(NEW_MANAGER, System.getProperty(MANAGER_PROPERTY));
+    assertEquals(null, System.getProperty(OLD_MANAGER_PROPERTY));
+
+    System.setProperty(MANAGER_PROPERTY, USERS_MANAGER);
+    DevModeBase.setLogManager();
+    assertEquals(NEW_MANAGER, System.getProperty(MANAGER_PROPERTY));
+    assertEquals(USERS_MANAGER, System.getProperty(OLD_MANAGER_PROPERTY));
+  }
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/shell/DevModeLogManagerTest.java b/dev/core/test/com/google/gwt/dev/shell/DevModeLogManagerTest.java
new file mode 100644
index 0000000..523cbdb
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/shell/DevModeLogManagerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.shell;
+
+import junit.framework.TestCase;
+
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+public class DevModeLogManagerTest extends TestCase {
+  private static String LOGGER_1_NAME = "Logger1";
+  private static String LOGGER_2_NAME = "Logger2";
+  
+  /**
+   * Mocks out the detection of client code since I don't know how to change
+   * Thread.currentThread to be an instance of CodeServerThread in a unit test.
+   */
+  protected static class DevModeManagerMock extends DevModeLogManager {
+    private boolean isClientCode = false;
+    
+    public void setIsClientCode(boolean value) {
+      isClientCode = value;
+    }
+    
+    @Override
+    protected boolean isClientCode() {
+      return isClientCode;
+    }
+  }
+  
+  public void testDelegation() {
+    Logger logger1 = Logger.getLogger(LOGGER_1_NAME);
+    Logger logger2 = Logger.getLogger(LOGGER_2_NAME);
+    DevModeManagerMock devManager = new DevModeManagerMock();
+    LogManager delegate = devManager.clientLogManager.get();
+    assertNull(devManager.getLogger(LOGGER_1_NAME));
+    assertNull(devManager.getLogger(LOGGER_2_NAME));
+    assertNull(delegate.getLogger(LOGGER_1_NAME));
+    assertNull(delegate.getLogger(LOGGER_2_NAME));
+        
+    // devManager delegates to the delegate
+    devManager.setIsClientCode(false);
+    devManager.addLogger(logger1);
+    assertNotNull(devManager.getLogger(LOGGER_1_NAME));
+    assertNull(delegate.getLogger(LOGGER_1_NAME));
+      
+    // devManager delegates to super
+    devManager.setIsClientCode(true);
+    devManager.addLogger(logger2);
+    assertNotNull(devManager.getLogger(LOGGER_2_NAME));
+    assertNotNull(delegate.getLogger(LOGGER_2_NAME));
+  }
+
+}
diff --git a/user/build.xml b/user/build.xml
index cac355b..19ca55e 100755
--- a/user/build.xml
+++ b/user/build.xml
@@ -41,6 +41,9 @@
    -->
   <property name="gwt.i18n.test.InnerClassChar" value="dollar"/>
 
+  <!-- Platform shouldn't matter here, just picking one -->
+  <property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev.jar" />
+
   <!--
     Classpaths added for test cases
   -->
@@ -52,11 +55,9 @@
     <pathelement location="${gwt.tools.lib}/easymock/easymock.jar"/>
     <pathelement location="${gwt.tools.lib}/easymock/easymockclassextension.jar"/>
     <pathelement location="${gwt.tools.lib}/objectweb/asm-3.1.jar"/>
+    <pathelement location="${gwt.dev.jar}" />
   </path>
 
-  <!-- Platform shouldn't matter here, just picking one -->
-  <property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev.jar" />
-
   <target name="compile" description="Compile all class files"
       unless="compile.complete">
     <mkdir dir="${javac.out}" />
@@ -97,15 +98,9 @@
         destdir="${javac.junit.out}">
       <classpath>
         <pathelement location="${javac.out}" />
-        <pathelement location="${gwt.build}/out/dev/bin-test" />
-        <pathelement location="${gwt.tools.lib}/tomcat/servlet-api-2.5.jar" />
         <pathelement location="${gwt.tools.lib}/junit/junit-3.8.1.jar" />
         <pathelement location="${gwt.tools.lib}/selenium/selenium-java-client-driver.jar" />
-       <pathelement location="${gwt.tools.lib}/cglib/cglib-2.2.jar"/>
-       <pathelement location="${gwt.tools.lib}/easymock/easymock.jar"/>
-       <pathelement location="${gwt.tools.lib}/easymock/easymockclassextension.jar"/>
-       <pathelement location="${gwt.tools.lib}/objectweb/asm-3.1.jar"/>
-        <pathelement location="${gwt.dev.jar}" />
+        <path refid="test.extraclasspath" />
       </classpath>
     </gwt.javac>
   </target>
@@ -506,9 +501,7 @@
     <limit failonerror="true" hours="${test.timeout}">
     <parallel threadsPerProcessor="${gwt.threadsPerProcessor}"
         threadCount="${gwt.threadCount}">
-      <!-- disable HtmlUnit until it is reliable
       <antcall target="test.dev.htmlunit"/>
-      -->
       <!-- no-op unless gwt.hosts.dev.remote is defined -->
       <antcall target="test.dev.remote"/>
       <!-- no-op unless gwt.hosts.dev.selenium is defined -->
@@ -527,9 +520,7 @@
     <limit failonerror="true" hours="${test.timeout}">
     <parallel threadsPerProcessor="${gwt.threadsPerProcessor}"
         threadCount="${gwt.threadCount}">
-      <!-- disable HtmlUnit until it is reliable
       <antcall target="test.web.htmlunit"/>
-      -->
       <!-- no-op unless gwt.hosts.web.remote is defined -->
       <antcall target="test.web.remote"/>
       <!-- no-op unless gwt.hosts.web.selenium is defined -->
@@ -548,9 +539,7 @@
     <limit failonerror="true" hours="${test.timeout}">
     <parallel threadsPerProcessor="${gwt.threadsPerProcessor}"
         threadCount="${gwt.threadCount}">
-      <!-- disable HtmlUnit until it is reliable
       <antcall target="test.emma.htmlunit"/>
-      -->
       <!-- no-op unless gwt.hosts.dev.remote is defined -->
       <antcall target="test.emma.remote"/>
       <!-- no-op unless gwt.hosts.dev.selenium is defined -->
@@ -569,9 +558,7 @@
     <limit failonerror="true" hours="${test.timeout}">
     <parallel threadsPerProcessor="${gwt.threadsPerProcessor}"
         threadCount="${gwt.threadCount}">
-      <!-- disable HtmlUnit until it is reliable
       <antcall target="test.draft.htmlunit"/>
-      -->
       <!-- no-op unless gwt.hosts.web.remote is defined -->
       <antcall target="test.draft.remote"/>
       <!-- no-op unless gwt.hosts.web.selenium is defined -->
@@ -590,9 +577,7 @@
     <limit failonerror="true" hours="${test.timeout}">
     <parallel threadsPerProcessor="${gwt.threadsPerProcessor}"
         threadCount="${gwt.threadCount}">
-      <!-- disable HtmlUnit until it is reliable
       <antcall target="test.web.htmlunit"/>
-      -->
       <!-- no-op unless gwt.hosts.web.remote is defined -->
       <antcall target="test.nometa.remote"/>
       <!-- no-op unless gwt.hosts.web.selenium is defined -->
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml b/user/src/com/google/gwt/cell/Cell.gwt.xml
similarity index 87%
copy from bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml
copy to user/src/com/google/gwt/cell/Cell.gwt.xml
index 32c072b..ce4a59e 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml
+++ b/user/src/com/google/gwt/cell/Cell.gwt.xml
@@ -1,12 +1,12 @@
 <!--
   Copyright 2010 Google Inc.
-
+ 
   Licensed under the Apache License, Version 2.0 (the "License"); you may not
   use this file except in compliance with the License. You may obtain a copy of
   the License at
-
+ 
   http://www.apache.org/licenses/LICENSE-2.0
-
+ 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -14,6 +14,6 @@
   the License.
 -->
 <module>
-  <inherits name='com.google.gwt.dom.DOM'/>
-  <source path="client" />
+   <inherits name="com.google.gwt.dom.DOM"/>
+   <source path="client"/>
 </module>
diff --git a/user/src/com/google/gwt/cell/client/AbstractCell.java b/user/src/com/google/gwt/cell/client/AbstractCell.java
new file mode 100644
index 0000000..85c2402
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/AbstractCell.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.cell.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * A default implementation of the {@link Cell} interface.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
+ * @param <C> the type that this Cell represents
+ */
+public abstract class AbstractCell<C> implements Cell<C> {
+
+  public boolean consumesEvents() {
+    return false;
+  }
+
+  public boolean dependsOnSelection() {
+    return false;
+  }
+
+  public Object onBrowserEvent(Element parent, C value, Object viewData,
+      NativeEvent event, ValueUpdater<C> valueUpdater) {
+    return null;
+  }
+
+  public abstract void render(C value, Object viewData, StringBuilder sb);
+
+  public void setValue(Element parent, C value, Object viewData) {
+    StringBuilder sb = new StringBuilder();
+    render(value, viewData, sb);
+    parent.setInnerHTML(sb.toString());
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ActionCell.java b/user/src/com/google/gwt/cell/client/ActionCell.java
similarity index 69%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/ActionCell.java
rename to user/src/com/google/gwt/cell/client/ActionCell.java
index f8752e7..95fbd16 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ActionCell.java
+++ b/user/src/com/google/gwt/cell/client/ActionCell.java
@@ -1,19 +1,19 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
@@ -21,11 +21,18 @@
 /**
  * A cell that renders a button and takes a delegate to perform actions on
  * mouseUp.
- *
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <C> the type that this Cell represents
  */
-public class ActionCell<C> extends Cell<C, Void> {
+public class ActionCell<C> extends AbstractCell<C> {
+
   /**
+   * TODO: doc
+   * 
    * @param <T> the type that this delegate acts on
    */
   public interface Delegate<T> {
@@ -36,6 +43,8 @@
   private final Delegate<C> delegate;
 
   /**
+   * TODO: doc
+   * 
    * @param message
    * @param delegate
    */
@@ -45,16 +54,22 @@
   }
 
   @Override
-  public Void onBrowserEvent(Element parent, C value, Void viewData,
-      NativeEvent event, ValueUpdater<C, Void> valueUpdater) {
-    if ("mouseup".equals(event.getType())) {
+  public boolean consumesEvents() {
+    // TODO Auto-generated method stub
+    return true;
+  }
+
+  @Override
+  public Void onBrowserEvent(Element parent, C value, Object viewData,
+      NativeEvent event, ValueUpdater<C> valueUpdater) {
+    if ("click".equals(event.getType())) {
       delegate.execute(value);
     }
     return null;
   }
 
   @Override
-  public void render(C value, Void viewData, StringBuilder sb) {
+  public void render(C value, Object viewData, StringBuilder sb) {
     sb.append("<button>" + message + "</button>");
   }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ButtonCell.java b/user/src/com/google/gwt/cell/client/ButtonCell.java
similarity index 64%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/ButtonCell.java
rename to user/src/com/google/gwt/cell/client/ButtonCell.java
index 5ffd0ae..864c8a3 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ButtonCell.java
+++ b/user/src/com/google/gwt/cell/client/ButtonCell.java
@@ -1,39 +1,31 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 
 /**
  * A {@link Cell} used to render a button.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
-public class ButtonCell extends Cell<String, Void> {
-
-  private static ButtonCell instance;
-
-  public static ButtonCell getInstance() {
-    if (instance == null) {
-      instance = new ButtonCell();
-    }
-    return instance;
-  }
-
-  private ButtonCell() {
-  }
+public class ButtonCell extends AbstractCell<String> {
 
   @Override
   public boolean consumesEvents() {
@@ -41,17 +33,17 @@
   }
 
   @Override
-  public Void onBrowserEvent(Element parent, String value, Void viewData,
-      NativeEvent event, ValueUpdater<String, Void> valueUpdater) {
+  public Object onBrowserEvent(Element parent, String value, Object viewData,
+      NativeEvent event, ValueUpdater<String> valueUpdater) {
     if (valueUpdater != null && "mouseup".equals(event.getType())) {
-      valueUpdater.update(value, viewData);
+      valueUpdater.update(value);
     }
 
     return viewData;
   }
 
   @Override
-  public void render(String data, Void viewData, StringBuilder sb) {
+  public void render(String data, Object viewData, StringBuilder sb) {
     sb.append("<button>");
     if (data != null) {
       sb.append(data);
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java b/user/src/com/google/gwt/cell/client/Cell.java
similarity index 64%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
rename to user/src/com/google/gwt/cell/client/Cell.java
index 1248a46..60417b2 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
+++ b/user/src/com/google/gwt/cell/client/Cell.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
@@ -21,27 +21,26 @@
 /**
  * A light weight representation of a renderable object.
  * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <C> the type that this Cell represents
- * @param <V> the type of view data that this cell consumes
  */
-public abstract class Cell<C, V> {
+public interface Cell<C> {
 
   /**
    * Returns true if the cell is interested in browser events. The default
    * implementation returns false.
    */
-  public boolean consumesEvents() {
-    return false;
-  }
+  public abstract boolean consumesEvents();
 
   /**
    * Check if this cell depends on the selection state.
    * 
    * @return true if dependant on selection, false if not
    */
-  public boolean dependsOnSelection() {
-    return false;
-  }
+  public abstract boolean dependsOnSelection();
 
   /**
    * Handle a browser event that took place within the cell. The default
@@ -54,10 +53,8 @@
    * @param valueUpdater a {@link ValueUpdater}, or null
    * @return a view data object which may be the one passed in or a new object
    */
-  public V onBrowserEvent(Element parent, C value, V viewData,
-      NativeEvent event, ValueUpdater<C, V> valueUpdater) {
-    return null;
-  }
+  public abstract Object onBrowserEvent(Element parent, C value,
+      Object viewData, NativeEvent event, ValueUpdater<C> valueUpdater);
 
   /**
    * Render a cell as HTML into a StringBuilder, suitable for passing to
@@ -67,12 +64,17 @@
    * @param viewData view data associated with the cell
    * @param sb the StringBuilder to be written to
    */
-  // TODO: render needs a way of assuming text by default, but allowing HTML
-  public abstract void render(C value, V viewData, StringBuilder sb);
+  public abstract void render(C value, Object viewData, StringBuilder sb);
 
-  public void setValue(Element parent, C value, V viewData) {
-    StringBuilder sb = new StringBuilder();
-    render(value, viewData, sb);
-    parent.setInnerHTML(sb.toString());
-  }
+  /**
+   * This method may be used by cell containers to set the value on a single
+   * cell directly, rather than using {@link Element#setInnerHTML(String)}. See
+   * {@link AbstractCell#setValue(Element, Object, Object)} for a default
+   * implementation that uses {@link #render(Object, Object, StringBuilder)}.
+   * 
+   * @param parent the parent Element
+   * @param value the value associated with the cell
+   * @param viewData the view data associated with the cell, or null
+   */
+  public abstract void setValue(Element parent, C value, Object viewData);
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CheckboxCell.java b/user/src/com/google/gwt/cell/client/CheckboxCell.java
similarity index 72%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/CheckboxCell.java
rename to user/src/com/google/gwt/cell/client/CheckboxCell.java
index 2e3c411..0835b99 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CheckboxCell.java
+++ b/user/src/com/google/gwt/cell/client/CheckboxCell.java
@@ -1,19 +1,19 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
@@ -21,8 +21,12 @@
 
 /**
  * A {@link Cell} used to render a checkbox.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
-public class CheckboxCell extends Cell<Boolean, Void> {
+public class CheckboxCell extends AbstractCell<Boolean> {
 
   @Override
   public boolean consumesEvents() {
@@ -30,19 +34,19 @@
   }
 
   @Override
-  public Void onBrowserEvent(Element parent, Boolean value, Void viewData,
-      NativeEvent event, ValueUpdater<Boolean, Void> valueUpdater) {
+  public Object onBrowserEvent(Element parent, Boolean value, Object viewData,
+      NativeEvent event, ValueUpdater<Boolean> valueUpdater) {
     String type = event.getType();
     if (valueUpdater != null && "change".equals(type)) {
       InputElement input = parent.getFirstChild().cast();
-      valueUpdater.update(input.isChecked(), viewData);
+      valueUpdater.update(input.isChecked());
     }
 
     return viewData;
   }
 
   @Override
-  public void render(Boolean data, Void viewData, StringBuilder sb) {
+  public void render(Boolean data, Object viewData, StringBuilder sb) {
     sb.append("<input type=\"checkbox\"");
     if ((data != null) && (data == true)) {
       sb.append(" checked");
diff --git a/user/src/com/google/gwt/cell/client/ClickableTextCell.java b/user/src/com/google/gwt/cell/client/ClickableTextCell.java
new file mode 100644
index 0000000..27bb993
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/ClickableTextCell.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.cell.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+/**
+ * A {@link Cell} used to render text. Clicking on the call causes its
+ * {@link ValueUpdater} to be called.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ */
+public class ClickableTextCell extends AbstractCell<String> {
+
+  @Override
+  public boolean consumesEvents() {
+    return true;
+  }
+
+  @Override
+  public Object onBrowserEvent(Element parent, String value, Object viewData,
+      NativeEvent event, ValueUpdater<String> valueUpdater) {
+    String type = event.getType();
+    if (type.equals("click")) {
+      valueUpdater.update(value);
+    }
+    return null;
+  }
+
+  @Override
+  public void render(String value, Object viewData, StringBuilder sb) {
+    if (value != null) {
+      sb.append(value);
+    }
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CompositeCell.java b/user/src/com/google/gwt/cell/client/CompositeCell.java
similarity index 64%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/CompositeCell.java
rename to user/src/com/google/gwt/cell/client/CompositeCell.java
index 902fb8d..bff974a 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CompositeCell.java
+++ b/user/src/com/google/gwt/cell/client/CompositeCell.java
@@ -13,9 +13,8 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
-import com.google.gwt.bikeshed.list.client.HasCell;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 
@@ -26,35 +25,39 @@
  * <p>
  * A {@link Cell} that is composed of other {@link Cell}s.
  * </p>
+ * 
  * <p>
  * When this cell is rendered, it will render each component {@link Cell} inside
  * a span. If the component {@link Cell} uses block level elements (such as a
  * Div), the component cells will stack vertically.
  * </p>
  * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <C> the type that this Cell represents
- * @param <V> the type of view data that this cell consumes
  */
-public class CompositeCell<C, V> extends Cell<C, V> {
+public class CompositeCell<C> extends AbstractCell<C> {
 
   /**
    * The cells that compose this {@link Cell}.
    */
-  private List<HasCell<C, ?, V>> hasCells = new ArrayList<HasCell<C, ?, V>>();
+  private List<HasCell<C, ?>> hasCells = new ArrayList<HasCell<C, ?>>();
 
   /**
    * Add a {@link HasCell} to the composite.
    * 
    * @param hasCell the {@link HasCell} to add
    */
-  public void addHasCell(HasCell<C, ?, V> hasCell) {
+  public void addHasCell(HasCell<C, ?> hasCell) {
     hasCells.add(hasCell);
   }
 
   @Override
   public boolean consumesEvents() {
     // TODO(jlabanca): Should we cache this value? Can it change?
-    for (HasCell<C, ?, V> hasCell : hasCells) {
+    for (HasCell<C, ?> hasCell : hasCells) {
       if (hasCell.getCell().consumesEvents()) {
         return true;
       }
@@ -65,7 +68,7 @@
   @Override
   public boolean dependsOnSelection() {
     // TODO(jlabanca): Should we cache this value? Can it change?
-    for (HasCell<C, ?, V> hasCell : hasCells) {
+    for (HasCell<C, ?> hasCell : hasCells) {
       if (hasCell.getCell().dependsOnSelection()) {
         return true;
       }
@@ -79,13 +82,13 @@
    * @param index the index to insert into
    * @param hasCell the {@link HasCell} to insert
    */
-  public void insertHasCell(int index, HasCell<C, ?, V> hasCell) {
+  public void insertHasCell(int index, HasCell<C, ?> hasCell) {
     hasCells.add(index, hasCell);
   }
 
   @Override
-  public V onBrowserEvent(Element parent, C value, V viewData,
-      NativeEvent event, ValueUpdater<C, V> valueUpdater) {
+  public Object onBrowserEvent(Element parent, C value, Object viewData,
+      NativeEvent event, ValueUpdater<C> valueUpdater) {
     int index = 0;
     Element target = event.getEventTarget().cast();
     Element wrapper = parent.getFirstChildElement();
@@ -106,54 +109,54 @@
    * 
    * @param hasCell the {@link HasCell} to remove
    */
-  public void removeHasCell(HasCell<C, ?, V> hasCell) {
+  public void removeHasCell(HasCell<C, ?> hasCell) {
     hasCells.remove(hasCell);
   }
 
   @Override
-  public void render(C value, V viewData, StringBuilder sb) {
-    for (HasCell<C, ?, V> hasCell : hasCells) {
+  public void render(C value, Object viewData, StringBuilder sb) {
+    for (HasCell<C, ?> hasCell : hasCells) {
       render(value, viewData, sb, hasCell);
     }
   }
 
   @Override
-  public void setValue(Element parent, C object, V viewData) {
-    for (HasCell<C, ?, V> hasCell : hasCells) {
+  public void setValue(Element parent, C object, Object viewData) {
+    for (HasCell<C, ?> hasCell : hasCells) {
       setValueImpl(parent, object, viewData, hasCell);
     }
   }
 
-  protected <X> void render(C value, V viewData, StringBuilder sb,
-      HasCell<C, X, V> hasCell) {
-    Cell<X, V> cell = hasCell.getCell();
+  protected <X> void render(C value, Object viewData, StringBuilder sb,
+      HasCell<C, X> hasCell) {
+    Cell<X> cell = hasCell.getCell();
     sb.append("<span>");
     cell.render(hasCell.getValue(value), viewData, sb);
     sb.append("</span>");
   }
 
-  private <X> V onBrowserEventImpl(Element parent, final C object, V viewData,
-      NativeEvent event, final ValueUpdater<C, V> valueUpdater,
-      final HasCell<C, X, V> hasCell) {
-    ValueUpdater<X, V> tempUpdater = null;
-    final FieldUpdater<C, X, V> fieldUpdater = hasCell.getFieldUpdater();
+  private <X> Object onBrowserEventImpl(Element parent, final C object, Object viewData,
+      NativeEvent event, final ValueUpdater<C> valueUpdater,
+      final HasCell<C, X> hasCell) {
+    ValueUpdater<X> tempUpdater = null;
+    final FieldUpdater<C, X> fieldUpdater = hasCell.getFieldUpdater();
     if (fieldUpdater != null) {
-      tempUpdater = new ValueUpdater<X, V>() {
-        public void update(X value, V viewData) {
-          fieldUpdater.update(-1, object, value, viewData);
+      tempUpdater = new ValueUpdater<X>() {
+        public void update(X value) {
+          fieldUpdater.update(-1, object, value);
           if (valueUpdater != null) {
-            valueUpdater.update(object, viewData);
+            valueUpdater.update(object);
           }
         }
       };
     }
-    Cell<X, V> cell = hasCell.getCell();
+    Cell<X> cell = hasCell.getCell();
     return cell.onBrowserEvent(parent, hasCell.getValue(object), viewData,
         event, tempUpdater);
   }
 
-  private <X> void setValueImpl(Element parent, C object, V viewData,
-      HasCell<C, X, V> hasCell) {
+  private <X> void setValueImpl(Element parent, C object, Object viewData,
+      HasCell<C, X> hasCell) {
     hasCell.getCell().setValue(parent, hasCell.getValue(object), viewData);
   }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CurrencyCell.java b/user/src/com/google/gwt/cell/client/CurrencyCell.java
similarity index 76%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/CurrencyCell.java
rename to user/src/com/google/gwt/cell/client/CurrencyCell.java
index a1b6c9b..a0c9464 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CurrencyCell.java
+++ b/user/src/com/google/gwt/cell/client/CurrencyCell.java
@@ -13,15 +13,20 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 /**
  * A {@link Cell} used to render currency.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
-public class CurrencyCell extends Cell<Integer, Void> {
+public class CurrencyCell extends AbstractCell<Integer> {
 
   @Override
-  public void render(Integer price, Void viewData, StringBuilder sb) {
+  public void render(Integer price, Object viewData, StringBuilder sb) {
+    // TODO: Use legit i18n'd currency formatting.
     boolean negative = price < 0;
     if (negative) {
       price = -price;
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/DateCell.java b/user/src/com/google/gwt/cell/client/DateCell.java
similarity index 75%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/DateCell.java
rename to user/src/com/google/gwt/cell/client/DateCell.java
index d9cef80..c3f3009 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/DateCell.java
+++ b/user/src/com/google/gwt/cell/client/DateCell.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.i18n.client.DateTimeFormat;
 
@@ -21,21 +21,33 @@
 
 /**
  * A {@link Cell} used to render {@link Date}s.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
-public class DateCell extends Cell<Date, Void> {
+public class DateCell extends AbstractCell<Date> {
 
   private final DateTimeFormat format;
 
+  /**
+   * TODO: doc
+   */
   public DateCell() {
     this(DateTimeFormat.getFullDateFormat());
   }
 
+  /**
+   * TODO: doc
+   * 
+   * @param format
+   */
   public DateCell(DateTimeFormat format) {
     this.format = format;
   }
 
   @Override
-  public void render(Date value, Void viewData, StringBuilder sb) {
+  public void render(Date value, Object viewData, StringBuilder sb) {
     if (value != null) {
       sb.append(format.format(value));
     }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/DatePickerCell.java b/user/src/com/google/gwt/cell/client/DatePickerCell.java
similarity index 79%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/DatePickerCell.java
rename to user/src/com/google/gwt/cell/client/DatePickerCell.java
index c676c62..4ce2cd2 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/DatePickerCell.java
+++ b/user/src/com/google/gwt/cell/client/DatePickerCell.java
@@ -13,13 +13,15 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
 import com.google.gwt.user.datepicker.client.DatePicker;
@@ -40,10 +42,13 @@
  * Each DatePickerCell has a unique DatePicker popup associated with it; thus,
  * if a single DatePickerCell is used as the cell for a column in a table, only
  * one entry in that column will be editable at a given time.
- *
- * @param <V> the view data type
+ * </p>
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
-public class DatePickerCell<V> extends Cell<Date, V> {
+public class DatePickerCell extends AbstractCell<Date> {
 
   private static final int ESCAPE = 27;
 
@@ -52,8 +57,7 @@
   private int offsetX = 10;
   private int offsetY = 10;
   private PopupPanel panel;
-  private ValueUpdater<Date, V> valueUpdater;
-  private V viewData;
+  private ValueUpdater<Date> valueUpdater;
 
   /**
    * Constructs a new DatePickerCell that uses the date/time format
@@ -71,13 +75,14 @@
 
     this.datePicker = new DatePicker();
     this.panel = new PopupPanel(true, true) {
-      // Dismiss when escape is pressed
       @Override
-      public boolean onKeyUpPreview(char key, int modifiers) {
-        if (key == ESCAPE) {
-          panel.hide();
+      protected void onPreviewNativeEvent(NativePreviewEvent event) {
+        if (Event.ONKEYUP == event.getTypeInt()) {
+          if (event.getNativeEvent().getKeyCode() == ESCAPE) {
+            // Dismiss when escape is pressed
+            panel.hide();
+          }
         }
-        return true;
       }
     };
     panel.add(datePicker);
@@ -86,16 +91,15 @@
     datePicker.addValueChangeHandler(new ValueChangeHandler<Date>() {
       public void onValueChange(ValueChangeEvent<Date> event) {
         panel.hide();
-        valueUpdater.update(event.getValue(), viewData);
+        valueUpdater.update(event.getValue());
       }
     });
   }
 
   @Override
-  public V onBrowserEvent(final Element parent, Date value, V viewData,
-      NativeEvent event, ValueUpdater<Date, V> valueUpdater) {
+  public Object onBrowserEvent(final Element parent, Date value, Object viewData,
+      NativeEvent event, ValueUpdater<Date> valueUpdater) {
     if (event.getType().equals("click")) {
-      this.viewData = viewData;
       this.valueUpdater = valueUpdater;
 
       datePicker.setCurrentMonth(value);
@@ -111,7 +115,7 @@
   }
 
   @Override
-  public void render(Date value, V viewData, StringBuilder sb) {
+  public void render(Date value, Object viewData, StringBuilder sb) {
     if (value != null) {
       sb.append(format.format(value));
     }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/EditTextCell.java b/user/src/com/google/gwt/cell/client/EditTextCell.java
similarity index 68%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/EditTextCell.java
rename to user/src/com/google/gwt/cell/client/EditTextCell.java
index 91dc6cc..b7e5d8c 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/EditTextCell.java
+++ b/user/src/com/google/gwt/cell/client/EditTextCell.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
@@ -22,20 +22,33 @@
 
 /**
  * An editable text cell. Click to edit, escape to cancel, return to commit.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
+ * Important TODO: This cell still treats its value as HTML for rendering
+ * purposes, which is entirely wrong. It should be able to treat it as a proper
+ * string (especially since that's all the user can enter).
  */
-public class EditTextCell extends Cell<String, String> {
+public class EditTextCell extends AbstractCell<String> {
 
   @Override
-  public String onBrowserEvent(Element parent, String value, String viewData,
-      NativeEvent event, ValueUpdater<String, String> valueUpdater) {
+  public boolean consumesEvents() {
+    return true;
+  }
+
+  @Override
+  public String onBrowserEvent(Element parent, String value, Object viewData,
+      NativeEvent event, ValueUpdater<String> valueUpdater) {
     if (viewData != null) {
-      return editEvent(parent, value, viewData, event, valueUpdater);
+      return editEvent(parent, value, (String) viewData, event, valueUpdater);
     }
     return nonEditEvent(parent, value, event);
   }
 
   @Override
-  public void render(String value, String viewData, StringBuilder sb) {
+  public void render(String value, Object viewData, StringBuilder sb) {
     if (viewData != null) {
       sb.append("<input type='text' value='" + viewData + "'></input>");
     } else {
@@ -56,17 +69,15 @@
     return null;
   }
 
-  private String commit(Element parent,
-      ValueUpdater<String, String> valueUpdater) {
-    String value;
+  private String commit(Element parent, ValueUpdater<String> valueUpdater) {
     InputElement input = (InputElement) parent.getFirstChild();
-    value = input.getValue();
-    valueUpdater.update(value, null);
+    String value = input.getValue();
+    valueUpdater.update(value);
     return cancel(parent, value);
   }
 
   private String editEvent(Element parent, String value, String viewData,
-      NativeEvent event, ValueUpdater<String, String> valueUpdater) {
+      NativeEvent event, ValueUpdater<String> valueUpdater) {
     if ("keydown".equals(event.getType())) {
       if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
         return commit(parent, valueUpdater);
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/FieldUpdater.java b/user/src/com/google/gwt/cell/client/FieldUpdater.java
similarity index 79%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/FieldUpdater.java
rename to user/src/com/google/gwt/cell/client/FieldUpdater.java
index 581a576..a7f8a6c 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/FieldUpdater.java
+++ b/user/src/com/google/gwt/cell/client/FieldUpdater.java
@@ -13,24 +13,27 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 /**
  * A {@link FieldUpdater} may be added to a Column to update a particular field
  * of a data item.
  *
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the data type that will be modified
  * @param <C> the data type of the modified field
- * @param <V> the data type of the view data for the field
  */
-public interface FieldUpdater<T, C, V> {
+public interface FieldUpdater<T, C> {
 
   /**
    * Announces a new value for a field within a base object.
+   * 
    * @param index the current row index of the object
    * @param object the base object to be updated
    * @param value the new value of the field being updated
-   * @param viewData the view data associated with the field
    */
-  void update(int index, T object, C value, V viewData);
+  void update(int index, T object, C value);
 }
diff --git a/user/src/com/google/gwt/cell/client/HasCell.java b/user/src/com/google/gwt/cell/client/HasCell.java
new file mode 100644
index 0000000..ce09b72
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/HasCell.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.cell.client;
+
+/**
+ * An interface for extracting a value of type C from an underlying data value
+ * of type T, provide a {@link Cell} to render that value, and provide a
+ * {@link FieldUpdater} to perform notification of updates to the cell.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
+ * @param <T> the underlying data type
+ * @param <C> the cell data type
+ */
+public interface HasCell<T, C> {
+
+  /**
+   * Returns the {@link Cell} of type C.
+   */
+  Cell<C> getCell();
+
+  /**
+   * Returns the {@link FieldUpdater} instance.
+   * 
+   * @return an instance of FieldUpdater<T, C>
+   */
+  FieldUpdater<T, C> getFieldUpdater();
+
+  /**
+   * Returns the value of type C extracted from the record of type T.
+   * 
+   * @param object a record of type T
+   * @return a value of type C suitable for passing to the cell
+   */
+  C getValue(T object);
+}
diff --git a/user/src/com/google/gwt/cell/client/IconCellDecorator.java b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
new file mode 100644
index 0000000..5e2be13
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/IconCellDecorator.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.cell.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
+
+/**
+ * A {@link Cell} decorator that adds an icon to another {@link Cell}.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
+ * @param <C> the type that this Cell represents
+ */
+public class IconCellDecorator<C> extends AbstractCell<C> {
+
+  private final Cell<C> cell;
+  private final String iconHtml;
+  private final int imageWidth;
+  private final String placeHolderHtml;
+
+  /**
+   * Construct a new {@link IconCellDecorator}. The icon and the content will be
+   * middle aligned by default.
+   * 
+   * @param icon the icon to use
+   * @param cell the cell to decorate
+   */
+  public IconCellDecorator(ImageResource icon, Cell<C> cell) {
+    this(icon, cell, HasVerticalAlignment.ALIGN_MIDDLE, 6);
+  }
+
+  /**
+   * Construct a new {@link IconCellDecorator}.
+   * 
+   * @param icon the icon to use
+   * @param cell the cell to decorate
+   * @param valign the vertical alignment attribute of the contents
+   * @param spacing the pixel space between the icon and the cell
+   */
+  public IconCellDecorator(ImageResource icon, Cell<C> cell,
+      VerticalAlignmentConstant valign, int spacing) {
+    this.cell = cell;
+    this.iconHtml = getImageHtml(icon, valign, false);
+    this.imageWidth = icon.getWidth() + 6;
+    this.placeHolderHtml = getImageHtml(icon, valign, true);
+  }
+
+  @Override
+  public boolean consumesEvents() {
+    return cell.consumesEvents();
+  }
+
+  @Override
+  public boolean dependsOnSelection() {
+    return cell.dependsOnSelection();
+  }
+
+  @Override
+  public Object onBrowserEvent(Element parent, C value, Object viewData,
+      NativeEvent event, ValueUpdater<C> valueUpdater) {
+    return cell.onBrowserEvent(getCellParent(parent), value, viewData, event,
+        valueUpdater);
+  }
+
+  @Override
+  public void render(C value, Object viewData, StringBuilder sb) {
+    sb.append("<div style='position:relative;padding-left:");
+    sb.append(imageWidth);
+    sb.append("px;'>");
+    if (isIconUsed(value)) {
+      sb.append(getIconHtml(value));
+    } else {
+      sb.append(placeHolderHtml);
+    }
+    sb.append("<div>");
+    cell.render(value, viewData, sb);
+    sb.append("</div></div>");
+  }
+
+  @Override
+  public void setValue(Element parent, C value, Object viewData) {
+    cell.setValue(getCellParent(parent), value, viewData);
+  }
+
+  /**
+   * Get the HTML string that represents the icon. Override this method to
+   * change the icon based on the value.
+   * 
+   * @param value the value being rendered
+   * @return the HTML string that represents the icon
+   */
+  protected String getIconHtml(C value) {
+    return iconHtml;
+  }
+
+  /**
+   * Check if the icon should be used for the value. If the icon should not be
+   * used, a placeholder of the same size will be used instead. The default
+   * implementations returns true.
+   * 
+   * @param value the value being rendered
+   * @return true to use the icon, false to use a placeholder
+   */
+  protected boolean isIconUsed(C value) {
+    return true;
+  }
+
+  /**
+   * Get the parent element of the decorated cell.
+   * 
+   * @param parent the parent of this cell
+   * @return the decorated cell's parent
+   */
+  private Element getCellParent(Element parent) {
+    return parent.getFirstChildElement().getChild(1).cast();
+  }
+
+  /**
+   * Get the HTML representation of an image.
+   * 
+   * @param res the {@link ImageResource} to render as HTML
+   * @param valign the vertical alignment
+   * @param isPlaceholder if true, do not include the background image
+   * @return the rendered HTML
+   */
+  // TODO(jlabanca): Move this to a Utility class.
+  private String getImageHtml(ImageResource res,
+      VerticalAlignmentConstant valign, boolean isPlaceholder) {
+    // Add the position and dimensions.
+    StringBuilder sb = new StringBuilder();
+    sb.append("<div style=\"position:absolute;left:0px;top:0px;height:100%;");
+    sb.append("width:").append(res.getWidth()).append("px;");
+
+    // Add the background, vertically centered.
+    if (!isPlaceholder) {
+      String vert = valign == HasVerticalAlignment.ALIGN_MIDDLE ? "center"
+          : valign.getVerticalAlignString();
+      sb.append("background:url('").append(res.getURL()).append("') ");
+      sb.append("no-repeat scroll ").append(vert).append(" center transparent;");
+    }
+
+    // Close the div and return.
+    sb.append("\"></div>");
+    return sb.toString();
+  }
+}
diff --git a/user/src/com/google/gwt/cell/client/SelectionCell.java b/user/src/com/google/gwt/cell/client/SelectionCell.java
new file mode 100644
index 0000000..6bfedb0
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/SelectionCell.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.cell.client;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.SelectElement;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A {@link Cell} used to render a drop-down list.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ */
+public class SelectionCell extends AbstractCell<String> {
+
+  private HashMap<String, Integer> indexForOption = new HashMap<String, Integer>();
+  private final List<String> options;
+
+  /**
+   * Contruct a new {@link SelectionCell} with the specified options.
+   * 
+   * @param options the options in the cell.s
+   */
+  public SelectionCell(List<String> options) {
+    this.options = new ArrayList<String>(options);
+    int index = 0;
+    for (String option : options) {
+      indexForOption.put(option, index++);
+    }
+  }
+
+  @Override
+  public Object onBrowserEvent(Element parent, String value, Object viewData,
+      NativeEvent event, ValueUpdater<String> valueUpdater) {
+    String type = event.getType();
+    if (valueUpdater != null && "change".equals(type)) {
+      SelectElement select = parent.getFirstChild().cast();
+      valueUpdater.update(options.get(select.getSelectedIndex()));
+    }
+    return viewData;
+  }
+
+  @Override
+  public void render(String value, Object viewData, StringBuilder sb) {
+    int selectedIndex = getSelectedIndex(value);
+    sb.append("<select>");
+    int index = 0;
+    for (String option : options) {
+      if (index++ == selectedIndex) {
+        sb.append("<option selected='selected'>");
+      } else {
+        sb.append("<option>");
+      }
+      sb.append(option);
+      sb.append("</option>");
+    }
+    sb.append("</select>");
+  }
+
+  private int getSelectedIndex(String value) {
+    Integer index = indexForOption.get(value);
+    if (index == null) {
+      return -1;
+    }
+    return index.intValue();
+  }
+}
diff --git a/user/src/com/google/gwt/cell/client/TextCell.java b/user/src/com/google/gwt/cell/client/TextCell.java
new file mode 100644
index 0000000..9811fab
--- /dev/null
+++ b/user/src/com/google/gwt/cell/client/TextCell.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.cell.client;
+
+/**
+ * A {@link Cell} used to render text.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
+ * Important TODO: This cell treats its value as HTML. We need to properly treat
+ * its value as a raw string, so that it's safe to use with unsanitized data.
+ * See the related comment in {@link EditTextCell}.
+ */
+public class TextCell extends AbstractCell<String> {
+
+  @Override
+  public void render(String value, Object viewData, StringBuilder sb) {
+    if (value != null) {
+      sb.append(value);
+    }
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextInputCell.java b/user/src/com/google/gwt/cell/client/TextInputCell.java
similarity index 69%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/TextInputCell.java
rename to user/src/com/google/gwt/cell/client/TextInputCell.java
index 5d1a348..a885e07 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/TextInputCell.java
+++ b/user/src/com/google/gwt/cell/client/TextInputCell.java
@@ -1,28 +1,32 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
 import com.google.gwt.dom.client.NativeEvent;
 
 /**
- * A {@link Cell} used to render a text input.
+ * A {@link AbstractCell} used to render a text input.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
-public class TextInputCell extends Cell<String, Void> {
+public class TextInputCell extends AbstractCell<String> {
 
   @Override
   public boolean consumesEvents() {
@@ -30,18 +34,18 @@
   }
 
   @Override
-  public Void onBrowserEvent(Element parent, String value, Void viewData,
-      NativeEvent event, ValueUpdater<String, Void> valueUpdater) {
+  public Object onBrowserEvent(Element parent, String value, Object viewData,
+      NativeEvent event, ValueUpdater<String> valueUpdater) {
     if (valueUpdater != null && "change".equals(event.getType())) {
       InputElement input = parent.getFirstChild().cast();
-      valueUpdater.update(input.getValue(), viewData);
+      valueUpdater.update(input.getValue());
     }
 
     return viewData;
   }
 
   @Override
-  public void render(String data, Void viewData, StringBuilder sb) {
+  public void render(String data, Object viewData, StringBuilder sb) {
     sb.append("<input type='text'");
     if (data != null) {
       sb.append(" value='" + data + "'");
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ValueUpdater.java b/user/src/com/google/gwt/cell/client/ValueUpdater.java
similarity index 79%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/client/ValueUpdater.java
rename to user/src/com/google/gwt/cell/client/ValueUpdater.java
index 3a630c6..a749c27 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ValueUpdater.java
+++ b/user/src/com/google/gwt/cell/client/ValueUpdater.java
@@ -1,32 +1,35 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.cells.client;
+package com.google.gwt.cell.client;
 
 /**
  * A {@link ValueUpdater} may be added to a Cell to provide updated data.
- *
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <C> the data type of the cell
- * @param <V> the data type of view data associated with the cell
  */
-public interface ValueUpdater<C, V> {
+public interface ValueUpdater<C> {
 
   /**
    * Announces a new value.
-   *
+   * 
    * @param value the updated value
    */
-  void update(C value, V viewData);
+  void update(C value);
 }
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 0c35ed4..606d0eb 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -46,6 +46,7 @@
    <inherits name="com.google.gwt.user.Hyperlink"/>
    <inherits name="com.google.gwt.user.FileUpload"/>
    <inherits name="com.google.gwt.user.datepicker.DatePicker"/>
+   <inherits name="com.google.gwt.user.cellview.CellView"/>
 
    <super-source path="translatable"/>
    <source path="client"/>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml b/user/src/com/google/gwt/user/cellview/CellView.gwt.xml
similarity index 76%
copy from bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml
copy to user/src/com/google/gwt/user/cellview/CellView.gwt.xml
index 32c072b..0f690c2 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml
+++ b/user/src/com/google/gwt/user/cellview/CellView.gwt.xml
@@ -1,12 +1,12 @@
 <!--
   Copyright 2010 Google Inc.
-
+ 
   Licensed under the Apache License, Version 2.0 (the "License"); you may not
   use this file except in compliance with the License. You may obtain a copy of
   the License at
-
+ 
   http://www.apache.org/licenses/LICENSE-2.0
-
+ 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -14,6 +14,8 @@
   the License.
 -->
 <module>
-  <inherits name='com.google.gwt.dom.DOM'/>
-  <source path="client" />
+   <inherits name="com.google.gwt.user.User"/>
+   <inherits name="com.google.gwt.cell.Cell"/>
+   <inherits name="com.google.gwt.view.View"/>
+   <source path="client"/>
 </module>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml b/user/src/com/google/gwt/view/View.gwt.xml
similarity index 87%
rename from bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml
rename to user/src/com/google/gwt/view/View.gwt.xml
index 32c072b..06676be 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/Cells.gwt.xml
+++ b/user/src/com/google/gwt/view/View.gwt.xml
@@ -1,12 +1,12 @@
 <!--
   Copyright 2010 Google Inc.
-
+ 
   Licensed under the Apache License, Version 2.0 (the "License"); you may not
   use this file except in compliance with the License. You may obtain a copy of
   the License at
-
+ 
   http://www.apache.org/licenses/LICENSE-2.0
-
+ 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -14,6 +14,6 @@
   the License.
 -->
 <module>
-  <inherits name='com.google.gwt.dom.DOM'/>
-  <source path="client" />
+   <inherits name="com.google.gwt.core.Core"/>
+   <source path="client"/>
 </module>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/AbstractListViewAdapter.java b/user/src/com/google/gwt/view/client/AbstractListViewAdapter.java
similarity index 95%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/AbstractListViewAdapter.java
rename to user/src/com/google/gwt/view/client/AbstractListViewAdapter.java
index ab0d964..370aae4 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/AbstractListViewAdapter.java
+++ b/user/src/com/google/gwt/view/client/AbstractListViewAdapter.java
@@ -13,9 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
-
-import com.google.gwt.bikeshed.list.client.ListView;
+package com.google.gwt.view.client;
 
 import java.io.Serializable;
 import java.util.HashSet;
@@ -25,6 +23,10 @@
 /**
  * A base implementation of a data source for list views.
  *
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the data type of records in the list
  */
 public abstract class AbstractListViewAdapter<T> implements ProvidesKey<T> {
@@ -36,6 +38,12 @@
     private int length;
     private int start;
 
+    /**
+     * TODO: doc.
+     * 
+     * @param start
+     * @param length
+     */
     public DefaultRange(int start, int length) {
       this.start = start;
       this.length = length;
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/AsyncListViewAdapter.java b/user/src/com/google/gwt/view/client/AsyncListViewAdapter.java
similarity index 92%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/AsyncListViewAdapter.java
rename to user/src/com/google/gwt/view/client/AsyncListViewAdapter.java
index 06fd164..2313d03 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/AsyncListViewAdapter.java
+++ b/user/src/com/google/gwt/view/client/AsyncListViewAdapter.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
 import java.util.List;
 
@@ -21,6 +21,10 @@
  * An implementation of {@link AbstractListViewAdapter} that allows the data to be
  * modified.
  *
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the data type of records in the list
  */
 public abstract class AsyncListViewAdapter<T> extends AbstractListViewAdapter<T> {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/DefaultSelectionModel.java b/user/src/com/google/gwt/view/client/DefaultSelectionModel.java
similarity index 67%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/DefaultSelectionModel.java
rename to user/src/com/google/gwt/view/client/DefaultSelectionModel.java
index 30e5f7a..84afb56 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/DefaultSelectionModel.java
+++ b/user/src/com/google/gwt/view/client/DefaultSelectionModel.java
@@ -1,29 +1,34 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
-import com.google.gwt.bikeshed.list.shared.SelectionModel.AbstractSelectionModel;
+import com.google.gwt.view.client.SelectionModel.AbstractSelectionModel;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * A {@link SelectionModel} that allows records to be selected according to
- * a subclass-defined rule, plus a list of positive or negative exceptions.
- *
+ * A convenience {@link SelectionModel} that allows records to be selected
+ * according to a subclass-defined rule, plus a list of positive or negative
+ * exceptions.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the data type of records in the list
  */
 public abstract class DefaultSelectionModel<T> extends
@@ -31,8 +36,6 @@
 
   private final Map<Object, Boolean> exceptions = new HashMap<Object, Boolean>();
 
-  private final ProvidesKey<T> keyProvider = getKeyProvider();
-
   /**
    * Removes all exceptions.
    */
@@ -42,16 +45,14 @@
   }
 
   /**
-   * Returns true if the given object should be selected by default.
-   * Subclasses implement this method in order to define the default
-   * selection behavior.
+   * Returns true if the given object should be selected by default. Subclasses
+   * implement this method in order to define the default selection behavior.
    */
   public abstract boolean isDefaultSelected(T object);
 
   /**
-   * If the given object is marked as an exception, return the exception
-   * value.  Otherwise, return the value of isDefaultSelected for the given
-   * object.
+   * If the given object is marked as an exception, return the exception value.
+   * Otherwise, return the value of isDefaultSelected for the given object.
    */
   public boolean isSelected(T object) {
     // Check exceptions first
@@ -65,11 +66,10 @@
   }
 
   /**
-   * Sets an object's selection state.  If the object is currently marked
-   * as an exception, and the new selected state differs from the previous
-   * selected state, the object is removed from the list of exceptions.
-   * Otherwise, the object is added to the list of exceptions with the given
-   * selected state.
+   * Sets an object's selection state. If the object is currently marked as an
+   * exception, and the new selected state differs from the previous selected
+   * state, the object is removed from the list of exceptions. Otherwise, the
+   * object is added to the list of exceptions with the given selected state.
    */
   public void setSelected(T object, boolean selected) {
     Object key = getKey(object);
@@ -91,11 +91,4 @@
     output.clear();
     output.putAll(exceptions);
   }
-
-  private Object getKey(T object) {
-    if (keyProvider == null) {
-      return object;
-    }
-    return keyProvider.getKey(object);
-  }
 }
diff --git a/user/src/com/google/gwt/view/client/HasViewData.java b/user/src/com/google/gwt/view/client/HasViewData.java
new file mode 100644
index 0000000..2961091
--- /dev/null
+++ b/user/src/com/google/gwt/view/client/HasViewData.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.view.client;
+
+/**
+ * Interface that must be implemented by {@link com.google.gwt.cell.client.Cell}
+ * containers.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ */
+public interface HasViewData {
+
+  /**
+   * Gets the view data associated with the given item.
+   * 
+   * @param key the key of the item whose view data is desired
+   * @return the view data
+   */
+  Object getViewData(Object key);
+
+  /**
+   * Sets the view data associated with the given item.
+   * 
+   * @param key the key of the item whose view data will be set
+   * @param viewData the view data
+   */
+  void setViewData(Object key, Object viewData);
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/ListView.java b/user/src/com/google/gwt/view/client/ListView.java
similarity index 84%
rename from bikeshed/src/com/google/gwt/bikeshed/list/client/ListView.java
rename to user/src/com/google/gwt/view/client/ListView.java
index 108f9e1..3fbaad5 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/ListView.java
+++ b/user/src/com/google/gwt/view/client/ListView.java
@@ -13,16 +13,17 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.client;
-
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
+package com.google.gwt.view.client;
 
 import java.util.List;
 
 /**
  * A list view.
  * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the data type of each row
  */
 public interface ListView<T> {
@@ -36,8 +37,16 @@
     void onRangeChanged(ListView<T> listView);
   }
 
+  /**
+   * TODO: doc.
+   * 
+   * @param delegate
+   */
   void setDelegate(Delegate<T> delegate);
 
+  /**
+   * TODO: doc.
+   */
   Range getRange();
 
   /**
@@ -49,6 +58,12 @@
    */
   void setData(int start, int length, List<T> values);
 
+  /**
+   * TODO: doc.
+   * 
+   * @param size
+   * @param isExact
+   */
   void setDataSize(int size, boolean isExact);
 
   /**
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListViewAdapter.java b/user/src/com/google/gwt/view/client/ListViewAdapter.java
similarity index 95%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/ListViewAdapter.java
rename to user/src/com/google/gwt/view/client/ListViewAdapter.java
index d152b65..339e7b0 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListViewAdapter.java
+++ b/user/src/com/google/gwt/view/client/ListViewAdapter.java
@@ -1,21 +1,20 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
-import com.google.gwt.bikeshed.list.client.ListView;
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DeferredCommand;
 
@@ -29,7 +28,11 @@
 /**
  * A concrete subclass of {@link AbstractListViewAdapter} that is backed by an
  * in-memory list.
- *
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the data type of the list
  */
 public class ListViewAdapter<T> extends AbstractListViewAdapter<T> {
@@ -52,7 +55,8 @@
       private WrappedListIterator(int start) {
         int size = ListWrapper.this.size();
         if (start < 0 || start > size) {
-          throw new IndexOutOfBoundsException("Index: " + start + ", Size: " + size);
+          throw new IndexOutOfBoundsException("Index: " + start + ", Size: "
+              + size);
         }
         i = start;
       }
@@ -342,7 +346,7 @@
 
     /**
      * Flush the data to the model and return the boolean.
-     *
+     * 
      * @param toRet the boolean to return
      */
     private boolean flush(boolean toRet) {
@@ -375,7 +379,7 @@
   /**
    * Get the list that backs this model. Changes to the list will be reflected
    * in the model.
-   *
+   * 
    * @return the list
    */
   public List<T> getList() {
@@ -383,8 +387,15 @@
   }
 
   /**
+   * Refresh all of the views listening to this adapter.
+   */
+  public void refresh() {
+    updateViewData(0, listWrapper.size(), listWrapper);
+  }
+
+  /**
    * Replaces this model's list.
-   *
+   * 
    * @param wrappee the model's new list
    */
   public void setList(List<T> wrappee) {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/MultiSelectionModel.java b/user/src/com/google/gwt/view/client/MultiSelectionModel.java
similarity index 80%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/MultiSelectionModel.java
rename to user/src/com/google/gwt/view/client/MultiSelectionModel.java
index 9d6d776..b5687d3 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/MultiSelectionModel.java
+++ b/user/src/com/google/gwt/view/client/MultiSelectionModel.java
@@ -13,9 +13,9 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
-import com.google.gwt.bikeshed.list.shared.SelectionModel.AbstractSelectionModel;
+import com.google.gwt.view.client.SelectionModel.AbstractSelectionModel;
 
 import java.util.Set;
 import java.util.TreeSet;
@@ -23,6 +23,10 @@
 /**
  * A simple selection model that allows multiple objects to be selected.
  * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the record data type
  */
 public class MultiSelectionModel<T> extends AbstractSelectionModel<T> {
@@ -40,16 +44,16 @@
   }
 
   public boolean isSelected(T object) {
-    return selectedKeys.contains(getKeyProvider().getKey(object));
+    return selectedKeys.contains(getKey(object));
   }
 
   public void setSelected(T object, boolean selected) {
     if (selected) {
       selectedSet.add(object);
-      selectedKeys.add(getKeyProvider().getKey(object));
+      selectedKeys.add(getKey(object));
     } else {
       selectedSet.remove(object);
-      selectedKeys.remove(getKeyProvider().getKey(object));
+      selectedKeys.remove(getKey(object));
     }
     scheduleSelectionChangeEvent();
   }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingListView.java b/user/src/com/google/gwt/view/client/PagingListView.java
similarity index 76%
rename from bikeshed/src/com/google/gwt/bikeshed/list/client/PagingListView.java
rename to user/src/com/google/gwt/view/client/PagingListView.java
index d97c51e..0d3e646 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingListView.java
+++ b/user/src/com/google/gwt/view/client/PagingListView.java
@@ -13,11 +13,15 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.client;
+package com.google.gwt.view.client;
 
 /**
  * A list view that displays data in 'pages'.
  *
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ *
  * @param <T> the data type of each row
  */
 public interface PagingListView<T> extends ListView<T> {
@@ -32,15 +36,39 @@
     void onRangeOrSizeChanged(PagingListView<T> listView);
   }
 
+  /**
+   * TODO: doc.
+   */
   int getDataSize();
-  
+
+  /**
+   * TODO: doc.
+   */
   int getPageSize();
-  
+
+  /**
+   * TODO: doc.
+   */
   int getPageStart();
-  
+
+  /**
+   * TODO: doc.
+   * 
+   * @param pager
+   */
   void setPager(Pager<T> pager);
-  
+
+  /**
+   * TODO: doc.
+   * 
+   * @param pageSize
+   */
   void setPageSize(int pageSize);
-  
+
+  /**
+   * TODO: doc.
+   * 
+   * @param pageStart
+   */
   void setPageStart(int pageStart);
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ProvidesKey.java b/user/src/com/google/gwt/view/client/ProvidesKey.java
similarity index 91%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/ProvidesKey.java
rename to user/src/com/google/gwt/view/client/ProvidesKey.java
index b86e09c..a5c3614 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ProvidesKey.java
+++ b/user/src/com/google/gwt/view/client/ProvidesKey.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
 /**
  * <p>
@@ -29,6 +29,10 @@
  * be the case that A.equals(B) == false and B.equals(A) == false.
  * </p>
  *
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ *
  * @param <T> the data type of records in the list
  */
 public interface ProvidesKey<T> {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/Range.java b/user/src/com/google/gwt/view/client/Range.java
similarity index 87%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/Range.java
rename to user/src/com/google/gwt/view/client/Range.java
index 6a8cff2..2ae98a0 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/Range.java
+++ b/user/src/com/google/gwt/view/client/Range.java
@@ -13,10 +13,14 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
 /**
  * The range of interest for a single handler.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
 public interface Range {
 
@@ -34,4 +38,3 @@
    */
   int getLength();
 }
-
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java b/user/src/com/google/gwt/view/client/SelectionModel.java
similarity index 91%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
rename to user/src/com/google/gwt/view/client/SelectionModel.java
index e6ef41f..88a0da6 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
+++ b/user/src/com/google/gwt/view/client/SelectionModel.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -26,9 +26,13 @@
 /**
  * A model for selection within a list.
  * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the data type of records in the list
  */
-public interface SelectionModel<T> extends HasHandlers {
+public interface SelectionModel<T> extends HasHandlers, ProvidesKey<T> {
 
   /**
    * Handler interface for {@link SelectionChangeEvent} events.
@@ -122,17 +126,14 @@
       handlerManager.fireEvent(event);
     }
 
+    public Object getKey(T item) {
+      return keyProvider == null ? item : keyProvider.getKey(item);
+    }
+
     /**
      * Returns a ProvidesKey instance that simply returns the input data item.
      */
     public ProvidesKey<T> getKeyProvider() {
-      if (keyProvider == null) {
-        keyProvider = new ProvidesKey<T>() {
-          public Object getKey(T item) {
-            return item;
-          }
-        };
-      }
       return keyProvider;
     }
 
@@ -171,12 +172,6 @@
   HandlerRegistration addSelectionChangeHandler(SelectionChangeHandler handler);
 
   /**
-   * Returns a ProvidesKey instance that may be used to provide a unique key for
-   * each record.
-   */
-  ProvidesKey<T> getKeyProvider();
-
-  /**
    * Check if an object is selected.
    * 
    * @param object the object
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SingleSelectionModel.java b/user/src/com/google/gwt/view/client/SingleSelectionModel.java
similarity index 76%
rename from bikeshed/src/com/google/gwt/bikeshed/list/shared/SingleSelectionModel.java
rename to user/src/com/google/gwt/view/client/SingleSelectionModel.java
index 293c4c3..8c1134b 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SingleSelectionModel.java
+++ b/user/src/com/google/gwt/view/client/SingleSelectionModel.java
@@ -13,16 +13,20 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.list.shared;
+package com.google.gwt.view.client;
 
-import com.google.gwt.bikeshed.list.shared.SelectionModel.AbstractSelectionModel;
+import com.google.gwt.view.client.SelectionModel.AbstractSelectionModel;
 
 /**
  * A simple selection model that allows only one object to be selected a a time.
  * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
+ * 
  * @param <T> the record data type
  */
-public final class SingleSelectionModel<T> extends AbstractSelectionModel<T> {
+public class SingleSelectionModel<T> extends AbstractSelectionModel<T> {
 
   private T curSelection;
   private Object curKey;
@@ -38,13 +42,11 @@
     if (curSelection == null || curKey == null || object == null) {
       return false;
     }
-    ProvidesKey<T> keyProvider = getKeyProvider();
-    Object newKey = keyProvider.getKey(object);
-    return curKey.equals(newKey);
+    return curKey.equals(getKey(object));
   }
 
   public void setSelected(T object, boolean selected) {
-    Object key = getKeyProvider().getKey(object);
+    Object key = getKey(object);
     if (selected) {
       curSelection = object;
       curKey = key;
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java b/user/src/com/google/gwt/view/client/TreeViewModel.java
similarity index 81%
rename from bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
rename to user/src/com/google/gwt/view/client/TreeViewModel.java
index 85df9db..9349acc 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
+++ b/user/src/com/google/gwt/view/client/TreeViewModel.java
@@ -13,18 +13,17 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.bikeshed.tree.client;
+package com.google.gwt.view.client;
 
-import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.SingleSelectionModel;
+import com.google.gwt.cell.client.Cell;
+import com.google.gwt.cell.client.ValueUpdater;
 
 /**
  * A model of a tree.
+ * 
+ * <p>
+ * Note: This class is new and its interface subject to change.
+ * </p>
  */
 public interface TreeViewModel {
 
@@ -33,10 +32,10 @@
    */
   class DefaultNodeInfo<T> implements NodeInfo<T> {
 
-    private Cell<T, Void> cell;
+    private Cell<T> cell;
     private AbstractListViewAdapter<T> listViewAdapter;
     private SelectionModel<? super T> selectionModel;
-    private ValueUpdater<T, Void> valueUpdater;
+    private ValueUpdater<T> valueUpdater;
     private ListView<T> view;
 
     /**
@@ -46,8 +45,7 @@
      *          child values
      * @param cell the {@link Cell} used to render the child values
      */
-    public DefaultNodeInfo(AbstractListViewAdapter<T> adapter,
-        Cell<T, Void> cell) {
+    public DefaultNodeInfo(AbstractListViewAdapter<T> adapter, Cell<T> cell) {
       this(adapter, cell, new SingleSelectionModel<T>(), null);
     }
 
@@ -61,15 +59,15 @@
      * @param valueUpdater the {@link ValueUpdater}
      */
     public DefaultNodeInfo(AbstractListViewAdapter<T> adapter,
-        final Cell<T, Void> cell, SelectionModel<? super T> selectionModel,
-        final ValueUpdater<T, Void> valueUpdater) {
+        final Cell<T> cell, SelectionModel<? super T> selectionModel,
+        final ValueUpdater<T> valueUpdater) {
       this.listViewAdapter = adapter;
       this.cell = cell;
       this.selectionModel = selectionModel;
       this.valueUpdater = valueUpdater;
     }
 
-    public Cell<T, Void> getCell() {
+    public Cell<T> getCell() {
       return cell;
     }
 
@@ -81,7 +79,7 @@
       return selectionModel;
     }
 
-    public ValueUpdater<T, Void> getValueUpdater() {
+    public ValueUpdater<T> getValueUpdater() {
       return valueUpdater;
     }
 
@@ -105,7 +103,7 @@
      * 
      * @return the {@link Cell}
      */
-    Cell<T, Void> getCell();
+    Cell<T> getCell();
 
     /**
      * Return the key provider for children of this node.
@@ -129,7 +127,7 @@
      * 
      * @return the value updater
      */
-    ValueUpdater<T, Void> getValueUpdater();
+    ValueUpdater<T> getValueUpdater();
 
     /**
      * Set the view that is listening to this {@link NodeInfo}. The
diff --git a/user/test/com/google/gwt/uibinder/test/client/Specific.java b/user/test/com/google/gwt/uibinder/test/client/Specific.java
index 705d748..cdca3c0 100644
--- a/user/test/com/google/gwt/uibinder/test/client/Specific.java
+++ b/user/test/com/google/gwt/uibinder/test/client/Specific.java
@@ -18,8 +18,8 @@
 /**
  * A specific implementation of {@link Abstract}, used to test generics in
  * UiBinder. See {@link ParameterizedWidget}.
- *
- * @param <T>
+ * 
+ * @param <T> a param type
  */
 public class Specific<T> extends Abstract<T> {
 }