<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>LICENSE</filename>
    </added>
    <added>
      <filename>README</filename>
    </added>
    <added>
      <filename>app/controllers/dashboard_controller.rb</filename>
    </added>
    <added>
      <filename>app/controllers/error_controller.rb</filename>
    </added>
    <added>
      <filename>app/controllers/iterations_controller.rb</filename>
    </added>
    <added>
      <filename>app/controllers/milestones_controller.rb</filename>
    </added>
    <added>
      <filename>app/controllers/session_controller.rb</filename>
    </added>
    <added>
      <filename>app/controllers/stories_controller.rb</filename>
    </added>
    <added>
      <filename>app/helpers/collection_table_helper.rb</filename>
    </added>
    <added>
      <filename>app/helpers/dashboard_helper.rb</filename>
    </added>
    <added>
      <filename>app/helpers/error_helper.rb</filename>
    </added>
    <added>
      <filename>app/helpers/iterations_helper.rb</filename>
    </added>
    <added>
      <filename>app/helpers/milestones_helper.rb</filename>
    </added>
    <added>
      <filename>app/helpers/session_helper.rb</filename>
    </added>
    <added>
      <filename>app/helpers/sort_helper.rb</filename>
    </added>
    <added>
      <filename>app/helpers/stories_helper.rb</filename>
    </added>
    <added>
      <filename>app/models/iteration.rb</filename>
    </added>
    <added>
      <filename>app/models/story.rb</filename>
    </added>
    <added>
      <filename>app/views/dashboard/index.rhtml</filename>
    </added>
    <added>
      <filename>app/views/dashboard/index_no_projects.rhtml</filename>
    </added>
    <added>
      <filename>app/views/dashboard/project.rhtml</filename>
    </added>
    <added>
      <filename>app/views/error/index.rhtml</filename>
    </added>
    <added>
      <filename>app/views/error/popup.rhtml</filename>
    </added>
    <added>
      <filename>app/views/iterations/_iteration_form.rhtml</filename>
    </added>
    <added>
      <filename>app/views/iterations/edit.rhtml</filename>
    </added>
    <added>
      <filename>app/views/iterations/index.rhtml</filename>
    </added>
    <added>
      <filename>app/views/iterations/new.rhtml</filename>
    </added>
    <added>
      <filename>app/views/iterations/select_stories.rhtml</filename>
    </added>
    <added>
      <filename>app/views/iterations/show.rhtml</filename>
    </added>
    <added>
      <filename>app/views/layouts/main.rhtml</filename>
    </added>
    <added>
      <filename>app/views/layouts/popup.rhtml</filename>
    </added>
    <added>
      <filename>app/views/layouts/refresh_parent_close_popup.rhtml</filename>
    </added>
    <added>
      <filename>app/views/milestones/_list.rhtml</filename>
    </added>
    <added>
      <filename>app/views/milestones/_milestone_form.rhtml</filename>
    </added>
    <added>
      <filename>app/views/milestones/_milestones_calendar.rhtml</filename>
    </added>
    <added>
      <filename>app/views/milestones/edit.rhtml</filename>
    </added>
    <added>
      <filename>app/views/milestones/index.rhtml</filename>
    </added>
    <added>
      <filename>app/views/milestones/new.rhtml</filename>
    </added>
    <added>
      <filename>app/views/milestones/show.rhtml</filename>
    </added>
    <added>
      <filename>app/views/projects/_my_projects_list.rhtml</filename>
    </added>
    <added>
      <filename>app/views/projects/_project_form.rhtml</filename>
    </added>
    <added>
      <filename>app/views/projects/add_users.rhtml</filename>
    </added>
    <added>
      <filename>app/views/projects/index.rhtml</filename>
    </added>
    <added>
      <filename>app/views/session/login.rhtml</filename>
    </added>
    <added>
      <filename>app/views/stories/_story_form.rhtml</filename>
    </added>
    <added>
      <filename>app/views/stories/edit.rhtml</filename>
    </added>
    <added>
      <filename>app/views/stories/index.rhtml</filename>
    </added>
    <added>
      <filename>app/views/stories/new.rhtml</filename>
    </added>
    <added>
      <filename>app/views/stories/show.rhtml</filename>
    </added>
    <added>
      <filename>app/views/users/_user_form.rhtml</filename>
    </added>
    <added>
      <filename>app/views/users/index.rhtml</filename>
    </added>
    <added>
      <filename>app/views/users/project.rhtml</filename>
    </added>
    <added>
      <filename>db/migrate/1_fix_stories_table_to_use_integer_fields_not_boolean.rb</filename>
    </added>
    <added>
      <filename>db/schema.rb</filename>
    </added>
    <added>
      <filename>doc/README_FOR_APP</filename>
    </added>
    <added>
      <filename>lib/tasks/test.rake</filename>
    </added>
    <added>
      <filename>public/images/admin_dot.gif</filename>
    </added>
    <added>
      <filename>public/javascripts/xmlhttprequest.js</filename>
    </added>
    <added>
      <filename>script/about</filename>
    </added>
    <added>
      <filename>script/benchmarker</filename>
    </added>
    <added>
      <filename>script/console_sandbox</filename>
    </added>
    <added>
      <filename>script/console_sandbox.rb</filename>
    </added>
    <added>
      <filename>script/create_admin</filename>
    </added>
    <added>
      <filename>script/plugin</filename>
    </added>
    <added>
      <filename>script/profiler</filename>
    </added>
    <added>
      <filename>test/functional/dashboard_controller_test.rb</filename>
    </added>
    <added>
      <filename>test/functional/error_controller_test.rb</filename>
    </added>
    <added>
      <filename>test/functional/iterations_controller_test.rb</filename>
    </added>
    <added>
      <filename>test/functional/milestones_controller_test.rb</filename>
    </added>
    <added>
      <filename>test/functional/session_controller_test.rb</filename>
    </added>
    <added>
      <filename>test/functional/stories_controller_test.rb</filename>
    </added>
    <added>
      <filename>test/unit/collection_table_helper_test.rb</filename>
    </added>
    <added>
      <filename>test/unit/iteration_test.rb</filename>
    </added>
    <added>
      <filename>test/unit/story_test.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,46 +1,137 @@
-# The filters added to this controller will be run for all controllers in the 
-# application.  Likewise will all the methods added be available for all 
-# controllers.
-class ApplicationController &lt; ActionController::Base
-  # The currently logged-in user's account object (instance of User)
-  # Returns nil if no user is logged in, set to nil to 'log out' the user
-  attr_accessor :current_user
-  
-  # Method used as a before_filter to restrict access to certain actions.
-  def require_admin
-    unless current_user.admin?
-      flash[:error] = &quot;You must be logged in as an administrator to perform&quot;+
-                      &quot; this action&quot;
-      redirect_to :controller =&gt; 'users', :action =&gt; 'no_admin'
-      return false
-    end
-  end
-  
-  # Checks if the user is logged in with a valid account
-  def check_authentication
-    if current_user.nil?
-      session[:return_to] = request.request_uri
-      flash[:status] = &quot;Please log in, and we'll send you right along.&quot;
-      redirect_to :controller =&gt; 'users', :action =&gt; 'login'
-      return false
-    end
-  end
-
-  # See documentation for #current_user accessor
-  def current_user #:nodoc:
-    unless session[ :current_user_id ].nil?
-      @current_user ||= User.find session[ :current_user_id ]
-    end
-    @current_user
-  end
-
-  # See documentation for #current_user accessor
-  def current_user=( user ) #:nodoc:
-    @current_user = user
-    if user.nil?
-      session[ :current_user_id ] = nil
-    else
-      session[ :current_user_id ] = user.id
-    end
-  end
-end
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
+
+# The filters added to this controller will be run for all controllers in the application.
+# Likewise will all the methods added be available for all controllers.
+class ApplicationController &lt; ActionController::Base
+  model :user
+  model :project
+  model :iteration
+  model :story
+  model :milestone
+
+  helper :sort
+  helper :collection_table
+  
+  layout :choose_layout
+  before_filter :check_authentication
+  before_filter :set_selected_project
+  before_filter :require_team_membership
+  
+  protected
+
+  # Used as a before_filter to instantiate the @project instance variable based
+  # on the 'project_id' request parameter. This ensures that @project is always
+  # available when performing actions that should occur within the context of a
+  # single project.
+  def set_selected_project
+    if @params['project_id']
+      @project = Project.find(@params['project_id'])
+    else
+      @project = nil
+    end
+  end
+  
+  # Used as a before_filter to ensure that the 'current_user' session variable
+  # contians a valid User (or descendent class) object. Otherwise the user is
+  # redirected to the login page. If redirected, the 'return-to' session
+  # variable will contain the path to the page the user was originally trying to
+  # access.
+  def check_authentication
+    unless @session[:current_user].kind_of?(User) or
+      self.class == SessionController
+
+      @session[:return_to] = @request.request_uri
+      flash[:status] = &quot;Please log in, and we'll send you right along.&quot;
+      redirect_to :controller =&gt; 'session', :action =&gt; 'login'
+      return false
+    end
+  end
+
+  # This method can be used as a before_filter for actions which should require
+  # admin privileges to perform. If the user tries to perform an action which
+  # triggers this filter, and they do not have admin privileges, they will be
+  # redirected to the error page with an error message saying that they must log
+  # in as an administrator to perform the requested action.
+  def require_admin_privileges
+    unless @session[:current_user].admin?
+      flash[:error] = &quot;You must be logged in as an administrator to perform &quot; +
+                      &quot;the requested action.&quot;
+      redirect_to :controller =&gt; 'error', :action =&gt; 'index'
+      return false
+    end
+  end
+
+  # Used as a before_filter to ensure that the currently logged in user is
+  # allowed to access the current project by determining whether he is an
+  # administrator or if he is on the project team.
+  def require_team_membership
+    if @project and !@session[:current_user].admin?
+      unless User.find(@session[:current_user].id).projects.include?(@project)
+        flash[:error] = 'You do not have permission to access the project, ' +
+                        'because you are not part of the project team.'
+        redirect_to :controller =&gt; 'error', :action =&gt; 'index'
+        return false
+      end
+    end
+  end
+
+  # Used in the controller class definitions to specify which actions should be
+  # rendered using the popup layout.
+  def self.popups(*popup_actions)
+    @@popups ||= {}
+    @@popups[controller_name] ||= []
+    popup_actions.each do |a|
+      @@popups[controller_name] &lt;&lt; a.to_sym
+    end
+  end
+
+  # Chooses whether to use the regular page layout or the popup page layout
+  # depending on whether the action is listed as one of the controller's popup
+  # views.
+  def choose_layout
+    @@popups ||= {}
+    @@popups[controller_name] ||= []
+    if @@popups[controller_name].include?(action_name.to_sym)
+      'layouts/popup'
+    else
+      'layouts/main'
+    end
+  end
+
+  # Used as a before_filter to ensure that a project is selected. If no project
+  # is selected, the user is sent to an error page.
+  def require_current_project
+    unless @project
+      @@popups ||= {}
+      @@popups[controller_name] ||= []
+      if @@popups[controller_name].include? action_name.to_sym
+        redirect_to :controller =&gt; 'error', :action =&gt; 'popup'
+      else
+        redirect_to :controller =&gt; 'error', :action =&gt; 'index'
+      end
+      flash[:error] = &quot;You attempted to access a view that requires a &quot; +
+                      &quot;project to be selected, but no project id was set in &quot; +
+                      &quot;your request.&quot;
+      return false
+    end
+  end
+end
+
+</diff>
      <filename>app/controllers/application.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,70 +1,155 @@
-class ProjectsController &lt; ApplicationController
-  before_filter :check_authentication
-  before_filter :require_admin, :except =&gt; [:index, :list, :show, :team]
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
 
-  def index
-    list
-    render :action =&gt; 'list'
-  end
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
 
-  def list
-    @project_pages, @projects = paginate :project, :per_page =&gt; 10
-    
-  end
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
 
-  def show
-    @project = Project.find(params[:id])
-  end
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
 
+# All actions on this controller require the user to have administrative
+# privileges.
+class ProjectsController &lt; ApplicationController
+  model :project
+  before_filter :require_admin_privileges, :only =&gt; [ :new, :create, :add_users,
+                                                     :update_users, :edit,
+                                                     :update, :remove_user,
+                                                     :delete, :index ]
+  popups :new, :create, :add_users, :update_users, :edit, :update
+
+  # Lists all of the projects that exist on the system.
+  def index
+    @page_title = &quot;Projects&quot;
+    @projects = Project.find_all(nil, 'name ASC')
+  end
+  
+  # Displays a form for creating a new project.
   def new
-    @project = Project.new
+    @page_title = &quot;New Project&quot;
+    if @project = @session[:new_project]
+      @session[:new_project] = nil
+    else
+      @project = Project.new
+    end
   end
 
+  # Creates a new project based on the information submitted from the #new
+  # action.
   def create
-    @project = Project.new(params[:project])
-    if @project.save
-      flash[:notice] = 'Project was successfully created.'
-      redirect_to :action =&gt; 'list'
+    project = Project.new(@params['project'])
+    if project.valid?
+      project.save
+      if @params['add_me'] == '1'
+        @session[:current_user].projects &lt;&lt; project
+      end
+      flash[:status] = &quot;New project \&quot;#{project.name}\&quot; has been created.&quot;
+      render 'layouts/refresh_parent_close_popup'
     else
-      render :action =&gt; 'new'
+      @session[:new_project] = project
+      redirect_to :controller =&gt; 'projects', :action =&gt; 'new'
     end
   end
 
-  def edit
-    @project = Project.find(params[:id])
+  # Displays a form which allows existing user accounts to be added to the
+  # project team. Only users who do not already belong to the team will be
+  # displayed.
+  def add_users
+    @page_title = &quot;Add Users to Project Team&quot;
+    @available_users = User.find_all(nil,
+                               'last_name ASC, first_name ASC').select do |usr|
+      !usr.projects.include?(@project)
+    end
   end
 
-  def update
-    @project = Project.find(params[:id])
-    if @project.update_attributes(params[:project])
-      flash[:notice] = 'Project was successfully updated.'
-      redirect_to :action =&gt; 'show', :id =&gt; @project
-    else
-      render :action =&gt; 'edit'
+  # Adds the users identified by their id's in the 'selected_users' request
+  # parameter to the project.
+  def update_users
+    @params['selected_users'] ||= []
+    users_added = []
+    users_not_added = []
+    @params['selected_users'].each do |uid|
+      uid = uid.to_i
+      user = User.find(uid)
+      if user.valid?
+        @project.users &lt;&lt; user
+        users_added &lt;&lt; user.full_name
+      else
+        users_not_added &lt;&lt; user.full_name
+      end
+    end
+    @project.save
+    if users_added.size &gt; 0
+      flash[:status] = &quot;The following users were added to the project: &quot; +
+                        users_added.join(', ')
     end
+    if users_not_added.size &gt; 0
+      flash[:error] = &quot;The following users could not be added to the &quot; +
+                       &quot;project, because there is a problem with their &quot; +
+                       &quot;account: #{users_not_added.join(', ')}&quot;
+    end
+    render 'layouts/refresh_parent_close_popup'
   end
 
-  def destroy
-    Project.find(params[:id]).destroy
-    redirect_to :action =&gt; 'list'
+  # Removes the user identified by the 'id' request parameter from the project.
+  def remove_user
+    user = User.find(@params['id'])
+    @project.users.delete(user)
+    flash[:status] = &quot;#{user.full_name} has been removed from the project.&quot;
+    redirect_to :controller =&gt; 'users', :action =&gt; 'index',
+                :project_id =&gt; @project.id
   end
-  
-  def team
-    @project = Project.find(params[:id])  
-    @users = @project.users
+
+  # Deletes the project identified by the 'id' request parameter form the
+  # system.
+  def delete
+    project = Project.find(@params['id'])
+    project.destroy
+    flash[:status] = &quot;#{project.name} has been deleted.&quot;
+    redirect_to :controller =&gt; 'projects', :action =&gt; 'index'
   end
-  
-  def remove_user
-    @project = Project.find(params[:id])  
-    @project.remove_users(User.find(params[:user_id]))
-    redirect_to :action =&gt; 'team', :id =&gt; @project.id
-    flash[:notice] = 'User was successfully removed from the project.'    
+
+  # Displays a form to edit the information for the project identified by the
+  # 'id' request parameter.
+  def edit
+    if @project = @session[:edit_project]
+      @session[:edit_project] = nil
+    else
+      @project = Project.find(@params['id'])
+    end
+    @page_title = &quot;Edit Project&quot;
   end
 
-### Implemented before the change to the story card.  Currently working on
-###  -- Eric 
-#  def add_user
-#    @project = Project.find(params[:id])
-#  end 
+  # Updates the project identified by the 'id' request parameter with the
+  # information submitted from the #edit action.
+  def update
+    project = Project.find(@params['id'])
+    project.attributes = @params['project']
+    if project.valid?
+      project.save
+      flash[:status] = &quot;Project \&quot;#{project.name}\&quot; has been updated.&quot;
+      render 'layouts/refresh_parent_close_popup'
+    else
+      @session[:edit_project] = project
+      redirect_to :controller =&gt; 'projects', :action =&gt; 'edit',
+                  :id =&gt; project.id
+    end
+  end
 
+  # Renders an ordered list of projects (with links) to which the current user
+  # belongs
+  def my_projects_list
+    @projects = @session[:current_user].projects
+    render_partial 'my_projects_list'
+  end
 end
+</diff>
      <filename>app/controllers/projects_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,95 +1,128 @@
-class UsersController &lt; ApplicationController
-  before_filter :check_authentication, :except =&gt; [:login, :authenticate]
-  before_filter :require_admin, :except =&gt; [:login, :logout, :authenticate,
-    :no_admin]
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
 
-  def index
-    list
-    render :action =&gt; 'list'
-  end
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
 
-  def list
-    @user_pages, @users = paginate :user, :per_page =&gt; 10
-  end
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
 
-  def show
-    @user = User.find(params[:id])
-  end
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
 
-  def new
-    @user = User.new
-  end
+class UsersController &lt; ApplicationController
+  before_filter :require_admin_privileges, :except =&gt; [:index, :project]
+  popups :new, :create, :edit, :update
 
-  def create
-    @user = User.new(params[:user])
-    if @user.save
-      flash[:notice] = 'User was successfully created.'
-      redirect_to :action =&gt; 'list'
+  # If the 'project_id' request parameter is set, this will display the
+  # project's team members. Otherwise, it shows all users on the system.
+  def index
+    if @project
+      @page_title = &quot;Project Team&quot;
+      render 'users/project'
     else
-      render :action =&gt; 'new'
+      @page_title = &quot;System Users&quot;
+      @users = User.find_all(nil, 'last_name ASC, first_name ASC')
     end
   end
 
-  def edit
-    @user = User.find(params[:id])
-  end
-
-  def update
-    @user = User.find(params[:id])
-    if @user.update_attributes(params[:user])
-      flash[:notice] = 'User was successfully updated.'
-      redirect_to :action =&gt; 'show', :id =&gt; @user
+  # Displays the form to create a new user account.
+  def new
+    @page_title = &quot;New User&quot;
+    if @session[:new_user]
+      @user = @session[:new_user]
+      @session[:new_user] = nil
     else
-      render :action =&gt; 'edit'
+      @user = User.new
     end
   end
 
-  def destroy
-    @user = User.find(@params[:id])
-    if session[ :current_user_id ] == @user.id
-      flash[ :error ] = 'You may not delete your own account'
-    else
-      User.find(@params[:id]).destroy
-      flash[ :notice ] = 'User has been deleted'
+  # Displays the form to edit a user account.
+  def edit
+    @user = User.find(@params['id'])
+    @page_title = @user.full_name
+    if @session[:edit_user]
+      @user = @session[:edit_user]
+      @session[:edit_user] = nil
     end
-    redirect_to :action =&gt; 'list'
   end
 
-  def login
-  end
+  # Creates a new user account based o information submitted from the #new
+  # action.
+  def create
+    user = User.new(@params['user'])
+    if user.valid?
+      user.save
+      flash[:status] = &quot;User account for #{user.full_name} has been created.&quot;
 
-  def authenticate
-    if self.current_user = User.authenticate(@params[ :login ],
-                                             @params[ :password])
-      if session[:return_to]
-        redirect_to_path session[:return_to]
-        session[:return_to] = nil
-      else
-        redirect_to :controller =&gt; 'main', :action =&gt; 'dashboard'
+      if @project
+        @project.users &lt;&lt; user
+        flash[:status] = &quot;User account for #{user.full_name} has been &quot; +
+                          &quot;created and added to the project team.&quot;
       end
+      render 'layouts/refresh_parent_close_popup'
     else
-      flash[ :error ] = 'You entered an invalid username and/or password.'
-      redirect_to :controller =&gt; 'users', :action =&gt; 'login'
+      @session[:new_user] = user
+      if @project
+        redirect_to(:controller =&gt; 'users', :action =&gt; 'new',
+                    :project_id =&gt; @project.id)
+      else
+        redirect_to :controller =&gt; 'users', :action =&gt; 'new'
+      end
     end
   end
 
-  def logout
-    self.current_user = nil
-    redirect_to :controller =&gt; 'users', :action =&gt; 'login'
-    flash[ :notice ] = 'You have been logged out'    
+  # Updates a user account with the information submitted from the #edit action.
+  def update
+    user = User.find(@params['id'])
+    original_password = user.password
+    user.attributes = @params['user']
+    if @params['user']['password'] == ''
+      user.password = user.password_confirmation = original_password
+    end
+    if user == @session[:current_user] and !user.admin? and
+      @session[:current_user].admin?
+
+      user.admin = 1
+      flash[:error] = &quot;You can not remove admin privileges from yourself.&quot;
+    end
+    if user.valid?
+      user.save
+      flash[:status] = &quot;User account for #{user.full_name} has been updated.&quot;
+      render 'layouts/refresh_parent_close_popup'
+    else
+      @session[:edit_user] = user
+      redirect_to(:controller =&gt; 'users', :action =&gt; 'edit', :id =&gt; user.id)
+    end
   end
 
-  def no_admin
+  # Deletes the user account identified by the 'id' request parameter.
+  def delete
+    user = User.find(@params['id'])
+    if user == @session[:current_user]
+      flash[:error] = &quot;You can not delete your own account.&quot;
+    else
+      user.destroy
+      flash[:status] = &quot;User account for #{user.full_name} has been deleted.&quot;
+    end
+    redirect_to :controller =&gt; 'users', :action =&gt; 'index'
   end
 
   protected
 
-  # Overrides the ApplicationController#require_admin method so that
+  # Overrides the ApplicationController#require_admin_privileges method so that
   # a non-admin user can edit their own account details.
-  def require_admin
+  def require_admin_privileges
     case action_name
-    when 'edit','update', 'show'
-      super unless @params['id'].to_i == self.current_user.id
+    when 'edit','update'
+      super if @params['id'].to_i != @session[:current_user].id
     else
       super
     end</diff>
      <filename>app/controllers/users_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,165 @@
-# Methods added to this helper will be available to all templates in the application.
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
+
+# The methods added to this helper will be available to all templates in the application.
 module ApplicationHelper
+  VERSION = '1.4.0'
+  
+  # Used to determine if the currently logged in user has administrative
+  # privileges
+  def is_admin?
+    @session[:current_user].admin?
+  end
+
+  # Returns an array of projects other than the currently active project which
+  # the curren tuser has access to
+  def other_projects
+    unless @other_projects_cache
+      can_access = @session[:current_user].projects
+      @other_projects_cache = can_access.select { |p| p != @project }
+    end
+    @other_projects_cache
+  end
+
+  # Returns the title to use for the page's html title tag.
+  def page_title
+    if @project
+      &quot;#{@project.name} &amp;raquo; #{@page_title}&quot;
+    else
+      @page_title
+    end
+  end
+
+  # Returns the text of the top menu markup (HTML).
+  def top_menu
+    xml = Builder::XmlMarkup.new
+    xml.ul(:id =&gt; 'MainMenu') do
+      xml.li do
+        if @project
+          xml &lt;&lt; main_menu_link('Dashboard', :controller =&gt; 'dashboard',
+                                :action =&gt; 'index',
+                                :project_id =&gt; @project.id)
+        else
+          xml &lt;&lt; main_menu_link('Overview', :controller =&gt; 'dashboard',
+                                :action =&gt; 'index')
+        end
+      end
+      if is_admin?
+        xml.li(:class =&gt; 'right') do
+          xml &lt;&lt; main_menu_link('Users', :controller =&gt; 'users',
+                                :action =&gt; 'index') {
+            @project.nil? and controller.controller_name == 'users'
+          }
+        end
+        xml.li(:class =&gt; 'right') do
+          xml &lt;&lt; main_menu_link('Projects', :controller =&gt; 'projects',
+                                :action =&gt; 'index')
+        end
+      end
+    end
+  end
+
+  # Returns the text of the main menu markup (HTML).
+  def main_menu
+    xml = Builder::XmlMarkup.new
+    xml.ul(:id =&gt; 'MainMenu') do
+      if @project
+        xml.li do
+          xml &lt;&lt; main_menu_link('Dashboard', :controller =&gt; 'dashboard',
+                                :action =&gt; 'index',
+                                :project_id =&gt; @project.id)
+        end
+        xml.li do
+          xml &lt;&lt; main_menu_link('Iterations', :controller =&gt; 'iterations',
+                                :action =&gt; 'index',
+                                :project_id =&gt; @project.id)
+        end
+        xml.li do
+          xml &lt;&lt; main_menu_link('Backlog', :controller =&gt; 'stories',
+                                :action =&gt; 'index',
+                                :project_id =&gt; @project.id)
+        end
+        xml.li do
+          xml &lt;&lt; main_menu_link('Milestones', :controller =&gt; 'milestones',
+                                :action =&gt; 'index',
+                                :project_id =&gt; @project.id)
+        end
+        xml.li do
+          xml &lt;&lt; main_menu_link('Team', :controller =&gt; 'users',
+                                :action =&gt; 'index',
+                                :project_id =&gt; @project.id)
+        end
+      end
+    end
+  end
+
+  # Returns a link tag with the specified +title+ for use in the site's main
+  # menu. +options+ is a hash of the same format as the +options+ hash passed to
+  # the ActionView::Helpers::UrlHelper#url_for method. If a block is given, and
+  # the block returns an non-false value, the 'current' CSS class will be set on
+  # the link. If no block is given, but the controller being linked to is the
+  # same as the current controller, the CSS class will be set to current.
+  # Otherwise, no CSS class is set.
+  def main_menu_link(title, options)
+    if (block_given? and yield) or
+      (!block_given? and controller.controller_name == options[:controller])
+      
+      html_options = { 'class' =&gt; 'current' }
+    else
+      html_options = {}
+    end
+    link_to(title,options,html_options)
+  end
+
+  # Returns a link tag with the specified +title+ that will invoke a popup
+  # window with the supplied +window_title+ and using the javascript window
+  # options passed in +window_options+. +options+ is a hash identical to that
+  # used by ActionView::Helpers::UrlHelper#url_for. i.e.:
+  #   popup_link('Click Me', 'click_me', 'width=400,height=400,scrollbars',
+  #              :controller =&gt; 'foo', :action =&gt; 'index')
+  # would produce something like:
+  #   &lt;a href=&quot;/foo/index&quot; onclick=&quot;window.open('/foo/index','click_me',
+  #     'width=400,height=400,scrollbars');return false;&quot;&gt;Click Me&lt;/a&gt;
+  def popup_link(title,window_title,window_options,options)
+    xml = Builder::XmlMarkup.new
+    link = url_for(options)
+    xml.a(title, :href =&gt; link,
+          :onclick =&gt; &quot;window.open('#{link}','#{window_title}',&quot; +
+                      &quot;'#{window_options}');return false;&quot;)
+  end
+
+  # Displays a textual representation of +date+ in the long format (i.e.:
+  # &quot;Tuesday March 22, 2005&quot;)
+  def long_date(date)
+    &quot;#{Date::DAYNAMES[date.wday]} #{Date::MONTHNAMES[date.mon]} #{date.day}, &quot; +
+    &quot;#{date.year}&quot;
+  end
+
+  # Displays a textual representation of +date+ in the abbreviated format (i.e.:
+  # &quot;Tue Mar 22, 05&quot;)
+  def short_date(date)
+    &quot;#{Date::ABBR_DAYNAMES[date.wday]} #{Date::ABBR_MONTHNAMES[date.mon]} &quot; +
+    &quot;#{date.day}, '#{date.year.to_s.slice(-2,2)}&quot;
+  end
+
+  # Displays the numeric representation of +date+ (i.e.: &quot;3/22/2005&quot;)
+  def numeric_date(date)
+    &quot;#{date.mon}/#{date.day}/#{date.year}&quot;
+  end
 end</diff>
      <filename>app/helpers/application_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,2 +1,21 @@
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
+
 module ProjectsHelper
 end</diff>
      <filename>app/helpers/projects_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,2 +1,21 @@
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
+
 module UsersHelper
 end</diff>
      <filename>app/helpers/users_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,49 @@
-class Milestone &lt; ActiveRecord::Base
-  belongs_to :project
-end
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
+
+# A Milestone represents a particular event or date that the project team should
+# be aware of but is not a work item or attached to any specific Iteration.
+# Examples might include: the planned date for a release, an important meeting,
+# a conference, etc.
+#
+# Milestone has the following associations:
+#   belongs_to :project
+#
+# And the following validations are performed on the data:
+#   validates_presence_of :name, :date
+#   validates_length_of :name, :maximum =&gt; 100
+#
+class Milestone &lt; ActiveRecord::Base
+  belongs_to :project
+  validates_presence_of :name, :date
+  validates_length_of :name, :maximum =&gt; 100
+  
+  def future?
+    date &gt;= Date.today
+  end
+  
+  def recent?
+    date &lt; Date.today &amp;&amp; date &gt; Date.today - 15
+  end
+  
+  def past?
+    date &lt; Date.today
+  end
+end
+</diff>
      <filename>app/models/milestone.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,116 @@
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
+
+# A Project is the hub which connects User, Milestone, Iteration, and Story
+# objects (and any other information in the system). With the exception of User
+# objects, everything else _must_ be associated with a project; and when a
+# project is deleted, everything associated with the project (except the User
+# accounts) should be deleted as well.
+#
+# Project has the following associations:
+#   has_many :iterations, :order =&gt; 'start_date ASC', :dependent =&gt; true
+#   has_many :milestones, :order =&gt; 'date ASC', :dependent =&gt; true
+#   has_many :stories, :dependent =&gt; true
+#   has_many :backlog, :class_name =&gt; 'Story',
+#            :conditions =&gt; &quot;iteration_id IS NULL&quot;
+#   has_many :future_iterations, :class_name =&gt; 'Iteration',
+#            :order =&gt; 'start_date ASC',
+#            :conditions =&gt; &quot;start_date &gt; CURDATE()&quot;
+#   has_many :past_iterations, :class_name =&gt; 'Iteration',
+#            :order =&gt; 'start_date DESC',
+#            :conditions =&gt; &quot;DATE_ADD(start_date, INTERVAL (length - 1) DAY) &quot; +
+#                           &quot;&lt; CURDATE()&quot;
+#   has_one :current_iteration, :class_name =&gt; 'Iteration',
+#           :order =&gt; 'start_date DESC',
+#           :conditions =&gt; &quot;start_date &lt;= CURDATE() AND &quot; +
+#                          &quot;DATE_ADD(start_date, INTERVAL (length - 1) DAY) &quot; +
+#                          &quot;&gt;= CURDATE()&quot;
+#   has_and_belongs_to_many :users, :order =&gt; 'last_name ASC, first_name ASC'
+#
+# And the following data validations:
+#   validates_presence_of :name
+#   validates_uniqueness_of :name
+#   validates_length_of :name, :maximum =&gt; 100
+#
 class Project &lt; ActiveRecord::Base
-  has_and_belongs_to_many :users
-  has_many :story_cards
-  has_many :milestones
-  
+  has_many :iterations, :order =&gt; 'start_date ASC', :dependent =&gt; true
+  has_many :milestones, :order =&gt; 'date ASC', :dependent =&gt; true
+  has_many :stories, :dependent =&gt; true
+  has_many :backlog, :class_name =&gt; 'Story',
+           :conditions =&gt; &quot;iteration_id IS NULL&quot;
+  has_and_belongs_to_many :users, :order =&gt; 'last_name ASC, first_name ASC'
   validates_presence_of :name
+  validates_uniqueness_of :name
+  validates_length_of :name, :maximum =&gt; 100
+
+  def past_iterations( reload = false )
+    today = Time.now
+    @past_iterations = nil if reload
+    @past_iterations ||= iterations( reload ).select do |i|
+      ( i.start_date + i.length ).to_time &lt;= today.at_midnight
+    end
+    if reload
+      @past_iterations.reverse!
+    else
+      @past_iterations
+    end
+  end
+
+  # Returns an Array of all associated iterations that start after today
+  def future_iterations( reload = false )
+    today = Time.now
+    @future_iterations = nil if reload
+    @future_iterations ||= iterations( reload ).select do |i|
+      i.start_date.to_time &gt; today
+    end
+  end
+
+  # Returns the current iteration (if there is one)
+  def current_iteration( reload = false )
+    today = Time.now
+    @current_iteration = nil if reload
+    @current_iteration ||= iterations( reload ).detect do |i|
+      ( i.start_date.to_time &lt;= today.at_midnight ) and
+      ( ( i.start_date + i.length ).to_time &gt;= today.tomorrow.at_midnight )
+    end
+  end
+
+  # Returns the last iteration to have ended or nil
+  def previous_iteration(reload = false)
+    past_iterations(reload).first
+  end
+
+  # Returns the next scheduled iteration or nil
+  def next_iteration(reload = false)
+    future_iterations(reload).first
+  end
+  
+  def future_milestones(reload = false)
+    milestones(reload).select { |m| m.future? }
+  end
+  
+  def recent_milestones(reload = false)
+    milestones(reload).reverse.select { |m| m.recent? }
+  end
+  
+  def past_milestones(reload = false)
+    milestones(reload).reverse.select { |m| m.past? }
+  end
 end
+</diff>
      <filename>app/models/project.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,27 +1,64 @@
+=begin License
+  eXPlain Project Management Tool
+  Copyright (C) 2005  John Wilger &lt;johnwilger@gmail.com&gt;
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+=end LICENSE
+
+# A User represents an account on the system. Each person that can log into the
+# system has an associated User object.
+#
+# User has the following associations:
+#   has_and_belongs_to_many :projects
+#   has_many :stories
+#
+# And the following data validations:
+#   validates_presence_of :first_name, :last_name, :email
+#   validates_uniqueness_of :username, :email
+#   validates_length_of :username, :minimum =&gt; 4
+#   validates_length_of :password, :minimum =&gt; 6
+#   validates_confirmation_of :password
+#   validates_format_of :email, :with =&gt; /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/
+#
 class User &lt; ActiveRecord::Base
   has_and_belongs_to_many :projects
-  has_many :story_cards
-  validates_presence_of :name, :login, :email, :password
-  validates_uniqueness_of :login
+  has_many :stories
+
+  validates_presence_of :first_name, :last_name, :email
+  validates_uniqueness_of :username, :email
+  validates_length_of :username, :minimum =&gt; 4
+  validates_length_of :password, :minimum =&gt; 6
   validates_confirmation_of :password
-  before_destroy :do_not_destroy_last_admin
-  
-  class &lt;&lt; self
-    # Return the User instance that matches the supplied username and password,
-    # or return nil if no matching account is found
-    def authenticate( login, password )
-      unless login.nil? || password.nil?
-        find :first, :conditions =&gt; [ 'login = ? AND password = ?',
-          login, password  ]
-      end
+  validates_format_of :email, :with =&gt; /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/
+
+  LastFirst = true
+
+  # Returns the full name of the user. If the &lt;tt&gt;last_first&lt;/tt&gt; argument is
+  # set to +true+ (or the constant User::LastFirst), the name will be returned
+  # as &quot;Doe, John&quot;. Otherwise it is returned as &quot;John Doe&quot;
+  def full_name(last_first = false)
+    if last_first
+      &quot;#{last_name}, #{first_name}&quot;
+    else
+      &quot;#{first_name} #{last_name}&quot;
     end
   end
 
-  # Used as a before_destroy callback to ensure that the last admin account can
-  # not be deleted.
-  def do_not_destroy_last_admin
-    if self.admin and User.count( [ 'admin = ?', true ] ) &lt; 2
-      return false
-    end
+  # Returns the User object that matches the supplied +username+ and +password+
+  # arguments, or returns +nil+ if no match is found.
+  def self.authenticate(username, password)
+    find_by_username_and_password(username, password)
   end
 end</diff>
      <filename>app/models/user.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,9 +1,5 @@
-&lt;h1&gt;Editing project&lt;/h1&gt;
-
-&lt;%= start_form_tag :action =&gt; 'update', :id =&gt; @project %&gt;
-  &lt;%= render :partial =&gt; 'form' %&gt;
-  &lt;%= submit_tag 'Edit' %&gt;
+&lt;%= start_form_tag(:controller =&gt; 'projects', :action =&gt; 'update',
+                   :id =&gt; @project.id) %&gt;
+&lt;%= render_partial('project_form', :project =&gt; @project) %&gt;
 &lt;%= end_form_tag %&gt;
 
-&lt;%= link_to 'Show', :action =&gt; 'show', :id =&gt; @project %&gt; |
-&lt;%= link_to 'Back', :action =&gt; 'list' %&gt;</diff>
      <filename>app/views/projects/edit.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,3 @@
-&lt;h1&gt;New project&lt;/h1&gt;
-
-&lt;%= start_form_tag :action =&gt; 'create' %&gt;
-  &lt;%= render :partial =&gt; 'form' %&gt;
-  &lt;%= submit_tag &quot;Create&quot; %&gt;
+&lt;%= start_form_tag(:controller =&gt; 'projects', :action =&gt; 'create') %&gt;
+&lt;%= render_partial('project_form', :project =&gt; @project) %&gt;
 &lt;%= end_form_tag %&gt;
-
-&lt;%= link_to 'Back', :action =&gt; 'list' %&gt;</diff>
      <filename>app/views/projects/new.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,9 +1,4 @@
-&lt;h1&gt;Editing user&lt;/h1&gt;
-
-&lt;%= start_form_tag :action =&gt; 'update', :id =&gt; @user %&gt;
-  &lt;%= render :partial =&gt; 'form' %&gt;
-  &lt;%= submit_tag 'Edit' %&gt;
+&lt;%= start_form_tag(:controller =&gt; 'users', :action =&gt; 'update', :id =&gt; @user.id) %&gt;
+&lt;%= render_partial('user_form', :user =&gt; @user) %&gt;
 &lt;%= end_form_tag %&gt;
 
-&lt;%= link_to 'Show', :action =&gt; 'show', :id =&gt; @user %&gt; |
-&lt;%= link_to 'Back', :action =&gt; 'list' %&gt;</diff>
      <filename>app/views/users/edit.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,6 @@
-&lt;h1&gt;New user&lt;/h1&gt;
-
-&lt;%= start_form_tag :action =&gt; 'create' %&gt;
-  &lt;%= render :partial =&gt; 'form' %&gt;
-  &lt;%= submit_tag &quot;Create&quot; %&gt;
+&lt;%= start_form_tag(:controller =&gt; 'users', :action =&gt; 'create') %&gt;
+&lt;% if @project %&gt;
+&lt;input type=&quot;hidden&quot; name=&quot;project_id&quot; value=&quot;&lt;%= @project.id %&gt;&quot; /&gt;
+&lt;% end %&gt;
+&lt;%= render_partial('user_form', :user =&gt; @user) %&gt;
 &lt;%= end_form_tag %&gt;
-
-&lt;%= link_to 'Back', :action =&gt; 'list' %&gt;</diff>
      <filename>app/views/users/new.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -1,8 +1,10 @@
+# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
+
 unless defined?(RAILS_ROOT)
   root_path = File.join(File.dirname(__FILE__), '..')
   unless RUBY_PLATFORM =~ /mswin32/
     require 'pathname'
-    root_path = Pathname.new(root_path).cleanpath.to_s
+    root_path = Pathname.new(root_path).cleanpath(true).to_s
   end
   RAILS_ROOT = root_path
 end
@@ -14,4 +16,4 @@ else
   require 'initializer'
 end
 
-Rails::Initializer.run(:set_load_path)
\ No newline at end of file
+Rails::Initializer.run(:set_load_path)</diff>
      <filename>config/boot.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,23 +1,20 @@
 development:
   adapter: mysql
-  database: explainpmt_development
-  socket: /var/run/mysqld/mysqld.sock
+  database: rails_development
+  host: localhost
   username: root
-  password:
-  
-# Warning: The database defined as 'test' will be erased and
-# re-generated from your development database when you run 'rake'.
-# Do not set this db to the same as development or production.
+  password: 
+
 test:
   adapter: mysql
-  database: explainpmt_test
-  socket: /var/run/mysqld/mysqld.sock
+  database: rails_test
+  host: localhost
   username: root
   password:
 
 production:
   adapter: mysql
-  database: explainpmt_production
-  socket: /var/run/mysqld/mysqld.sock
+  database: rails_production
+  host: localhost
   username: root
   password: </diff>
      <filename>config/database.yml.orig</filename>
    </modified>
    <modified>
      <diff>@@ -1,18 +1,20 @@
-# Be sure to restart your webserver when you modify this file.
+# Be sure to restart your web server when you modify this file.
 
-# Uncomment below to force Rails into production mode
-# (Use only when you can't set environment variables through your web/app server)
-# ENV['RAILS_ENV'] = 'production'
+# Uncomment below to force Rails into production mode when 
+# you don't control web/app server and can't set it the proper way
+# ENV['RAILS_ENV'] ||= 'production'
 
 # Bootstrap the Rails environment, frameworks, and default configuration
 require File.join(File.dirname(__FILE__), 'boot')
 
 Rails::Initializer.run do |config|
+  # Settings in config/environments/* take precedence those specified here
+  
   # Skip frameworks you're not going to use
   # config.frameworks -= [ :action_web_service, :action_mailer ]
 
   # Add additional load paths for your own custom dirs
-  # config.load_paths += %W( #{RAILS_ROOT}/app/services )
+  # config.load_paths += %W( #{RAILS_ROOT}/extras )
 
   # Force all environments to use the same logger level 
   # (by default production uses :info, the others :debug)</diff>
      <filename>config/environment.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,5 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
 # In the development environment your application's code is reloaded on
 # every request.  This slows down response time but is perfect for development
 # since you don't have to restart the webserver when you make code changes.</diff>
      <filename>config/environments/development.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,5 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
 # The production environment is meant for finished, &quot;live&quot; apps.
 # Code is not reloaded between requests
 config.cache_classes = true</diff>
      <filename>config/environments/production.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,5 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
 # The test environment is used exclusively to run your application's
 # test suite.  You never need to work with it otherwise.  Remember that
 # your test database is &quot;scratch space&quot; for the test suite and is wiped
@@ -14,10 +16,4 @@ config.action_controller.perform_caching             = false
 # Tell ActionMailer not to deliver emails to the real world.
 # The :test delivery method accumulates sent emails in the
 # ActionMailer::Base.deliveries array.
-config.action_mailer.delivery_method = :test
-
-# Overwrite the default settings for fixtures in tests. See Fixtures 
-# for more details about these settings.
-# config.transactional_fixtures = true
-# config.instantiated_fixtures = false
-# config.pre_loaded_fixtures = false
\ No newline at end of file
+config.action_mailer.delivery_method = :test
\ No newline at end of file</diff>
      <filename>config/environments/test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,26 +1,22 @@
 ActionController::Routing::Routes.draw do |map|
-  # Add your own custom routes here.
-  # The priority is based upon order of creation: first created -&gt; highest priority.
-  
-  # Here's a sample route:
-  # map.connect 'products/:id', :controller =&gt; 'catalog', :action =&gt; 'view'
-  # Keep in mind you can assign values other than :controller and :action
+# Add your own custom routes here.
+# The priority is based upon order of creation: first created -&gt; highest priority.
 
-  # You can have the root of your site routed by hooking up '' 
-  # -- just remember to delete public/index.html.
-  map.connect '', :controller =&gt; 'main', :action =&gt; 'dashboard'
+# Here's a sample route:
+# map.connect 'products/:id', :controller =&gt; 'catalog', :action =&gt; 'view'
+# Keep in mind you can assign values other than :controller and :action
 
-  # Map 'project/:id' to the project dashboard view.
-  map.connect 'project/:id', :controller =&gt; 'main', :action =&gt; 'project_dashboard'
-  
-  # Map 'project/:id/:controller' for controllers that require
-  # a project to be selected.
-  map.connect 'project/:project_id/:controller/:action/:id'
-  
-  # Allow downloading Web Service WSDL as a file with an extension
-  # instead of a file named 'wsdl'.
-  map.connect ':controller/service.wsdl', :action =&gt; 'wsdl'
+map.connect ':controller/:action/:id', :controller =&gt; 'dashboard'
 
-  # Install the default route as the lowest priority.
-  map.connect ':controller/:action/:id'
+map.connect 'project/:project_id/stories',
+            :controller =&gt; 'stories', :action =&gt; 'index'
+            
+map.connect 'project/:project_id/stories/show_cancelled',
+            :controller =&gt; 'stories', :action =&gt; 'index', :show_cancelled =&gt; '1'
+            
+map.connect 'project/:project_id/:controller/:action/:id',
+            :controller =&gt; 'dashboard'
+
+# Install the default route as the lowest priority.
+map.connect ':controller/:action/:id'
 end</diff>
      <filename>config/routes.rb</filename>
    </modified>
    <modified>
      <diff>@@ -80,7 +80,10 @@ Autocompleter.Base.prototype = {
 
   show: function() {
     if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
-    if(!this.iefix &amp;&amp; (navigator.appVersion.indexOf('MSIE')&gt;0) &amp;&amp; (Element.getStyle(this.update, 'position')=='absolute')) {
+    if(!this.iefix &amp;&amp; 
+      (navigator.appVersion.indexOf('MSIE')&gt;0) &amp;&amp;
+      (navigator.userAgent.indexOf('Opera')&lt;0) &amp;&amp;
+      (Element.getStyle(this.update, 'position')=='absolute')) {
       new Insertion.After(this.update, 
        '&lt;iframe id=&quot;' + this.update.id + '_iefix&quot; '+
        'style=&quot;display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);&quot; ' +
@@ -184,7 +187,10 @@ Autocompleter.Base.prototype = {
         this.show();
         this.active = true;
       }
-    } else this.hide();
+    } else {
+      this.active = false;
+      this.hide();
+    }
   },
   
   markPrevious: function() {
@@ -425,6 +431,15 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
 //
 // see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
 
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+}
+
 Ajax.InPlaceEditor = Class.create();
 Ajax.InPlaceEditor.defaultHighlightColor = &quot;#FFFF99&quot;;
 Ajax.InPlaceEditor.prototype = {
@@ -490,7 +505,7 @@ Ajax.InPlaceEditor.prototype = {
       Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
     }
   },
-  enterEditMode: function() {
+  enterEditMode: function(evt) {
     if (this.saving) return;
     if (this.editing) return;
     this.editing = true;
@@ -501,11 +516,12 @@ Ajax.InPlaceEditor.prototype = {
     Element.hide(this.element);
     this.createForm();
     this.element.parentNode.insertBefore(this.form, this.element);
-    Field.focus(this.editField);
+    Field.scrollFreeActivate(this.editField);
     // stop the event to avoid a page refresh in Safari
-    if (arguments.length &gt; 1) {
-      Event.stop(arguments[0]);
+    if (evt) {
+      Event.stop(evt);
     }
+    return false;
   },
   createForm: function() {
     this.form = document.createElement(&quot;form&quot;);
@@ -705,4 +721,30 @@ Ajax.InPlaceEditor.prototype = {
       Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
     }
   }
+};
+
+// Delayed observer, like Form.Element.Observer, 
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create();
+Form.Element.DelayedObserver.prototype = {
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element); 
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
 };
\ No newline at end of file</diff>
      <filename>public/javascripts/controls.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,5 @@
 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
 // 
-// Element.Class part Copyright (c) 2005 by Rick Olson
-// 
 // See scriptaculous.js for full license.
 
 /*--------------------------------------------------------------------------*/
@@ -10,7 +8,7 @@ var Droppables = {
   drops: [],
 
   remove: function(element) {
-    this.drops = this.drops.reject(function(d) { return d.element==element });
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
   },
 
   add: function(element) {
@@ -31,6 +29,8 @@ var Droppables = {
         options._containers.push($(containment));
       }
     }
+    
+    if(options.accept) options.accept = [options.accept].flatten();
 
     Element.makePositioned(element); // fix IE
     options.element = element;
@@ -43,55 +43,50 @@ var Droppables = {
     return drop._containers.detect(function(c) { return parentNode == c });
   },
 
-  isAffected: function(pX, pY, element, drop) {
+  isAffected: function(point, element, drop) {
     return (
       (drop.element!=element) &amp;&amp;
       ((!drop._containers) ||
         this.isContained(element, drop)) &amp;&amp;
       ((!drop.accept) ||
-        (Element.Class.has_any(element, drop.accept))) &amp;&amp;
-      Position.within(drop.element, pX, pY) );
+        (Element.classNames(element).detect( 
+          function(v) { return drop.accept.include(v) } ) )) &amp;&amp;
+      Position.within(drop.element, point[0], point[1]) );
   },
 
   deactivate: function(drop) {
     if(drop.hoverclass)
-      Element.Class.remove(drop.element, drop.hoverclass);
+      Element.removeClassName(drop.element, drop.hoverclass);
     this.last_active = null;
   },
 
   activate: function(drop) {
-    if(this.last_active) this.deactivate(this.last_active);
     if(drop.hoverclass)
-      Element.Class.add(drop.element, drop.hoverclass);
+      Element.addClassName(drop.element, drop.hoverclass);
     this.last_active = drop;
   },
 
-  show: function(event, element) {
+  show: function(point, element) {
     if(!this.drops.length) return;
-    var pX = Event.pointerX(event);
-    var pY = Event.pointerY(event);
-    Position.prepare();
-
-    var i = this.drops.length-1; do {
-      var drop = this.drops[i];
-      if(this.isAffected(pX, pY, element, drop)) {
+    
+    if(this.last_active) this.deactivate(this.last_active);
+    this.drops.each( function(drop) {
+      if(Droppables.isAffected(point, element, drop)) {
         if(drop.onHover)
            drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
         if(drop.greedy) { 
-          this.activate(drop);
-          return;
+          Droppables.activate(drop);
+          throw $break;
         }
       }
-    } while (i--);
-    
-    if(this.last_active) this.deactivate(this.last_active);
+    });
   },
 
   fire: function(event, element) {
     if(!this.last_active) return;
     Position.prepare();
 
-    if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
       if (this.last_active.onDrop) 
         this.last_active.onDrop(element, this.last_active.element, event);
   },
@@ -103,15 +98,84 @@ var Droppables = {
 }
 
 var Draggables = {
+  drags: [],
   observers: [],
+  
+  register: function(draggable) {
+    if(this.drags.length == 0) {
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+      
+      Event.observe(document, &quot;mouseup&quot;, this.eventMouseUp);
+      Event.observe(document, &quot;mousemove&quot;, this.eventMouseMove);
+      Event.observe(document, &quot;keypress&quot;, this.eventKeypress);
+    }
+    this.drags.push(draggable);
+  },
+  
+  unregister: function(draggable) {
+    this.drags = this.drags.reject(function(d) { return d==draggable });
+    if(this.drags.length == 0) {
+      Event.stopObserving(document, &quot;mouseup&quot;, this.eventMouseUp);
+      Event.stopObserving(document, &quot;mousemove&quot;, this.eventMouseMove);
+      Event.stopObserving(document, &quot;keypress&quot;, this.eventKeypress);
+    }
+  },
+  
+  activate: function(draggable) {
+    window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+    this.activeDraggable = draggable;
+  },
+  
+  deactivate: function(draggbale) {
+    this.activeDraggable = null;
+  },
+  
+  updateDrag: function(event) {
+    if(!this.activeDraggable) return;
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    // Mozilla-based browsers fire successive mousemove events with
+    // the same coordinates, prevent needless redrawing (moz bug?)
+    if(this._lastPointer &amp;&amp; (this._lastPointer.inspect() == pointer.inspect())) return;
+    this._lastPointer = pointer;
+    this.activeDraggable.updateDrag(event, pointer);
+  },
+  
+  endDrag: function(event) {
+    if(!this.activeDraggable) return;
+    this._lastPointer = null;
+    this.activeDraggable.endDrag(event);
+  },
+  
+  keyPress: function(event) {
+    if(this.activeDraggable)
+      this.activeDraggable.keyPress(event);
+  },
+  
   addObserver: function(observer) {
-    this.observers.push(observer);    
+    this.observers.push(observer);
+    this._cacheObserverCallbacks();
   },
-  removeObserver: function(element) {  // element instead of obsever fixes mem leaks
+  
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
     this.observers = this.observers.reject( function(o) { return o.element==element });
+    this._cacheObserverCallbacks();
   },
-  notify: function(eventName, draggable) {  // 'onStart', 'onEnd'
-    this.observers.invoke(eventName, draggable);
+  
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
+    if(this[eventName+'Count'] &gt; 0)
+      this.observers.each( function(o) {
+        if(o[eventName]) o[eventName](eventName, draggable, event);
+      });
+  },
+  
+  _cacheObserverCallbacks: function() {
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
+      Draggables[eventName+'Count'] = Draggables.observers.select(
+        function(o) { return o[eventName]; }
+      ).length;
+    });
   }
 }
 
@@ -127,68 +191,48 @@ Draggable.prototype = {
       },
       reverteffect: function(element, top_offset, left_offset) {
         var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
-        new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
+        element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
       },
       endeffect: function(element) { 
-         new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); 
       },
       zindex: 1000,
-      revert: false
+      revert: false,
+      snap: false   // false, or xy or [x,y] or function(x,y){ return [x,y] }
     }, arguments[1] || {});
 
-    this.element      = $(element);
+    this.element = $(element);
+    
     if(options.handle &amp;&amp; (typeof options.handle == 'string'))
-      this.handle = Element.Class.childrenWith(this.element, options.handle)[0];
-      
+      this.handle = Element.childrenWithClassName(this.element, options.handle)[0];  
     if(!this.handle) this.handle = $(options.handle);
     if(!this.handle) this.handle = this.element;
 
     Element.makePositioned(this.element); // fix IE    
 
-    this.offsetX      = 0;
-    this.offsetY      = 0;
-    this.originalLeft = this.currentLeft();
-    this.originalTop  = this.currentTop();
-    this.originalX    = this.element.offsetLeft;
-    this.originalY    = this.element.offsetTop;
-
-    this.options      = options;
+    this.delta    = this.currentDelta();
+    this.options  = options;
+    this.dragging = false;   
 
-    this.active       = false;
-    this.dragging     = false;   
-
-    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
-    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
-    this.eventMouseMove = this.update.bindAsEventListener(this);
-    this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+    Event.observe(this.handle, &quot;mousedown&quot;, this.eventMouseDown);
     
-    this.registerEvents();
+    Draggables.register(this);
   },
+  
   destroy: function() {
     Event.stopObserving(this.handle, &quot;mousedown&quot;, this.eventMouseDown);
-    this.unregisterEvents();
+    Draggables.unregister(this);
   },
-  registerEvents: function() {
-    Event.observe(document, &quot;mouseup&quot;, this.eventMouseUp);
-    Event.observe(document, &quot;mousemove&quot;, this.eventMouseMove);
-    Event.observe(document, &quot;keypress&quot;, this.eventKeypress);
-    Event.observe(this.handle, &quot;mousedown&quot;, this.eventMouseDown);
-  },
-  unregisterEvents: function() {
-    //if(!this.active) return;
-    //Event.stopObserving(document, &quot;mouseup&quot;, this.eventMouseUp);
-    //Event.stopObserving(document, &quot;mousemove&quot;, this.eventMouseMove);
-    //Event.stopObserving(document, &quot;keypress&quot;, this.eventKeypress);
-  },
-  currentLeft: function() {
-    return parseInt(this.element.style.left || '0');
+  
+  currentDelta: function() {
+    return([
+      parseInt(this.element.style.left || '0'),
+      parseInt(this.element.style.top || '0')]);
   },
-  currentTop: function() {
-    return parseInt(this.element.style.top || '0')
-  },
-  startDrag: function(event) {
-    if(Event.isLeftClick(event)) {
-      
+  
+  initDrag: function(event) {
+    if(Event.isLeftClick(event)) {    
       // abort on form elements, fixes a Firefox issue
       var src = Event.element(event);
       if(src.tagName &amp;&amp; (
@@ -196,20 +240,53 @@ Draggable.prototype = {
         src.tagName=='SELECT' ||
         src.tagName=='BUTTON' ||
         src.tagName=='TEXTAREA')) return;
+        
+      if(this.element._revert) {
+        this.element._revert.cancel();
+        this.element._revert = null;
+      }
       
-      // this.registerEvents();
-      this.active = true;
       var pointer = [Event.pointerX(event), Event.pointerY(event)];
-      var offsets = Position.cumulativeOffset(this.element);
-      this.offsetX =  (pointer[0] - offsets[0]);
-      this.offsetY =  (pointer[1] - offsets[1]);
+      var pos     = Position.cumulativeOffset(this.element);
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+      
+      Draggables.activate(this);
       Event.stop(event);
     }
   },
+  
+  startDrag: function(event) {
+    this.dragging = true;
+    
+    if(this.options.zindex) {
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+      this.element.style.zIndex = this.options.zindex;
+    }
+    
+    if(this.options.ghosting) {
+      this._clone = this.element.cloneNode(true);
+      Position.absolutize(this.element);
+      this.element.parentNode.insertBefore(this._clone, this.element);
+    }
+    
+    Draggables.notify('onStart', this, event);
+    if(this.options.starteffect) this.options.starteffect(this.element);
+  },
+  
+  updateDrag: function(event, pointer) {
+    if(!this.dragging) this.startDrag(event);
+    Position.prepare();
+    Droppables.show(pointer, this.element);
+    Draggables.notify('onDrag', this, event);
+    this.draw(pointer);
+    if(this.options.change) this.options.change(this);
+    
+    // fix AppleWebKit rendering
+    if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0);
+    Event.stop(event);
+  },
+  
   finishDrag: function(event, success) {
-    // this.unregisterEvents();
-
-    this.active = false;
     this.dragging = false;
 
     if(this.options.ghosting) {
@@ -219,18 +296,17 @@ Draggable.prototype = {
     }
 
     if(success) Droppables.fire(event, this.element);
-    Draggables.notify('onEnd', this);
+    Draggables.notify('onEnd', this, event);
 
     var revert = this.options.revert;
     if(revert &amp;&amp; typeof revert == 'function') revert = revert(this.element);
-
+    
+    var d = this.currentDelta();
     if(revert &amp;&amp; this.options.reverteffect) {
       this.options.reverteffect(this.element, 
-      this.currentTop()-this.originalTop,
-      this.currentLeft()-this.originalLeft);
+        d[1]-this.delta[1], d[0]-this.delta[0]);
     } else {
-      this.originalLeft = this.currentLeft();
-      this.originalTop  = this.currentTop();
+      this.delta = d;
     }
 
     if(this.options.zindex)
@@ -239,70 +315,48 @@ Draggable.prototype = {
     if(this.options.endeffect) 
       this.options.endeffect(this.element);
 
-
+    Draggables.deactivate(this);
     Droppables.reset();
   },
+  
   keyPress: function(event) {
-    if(this.active) {
-      if(event.keyCode==Event.KEY_ESC) {
-        this.finishDrag(event, false);
-        Event.stop(event);
-      }
-    }
+    if(!event.keyCode==Event.KEY_ESC) return;
+    this.finishDrag(event, false);
+    Event.stop(event);
   },
+  
   endDrag: function(event) {
-    if(this.active &amp;&amp; this.dragging) {
-      this.finishDrag(event, true);
-      Event.stop(event);
-    }
-    this.active = false;
-    this.dragging = false;
+    if(!this.dragging) return;
+    this.finishDrag(event, true);
+    Event.stop(event);
   },
-  draw: function(event) {
-    var pointer = [Event.pointerX(event), Event.pointerY(event)];
-    var offsets = Position.cumulativeOffset(this.element);
-    offsets[0] -= this.currentLeft();
-    offsets[1] -= this.currentTop();
+  
+  draw: function(point) {
+    var pos = Position.cumulativeOffset(this.element);
+    var d = this.currentDelta();
+    pos[0] -= d[0]; pos[1] -= d[1];
+    
+    var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this));
+    
+    if(this.options.snap) {
+      if(typeof this.options.snap == 'function') {
+        p = this.options.snap(p[0],p[1]);
+      } else {
+      if(this.options.snap instanceof Array) {
+        p = p.map( function(v, i) {
+          return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
+      } else {
+        p = p.map( function(v) {
+          return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
+      }
+    }}
+    
     var style = this.element.style;
     if((!this.options.constraint) || (this.options.constraint=='horizontal'))
-      style.left = (pointer[0] - offsets[0] - this.offsetX) + &quot;px&quot;;
+      style.left = p[0] + &quot;px&quot;;
     if((!this.options.constraint) || (this.options.constraint=='vertical'))
-      style.top  = (pointer[1] - offsets[1] - this.offsetY) + &quot;px&quot;;
+      style.top  = p[1] + &quot;px&quot;;
     if(style.visibility==&quot;hidden&quot;) style.visibility = &quot;&quot;; // fix gecko rendering
-  },
-  update: function(event) {
-   if(this.active) {
-      if(!this.dragging) {
-        var style = this.element.style;
-        this.dragging = true;
-        
-        if(Element.getStyle(this.element,'position')=='') 
-          style.position = &quot;relative&quot;;
-        
-        if(this.options.zindex) {
-          this.options.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
-          style.zIndex = this.options.zindex;
-        }
-
-        if(this.options.ghosting) {
-          this._clone = this.element.cloneNode(true);
-          Position.absolutize(this.element);
-          this.element.parentNode.insertBefore(this._clone, this.element);
-        }
-
-        Draggables.notify('onStart', this);
-        if(this.options.starteffect) this.options.starteffect(this.element);
-      }
-
-      Droppables.show(event, this.element);
-      this.draw(event);
-      if(this.options.change) this.options.change(this);
-
-      // fix AppleWebKit rendering
-      if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0); 
-
-      Event.stop(event);
-   }
   }
 }
 
@@ -315,9 +369,11 @@ SortableObserver.prototype = {
     this.observer  = observer;
     this.lastValue = Sortable.serialize(this.element);
   },
+  
   onStart: function() {
     this.lastValue = Sortable.serialize(this.element);
   },
+  
   onEnd: function() {
     Sortable.unmark();
     if(this.lastValue != Sortable.serialize(this.element))
@@ -327,10 +383,12 @@ SortableObserver.prototype = {
 
 var Sortable = {
   sortables: new Array(),
+  
   options: function(element){
     element = $(element);
     return this.sortables.detect(function(s) { return s.element == element });
   },
+  
   destroy: function(element){
     element = $(element);
     this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
@@ -340,6 +398,7 @@ var Sortable = {
     });
     this.sortables = this.sortables.reject(function(s) { return s.element == element });
   },
+  
   create: function(element) {
     element = $(element);
     var options = Object.extend({ 
@@ -355,8 +414,8 @@ var Sortable = {
       hoverclass:  null,
       ghosting:    false,
       format:      null,
-      onChange:    function() {},
-      onUpdate:    function() {}
+      onChange:    Prototype.emptyFunction,
+      onUpdate:    Prototype.emptyFunction
     }, arguments[1] || {});
 
     // clear any old sortable with same element
@@ -413,7 +472,7 @@ var Sortable = {
     (this.findElements(element, options) || []).each( function(e) {
       // handles are per-draggable
       var handle = options.handle ? 
-        Element.Class.childrenWith(e, options.handle)[0] : e;    
+        Element.childrenWithClassName(e, options.handle)[0] : e;    
       options.draggables.push(
         new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
       Droppables.add(e, options_for_droppable);
@@ -433,8 +492,8 @@ var Sortable = {
     if(!element.hasChildNodes()) return null;
     var elements = [];
     $A(element.childNodes).each( function(e) {
-      if(e.tagName &amp;&amp; e.tagName==options.tag.toUpperCase() &amp;&amp;
-        (!options.only || (Element.Class.has(e, options.only))))
+      if(e.tagName &amp;&amp; e.tagName.toUpperCase()==options.tag.toUpperCase() &amp;&amp;
+        (!options.only || (Element.hasClassName(e, options.only))))
           elements.push(e);
       if(options.tree) {
         var grandchildren = this.findElements(e, options);
@@ -472,7 +531,10 @@ var Sortable = {
 
   onEmptyHover: function(element, dropon) {
     if(element.parentNode!=dropon) {
+      var oldParentNode = element.parentNode;
       dropon.appendChild(element);
+      Sortable.options(oldParentNode).onChange(element);
+      Sortable.options(dropon).onChange(element);
     }
   },
 
@@ -488,14 +550,20 @@ var Sortable = {
     if(!Sortable._marker) {
       Sortable._marker = $('dropmarker') || document.createElement('DIV');
       Element.hide(Sortable._marker);
-      Element.Class.add(Sortable._marker, 'dropmarker');
+      Element.addClassName(Sortable._marker, 'dropmarker');
       Sortable._marker.style.position = 'absolute';
       document.getElementsByTagName(&quot;body&quot;).item(0).appendChild(Sortable._marker);
     }    
     var offsets = Position.cumulativeOffset(dropon);
-    Sortable._marker.style.top  = offsets[1] + 'px';
-    if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
     Sortable._marker.style.left = offsets[0] + 'px';
+    Sortable._marker.style.top = offsets[1] + 'px';
+    
+    if(position=='after')
+      if(sortable.overlap == 'horizontal') 
+        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
+      else
+        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+    
     Element.show(Sortable._marker);
   },
 
@@ -508,7 +576,7 @@ var Sortable = {
       name: element.id,
       format: sortableOptions.format || /^[^_]*_(.*)$/
     }, arguments[1] || {});
-    return $(this.findElements(element, options) || []).collect( function(item) {
+    return $(this.findElements(element, options) || []).map( function(item) {
       return (encodeURIComponent(options.name) + &quot;[]=&quot; + 
               encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
     }).join(&quot;&amp;&quot;);</diff>
      <filename>public/javascripts/dragdrop.js</filename>
    </modified>
    <modified>
      <diff>@@ -4,309 +4,107 @@
 //  Mark Pilgrim (http://diveintomark.org/)
 //  Martin Bialasinki
 // 
-// See scriptaculous.js for full license.
-
-Object.debug = function(obj) {
-  var info = [];
-  
-  if(typeof obj in [&quot;string&quot;,&quot;number&quot;]) {
-    return obj;
-  } else {
-    for(property in obj)
-      if(typeof obj[property]!=&quot;function&quot;)
-        info.push(property + ' =&gt; ' + 
-          (typeof obj[property] == &quot;string&quot; ?
-            '&quot;' + obj[property] + '&quot;' :
-            obj[property]));
-  }
-  
-  return (&quot;'&quot; + obj + &quot;' #&quot; + typeof obj + 
-    &quot;: {&quot; + info.join(&quot;, &quot;) + &quot;}&quot;);
-}
-
-
-/*--------------------------------------------------------------------------*/
-
-var Builder = {
-  NODEMAP: {
-    AREA: 'map',
-    CAPTION: 'table',
-    COL: 'table',
-    COLGROUP: 'table',
-    LEGEND: 'fieldset',
-    OPTGROUP: 'select',
-    OPTION: 'select',
-    PARAM: 'object',
-    TBODY: 'table',
-    TD: 'table',
-    TFOOT: 'table',
-    TH: 'table',
-    THEAD: 'table',
-    TR: 'table'
-  },
-  // note: For Firefox &lt; 1.5, OPTION and OPTGROUP tags are currently broken,
-  //       due to a Firefox bug
-  node: function(elementName) {
-    elementName = elementName.toUpperCase();
-    
-    // try innerHTML approach
-    var parentTag = this.NODEMAP[elementName] || 'div';
-    var parentElement = document.createElement(parentTag);
-    parentElement.innerHTML = &quot;&lt;&quot; + elementName + &quot;&gt;&lt;/&quot; + elementName + &quot;&gt;&quot;;
-    var element = parentElement.firstChild || null;
-      
-    // see if browser added wrapping tags
-    if(element &amp;&amp; (element.tagName != elementName))
-      element = element.getElementsByTagName(elementName)[0];
-    
-    // fallback to createElement approach
-    if(!element) element = document.createElement(elementName);
-    
-    // abort if nothing could be created
-    if(!element) return;
-
-    // attributes (or text)
-    if(arguments[1])
-      if(this._isStringOrNumber(arguments[1]) ||
-        (arguments[1] instanceof Array)) {
-          this._children(element, arguments[1]);
-        } else {
-          var attrs = this._attributes(arguments[1]);
-          if(attrs.length) {
-            parentElement.innerHTML = &quot;&lt;&quot; +elementName + &quot; &quot; +
-              attrs + &quot;&gt;&lt;/&quot; + elementName + &quot;&gt;&quot;;
-            element = parentElement.firstChild || null;
-            // workaround firefox 1.0.X bug
-            if(!element) {
-              element = document.createElement(elementName);
-              for(attr in arguments[1]) 
-                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
-            }
-            if(element.tagName != elementName)
-              element = parentElement.getElementsByTagName(elementName)[0];
-            }
-        } 
-
-    // text, or array of children
-    if(arguments[2])
-      this._children(element, arguments[2]);
-
-     return element;
-  },
-  _text: function(text) {
-     return document.createTextNode(text);
-  },
-  _attributes: function(attributes) {
-    var attrs = [];
-    for(attribute in attributes)
-      attrs.push((attribute=='className' ? 'class' : attribute) +
-          '=&quot;' + attributes[attribute].toString().escapeHTML() + '&quot;');
-    return attrs.join(&quot; &quot;);
-  },
-  _children: function(element, children) {
-    if(typeof children=='object') { // array can hold nodes and text
-      children.flatten().each( function(e) {
-        if(typeof e=='object')
-          element.appendChild(e)
-        else
-          if(Builder._isStringOrNumber(e))
-            element.appendChild(Builder._text(e));
-      });
-    } else
-      if(Builder._isStringOrNumber(children)) 
-         element.appendChild(Builder._text(children));
-  },
-  _isStringOrNumber: function(param) {
-    return(typeof param=='string' || typeof param=='number');
-  }
-}
-
-/* ------------- element ext -------------- */
-
-// converts rgb() and #xxx to #xxxxxx format,
-// returns self (or first argument) if not convertable
-String.prototype.parseColor = function() {
-  color = &quot;#&quot;;
-  if(this.slice(0,4) == &quot;rgb(&quot;) {
-    var cols = this.slice(4,this.length-1).split(',');
-    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i&lt;3);
-  } else {
-    if(this.slice(0,1) == '#') {
-      if(this.length==4) for(var i=1;i&lt;4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
-      if(this.length==7) color = this.toLowerCase();
-    }
-  }
-  return(color.length==7 ? color : (arguments[0] || this));
-}
-
-Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
-  var children = $(element).childNodes;
-  var text     = &quot;&quot;;
-  var classtest = new RegExp(&quot;^([^ ]+ )*&quot; + ignoreclass+ &quot;( [^ ]+)*$&quot;,&quot;i&quot;);
-
-  for (var i = 0; i &lt; children.length; i++) {
-    if(children[i].nodeType==3) {
-      text+=children[i].nodeValue;
-    } else {
-      if((!children[i].className.match(classtest)) &amp;&amp; children[i].hasChildNodes())
-        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
-    }
-  }
-
+// See scriptaculous.js for full license.  
+
+/* ------------- element ext -------------- */  
+ 
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';  
+  if(this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i&lt;3);  
+  } else {  
+    if(this.slice(0,1) == '#') {  
+      if(this.length==4) for(var i=1;i&lt;4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if(this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return(color.length==7 ? color : (arguments[0] || this));  
+}  
+
+Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {  
+  var children = $(element).childNodes;  
+  var text     = '';  
+  var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i');  
+ 
+  for (var i = 0; i &lt; children.length; i++) {  
+    if(children[i].nodeType==3) {  
+      text+=children[i].nodeValue;  
+    } else {  
+      if((!children[i].className.match(classtest)) &amp;&amp; children[i].hasChildNodes())  
+        text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);  
+    }  
+  }  
+ 
   return text;
 }
 
-Element.setContentZoom = function(element, percent) {
+Element.setStyle = function(element, style) {
   element = $(element);
-  element.style.fontSize = (percent/100) + &quot;em&quot;;  
-  if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0);
+  for(k in style) element.style[k.camelize()] = style[k];
 }
 
-Element.getOpacity = function(element){
-  var opacity;
-  if (opacity = Element.getStyle(element, &quot;opacity&quot;))
-    return parseFloat(opacity);
-  if (opacity = (Element.getStyle(element, &quot;filter&quot;) || '').match(/alpha\(opacity=(.*)\)/))
-    if(opacity[1]) return parseFloat(opacity[1]) / 100;
-  return 1.0;
+Element.setContentZoom = function(element, percent) {  
+  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
+  if(navigator.appVersion.indexOf('AppleWebKit')&gt;0) window.scrollBy(0,0);  
 }
 
-Element.setOpacity = function(element, value){
-  element= $(element);
-  var els = element.style;
-  if (value == 1){
-    els.opacity = '0.999999';
-    if(/MSIE/.test(navigator.userAgent))
-      els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
-  } else {
-    if(value &lt; 0.00001) value = 0;
-    els.opacity = value;
-    if(/MSIE/.test(navigator.userAgent))
-      els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + 
-        &quot;alpha(opacity=&quot;+value*100+&quot;)&quot;;
-  }  
-}
-
-Element.getInlineOpacity = function(element){
-  element= $(element);
-  var op;
-  op = element.style.opacity;
-  if (typeof op != &quot;undefined&quot; &amp;&amp; op != &quot;&quot;) return op;
-  return &quot;&quot;;
-}
-
-Element.setInlineOpacity = function(element, value){
-  element= $(element);
-  var els = element.style;
-  els.opacity = value;
+Element.getOpacity = function(element){  
+  var opacity;
+  if (opacity = Element.getStyle(element, 'opacity'))  
+    return parseFloat(opacity);  
+  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
+    if(opacity[1]) return parseFloat(opacity[1]) / 100;  
+  return 1.0;  
 }
 
-/*--------------------------------------------------------------------------*/
-
-Element.Class = {
-    // Element.toggleClass(element, className) toggles the class being on/off
-    // Element.toggleClass(element, className1, className2) toggles between both classes,
-    //   defaulting to className1 if neither exist
-    toggle: function(element, className) {
-      if(Element.Class.has(element, className)) {
-        Element.Class.remove(element, className);
-        if(arguments.length == 3) Element.Class.add(element, arguments[2]);
-      } else {
-        Element.Class.add(element, className);
-        if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
-      }
-    },
-
-    // gets space-delimited classnames of an element as an array
-    get: function(element) {
-      return $(element).className.split(' ');
-    },
-
-    // functions adapted from original functions by Gavin Kistner
-    remove: function(element) {
-      element = $(element);
-      var removeClasses = arguments;
-      $R(1,arguments.length-1).each( function(index) {
-        element.className = 
-          element.className.split(' ').reject( 
-            function(klass) { return (klass == removeClasses[index]) } ).join(' ');
-      });
-    },
-
-    add: function(element) {
-      element = $(element);
-      for(var i = 1; i &lt; arguments.length; i++) {
-        Element.Class.remove(element, arguments[i]);
-        element.className += (element.className.length &gt; 0 ? ' ' : '') + arguments[i];
-      }
-    },
-
-    // returns true if all given classes exist in said element
-    has: function(element) {
-      element = $(element);
-      if(!element || !element.className) return false;
-      var regEx;
-      for(var i = 1; i &lt; arguments.length; i++) {
-        if((typeof arguments[i] == 'object') &amp;&amp; 
-          (arguments[i].constructor == Array)) {
-          for(var j = 0; j &lt; arguments[i].length; j++) {
-            regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i][j] + &quot;(\\s|$)&quot;);
-            if(!regEx.test(element.className)) return false;
-          }
-        } else {
-          regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i] + &quot;(\\s|$)&quot;);
-          if(!regEx.test(element.className)) return false;
-        }
-      }
-      return true;
-    },
-
-    // expects arrays of strings and/or strings as optional paramters
-    // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
-    has_any: function(element) {
-      element = $(element);
-      if(!element || !element.className) return false;
-      var regEx;
-      for(var i = 1; i &lt; arguments.length; i++) {
-        if((typeof arguments[i] == 'object') &amp;&amp; 
-          (arguments[i].constructor == Array)) {
-          for(var j = 0; j &lt; arguments[i].length; j++) {
-            regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i][j] + &quot;(\\s|$)&quot;);
-            if(regEx.test(element.className)) return true;
-          }
-        } else {
-          regEx = new RegExp(&quot;(^|\\s)&quot; + arguments[i] + &quot;(\\s|$)&quot;);
-          if(regEx.test(element.className)) return true;
-        }
-      }
-      return false;
-    },
-
-    childrenWith: function(element, className) {
-      var children = $(element).getElementsByTagName('*');
-      var elements = new Array();
-
-      for (var i = 0; i &lt; children.length; i++)
-        if (Element.Class.has(children[i], className))
-          elements.push(children[i]);
-
-      return elements;
-    }
+Element.setOpacity = function(element, value){  
+  element= $(element);  
+  if (value == 1){
+    Element.setStyle(element, { opacity: 
+      (/Gecko/.test(navigator.userAgent) &amp;&amp; !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
+      0.999999 : null });
+    if(/MSIE/.test(navigator.userAgent))  
+      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
+  } else {  
+    if(value &lt; 0.00001) value = 0;  
+    Element.setStyle(element, {opacity: value});
+    if(/MSIE/.test(navigator.userAgent))  
+     Element.setStyle(element, 
+       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
+                 'alpha(opacity='+value*100+')' });  
+  }   
+}  
+ 
+Element.getInlineOpacity = function(element){  
+  return $(element).style.opacity || '';
+}  
+
+Element.childrenWithClassName = function(element, className) {  
+  return $A($(element).getElementsByTagName('*')).select(
+    function(c) { return Element.hasClassName(c, className) });
+}
+
+Array.prototype.call = function() {
+  var args = arguments;
+  this.each(function(f){ f.apply(this, args) });
 }
 
 /*--------------------------------------------------------------------------*/
 
 var Effect = {
   tagifyText: function(element) {
-    var tagifyStyle = &quot;position:relative&quot;;
-    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += &quot;;zoom:1&quot;;
+    var tagifyStyle = 'position:relative';
+    if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
     element = $(element);
     $A(element.childNodes).each( function(child) {
       if(child.nodeType==3) {
         child.nodeValue.toArray().each( function(character) {
           element.insertBefore(
             Builder.node('span',{style: tagifyStyle},
-              character == &quot; &quot; ? String.fromCharCode(160) : character), 
+              character == ' ' ? String.fromCharCode(160) : character), 
               child);
         });
         Element.remove(child);
@@ -326,11 +124,10 @@ var Effect = {
       speed: 0.1,
       delay: 0.0
     }, arguments[2] || {});
-    var speed = options.speed;
-    var delay = options.delay;
+    var masterDelay = options.delay;
 
     $A(elements).each( function(element, index) {
-      new effect(element, Object.extend(options, { delay: delay + index * speed }));
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
     });
   }
 };
@@ -371,6 +168,9 @@ Effect.Transitions.full = function(pos) {
 
 Effect.Queue = {
   effects:  [],
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
   interval: null,
   add: function(effect) {
     var timestamp = new Date().getTime();
@@ -407,6 +207,7 @@ Effect.Queue = {
     this.effects.invoke('loop', timePos);
   }
 }
+Object.extend(Effect.Queue, Enumerable);
 
 Effect.Base = function() {};
 Effect.Base.prototype = {
@@ -457,13 +258,15 @@ Effect.Base.prototype = {
       if(this.setup) this.setup();
       this.event('afterSetup');
     }
-    if(this.options.transition) pos = this.options.transition(pos);
-    pos *= (this.options.to-this.options.from);
-    pos += this.options.from;
-    this.position = pos;
-    this.event('beforeUpdate');
-    if(this.update) this.update(pos);
-    this.event('afterUpdate');
+    if(this.state == 'running') {
+      if(this.options.transition) pos = this.options.transition(pos);
+      pos *= (this.options.to-this.options.from);
+      pos += this.options.from;
+      this.position = pos;
+      this.event('beforeUpdate');
+      if(this.update) this.update(pos);
+      this.event('afterUpdate');
+    }
   },
   cancel: function() {
     if(!this.options.sync) Effect.Queue.remove(this);
@@ -472,6 +275,9 @@ Effect.Base.prototype = {
   event: function(eventName) {
     if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
     if(this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    return '#&lt;Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '&gt;';
   }
 }
 
@@ -501,7 +307,7 @@ Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
     this.element = $(element);
     // make this work on IE on elements without 'layout'
     if(/MSIE/.test(navigator.userAgent) &amp;&amp; (!this.element.hasLayout))
-      this.element.style.zoom = 1;
+      Element.setStyle(this.element, {zoom: 1});
     var options = Object.extend({
       from: Element.getOpacity(this.element) || 0.0,
       to:   1.0
@@ -525,20 +331,16 @@ Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
     // Bug in Opera: Opera returns the &quot;real&quot; position of a static element or
     // relative element that does not have top/left explicitly set.
     // ==&gt; Always set top and left for position relative elements in your stylesheets 
-    // (to 0 if you do not need them)
-    
+    // (to 0 if you do not need them) 
     Element.makePositioned(this.element);
     this.originalTop  = parseFloat(Element.getStyle(this.element,'top')  || '0');
     this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
   },
   update: function(position) {
-    var topd  = this.toTop  * position + this.originalTop;
-    var leftd = this.toLeft * position + this.originalLeft;
-    this.setPosition(topd, leftd);
-  },
-  setPosition: function(topd, leftd) {
-    this.element.style.top  = topd  + &quot;px&quot;;
-    this.element.style.left = leftd + &quot;px&quot;;
+    Element.setStyle(this.element, {
+      top:  this.toTop  * position + this.originalTop + 'px',
+      left: this.toLeft * position + this.originalLeft + 'px'
+    });
   }
 });
 
@@ -558,33 +360,31 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
     this.start(options);
   },
   setup: function() {
-    var effect = this;
-    
     this.restoreAfterFinish = this.options.restoreAfterFinish || false;
     this.elementPositioning = Element.getStyle(this.element,'position');
     
-    effect.originalStyle = {};
+    this.originalStyle = {};
     ['top','left','width','height','fontSize'].each( function(k) {
-      effect.originalStyle[k] = effect.element.style[k];
-    });
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
       
     this.originalTop  = this.element.offsetTop;
     this.originalLeft = this.element.offsetLeft;
     
-    var fontSize = Element.getStyle(this.element,'font-size') || &quot;100%&quot;;
+    var fontSize = Element.getStyle(this.element,'font-size') || '100%';
     ['em','px','%'].each( function(fontSizeType) {
       if(fontSize.indexOf(fontSizeType)&gt;0) {
-        effect.fontSize     = parseFloat(fontSize);
-        effect.fontSizeType = fontSizeType;
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
       }
-    });
+    }.bind(this));
     
     this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
     
     this.dims = null;
     if(this.options.scaleMode=='box')
-      this.dims = [this.element.clientHeight, this.element.clientWidth];
-    if(this.options.scaleMode=='content')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if(/^content/.test(this.options.scaleMode))
       this.dims = [this.element.scrollHeight, this.element.scrollWidth];
     if(!this.dims)
       this.dims = [this.options.scaleMode.originalHeight,
@@ -593,32 +393,28 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
   update: function(position) {
     var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
     if(this.options.scaleContent &amp;&amp; this.fontSize)
-      this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType;
+      Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType });
     this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
   },
   finish: function(position) {
-    if (this.restoreAfterFinish) {
-      var effect = this;
-      ['top','left','width','height','fontSize'].each( function(k) {
-        effect.element.style[k] = effect.originalStyle[k];
-      });
-    }
+    if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle);
   },
   setDimensions: function(height, width) {
-    var els = this.element.style;
-    if(this.options.scaleX) els.width = width + 'px';
-    if(this.options.scaleY) els.height = height + 'px';
+    var d = {};
+    if(this.options.scaleX) d.width = width + 'px';
+    if(this.options.scaleY) d.height = height + 'px';
     if(this.options.scaleFromCenter) {
       var topd  = (height - this.dims[0])/2;
       var leftd = (width  - this.dims[1])/2;
       if(this.elementPositioning == 'absolute') {
-        if(this.options.scaleY) els.top = this.originalTop-topd + &quot;px&quot;;
-        if(this.options.scaleX) els.left = this.originalLeft-leftd + &quot;px&quot;;
+        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
       } else {
-        if(this.options.scaleY) els.top = -topd + &quot;px&quot;;
-        if(this.options.scaleX) els.left = -leftd + &quot;px&quot;;
+        if(this.options.scaleY) d.top = -topd + 'px';
+        if(this.options.scaleX) d.left = -leftd + 'px';
       }
     }
+    Element.setStyle(this.element, d);
   }
 });
 
@@ -626,39 +422,32 @@ Effect.Highlight = Class.create();
 Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
   initialize: function(element) {
     this.element = $(element);
-    var options = Object.extend({
-      startcolor:   &quot;#ffff99&quot;
-    }, arguments[1] || {});
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
     this.start(options);
   },
   setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; }
     // Disable background image during the effect
-    this.oldBgImage = this.element.style.backgroundImage;
-    this.element.style.backgroundImage = &quot;none&quot;;
+    this.oldStyle = {
+      backgroundImage: Element.getStyle(this.element, 'background-image') };
+    Element.setStyle(this.element, {backgroundImage: 'none'});
     if(!this.options.endcolor)
       this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
-    if (typeof this.options.restorecolor == &quot;undefined&quot;)
-      this.options.restorecolor = this.element.style.backgroundColor;
+    if(!this.options.restorecolor)
+      this.options.restorecolor = Element.getStyle(this.element, 'background-color');
     // init color calculations
-    this.colors_base = [
-      parseInt(this.options.startcolor.slice(1,3),16),
-      parseInt(this.options.startcolor.slice(3,5),16),
-      parseInt(this.options.startcolor.slice(5),16) ];
-    this.colors_delta = [
-      parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
-      parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
-      parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
   },
   update: function(position) {
-    var effect = this; var colors = $R(0,2).map( function(i){ 
-      return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position))
-    });
-    this.element.style.backgroundColor = &quot;#&quot; +
-      colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
+    Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
   },
   finish: function() {
-    this.element.style.backgroundColor = this.options.restorecolor;
-    this.element.style.backgroundImage = this.oldBgImage;
+    Element.setStyle(this.element, Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
   }
 });
 
@@ -671,6 +460,7 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
   setup: function() {
     Position.prepare();
     var offsets = Position.cumulativeOffset(this.element);
+    if(this.options.offset) offsets[1] += this.options.offset;
     var max = window.innerHeight ? 
       window.height - window.innerHeight :
       document.body.scrollHeight - 
@@ -693,42 +483,38 @@ Effect.Fade = function(element) {
   var options = Object.extend({
   from: Element.getOpacity(element) || 1.0,
   to:   0.0,
-  afterFinishInternal: function(effect) 
-    { if (effect.options.to == 0) {
-        Element.hide(effect.element);
-        Element.setInlineOpacity(effect.element, oldOpacity);
-      }  
-    }
+  afterFinishInternal: function(effect) { with(Element) { 
+    if(effect.options.to!=0) return;
+    hide(effect.element);
+    setStyle(effect.element, {opacity: oldOpacity}); }}
   }, arguments[1] || {});
   return new Effect.Opacity(element,options);
 }
 
 Effect.Appear = function(element) {
   var options = Object.extend({
-  from: (Element.getStyle(element, &quot;display&quot;) == &quot;none&quot; ? 0.0 : Element.getOpacity(element) || 0.0),
+  from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0),
   to:   1.0,
-  beforeSetup: function(effect)  
-    { Element.setOpacity(effect.element, effect.options.from);
-      Element.show(effect.element); }
+  beforeSetup: function(effect) { with(Element) {
+    setOpacity(effect.element, effect.options.from);
+    show(effect.element); }}
   }, arguments[1] || {});
   return new Effect.Opacity(element,options);
 }
 
 Effect.Puff = function(element) {
   element = $(element);
-  var oldOpacity = Element.getInlineOpacity(element);
-  var oldPosition = element.style.position;
+  var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') };
   return new Effect.Parallel(
    [ new Effect.Scale(element, 200, 
       { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
      new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
      Object.extend({ duration: 1.0, 
-      beforeSetupInternal: function(effect) 
-       { effect.effects[0].element.style.position = 'absolute'; },
-      afterFinishInternal: function(effect)
-       { Element.hide(effect.effects[0].element);
-         effect.effects[0].element.style.position = oldPosition;
-         Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
+      beforeSetupInternal: function(effect) { with(Element) {
+        setStyle(effect.effects[0].element, {position: 'absolute'}); }},
+      afterFinishInternal: function(effect) { with(Element) {
+         hide(effect.effects[0].element);
+         setStyle(effect.effects[0].element, oldStyle); }}
      }, arguments[1] || {})
    );
 }
@@ -740,18 +526,15 @@ Effect.BlindUp = function(element) {
     Object.extend({ scaleContent: false, 
       scaleX: false, 
       restoreAfterFinish: true,
-      afterFinishInternal: function(effect)
-        { 
-          Element.hide(effect.element);
-          Element.undoClipping(effect.element);
-        } 
+      afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); }} 
     }, arguments[1] || {})
   );
 }
 
 Effect.BlindDown = function(element) {
   element = $(element);
-  var oldHeight = element.style.height;
+  var oldHeight = Element.getStyle(element, 'height');
   var elementDimensions = Element.getDimensions(element);
   return new Effect.Scale(element, 100, 
     Object.extend({ scaleContent: false, 
@@ -759,15 +542,15 @@ Effect.BlindDown = function(element) {
       scaleFrom: 0,
       scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
       restoreAfterFinish: true,
-      afterSetup: function(effect) {
-        Element.makeClipping(effect.element);
-        effect.element.style.height = &quot;0px&quot;;
-        Element.show(effect.element); 
-      },  
-      afterFinishInternal: function(effect) {
-        Element.undoClipping(effect.element);
-        effect.element.style.height = oldHeight;
-      }
+      afterSetup: function(effect) { with(Element) {
+        makeClipping(effect.element);
+        setStyle(effect.element, {height: '0px'});
+        show(effect.element); 
+      }},  
+      afterFinishInternal: function(effect) { with(Element) {
+        undoClipping(effect.element);
+        setStyle(effect.element, {height: oldHeight});
+      }}
     }, arguments[1] || {})
   );
 }
@@ -783,16 +566,13 @@ Effect.SwitchOff = function(element) {
       new Effect.Scale(effect.element, 1, { 
         duration: 0.3, scaleFromCenter: true,
         scaleX: false, scaleContent: false, restoreAfterFinish: true,
-        beforeSetup: function(effect) { 
-          Element.makePositioned(effect.element); 
-          Element.makeClipping(effect.element);
-        },
-        afterFinishInternal: function(effect) { 
-          Element.hide(effect.element); 
-          Element.undoClipping(effect.element);
-          Element.undoPositioned(effect.element);
-          Element.setInlineOpacity(effect.element, oldOpacity);
-        }
+        beforeSetup: function(effect) { with(Element) {
+          [makePositioned,makeClipping].call(effect.element);
+        }},
+        afterFinishInternal: function(effect) { with(Element) {
+          [hide,undoClipping,undoPositioned].call(effect.element);
+          setStyle(effect.element, {opacity: oldOpacity});
+        }}
       })
     }
   });
@@ -800,29 +580,28 @@ Effect.SwitchOff = function(element) {
 
 Effect.DropOut = function(element) {
   element = $(element);
-  var oldTop = element.style.top;
-  var oldLeft = element.style.left;
-  var oldOpacity = Element.getInlineOpacity(element);
+  var oldStyle = {
+    top: Element.getStyle(element, 'top'),
+    left: Element.getStyle(element, 'left'),
+    opacity: Element.getInlineOpacity(element) };
   return new Effect.Parallel(
     [ new Effect.MoveBy(element, 100, 0, { sync: true }), 
       new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
     Object.extend(
       { duration: 0.5,
-        beforeSetup: function(effect) { 
-          Element.makePositioned(effect.effects[0].element); },
-        afterFinishInternal: function(effect) { 
-          Element.hide(effect.effects[0].element); 
-          Element.undoPositioned(effect.effects[0].element);
-          effect.effects[0].element.style.left = oldLeft;
-          effect.effects[0].element.style.top = oldTop;
-          Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } 
+        beforeSetup: function(effect) { with(Element) {
+          makePositioned(effect.effects[0].element); }},
+        afterFinishInternal: function(effect) { with(Element) {
+          [hide, undoPositioned].call(effect.effects[0].element);
+          setStyle(effect.effects[0].element, oldStyle); }} 
       }, arguments[1] || {}));
 }
 
 Effect.Shake = function(element) {
   element = $(element);
-  var oldTop = element.style.top;
-  var oldLeft = element.style.left;
+  var oldStyle = {
+    top: Element.getStyle(element, 'top'),
+    left: Element.getStyle(element, 'left') };
   return new Effect.MoveBy(element, 0, 20, 
     { duration: 0.05, afterFinishInternal: function(effect) {
   new Effect.MoveBy(effect.element, 0, -40, 
@@ -834,39 +613,39 @@ Effect.Shake = function(element) {
   new Effect.MoveBy(effect.element, 0, 40, 
     { duration: 0.1, afterFinishInternal: function(effect) {
   new Effect.MoveBy(effect.element, 0, -20, 
-    { duration: 0.05, afterFinishInternal: function(effect) {
-        Element.undoPositioned(effect.element);
-        effect.element.style.left = oldLeft;
-        effect.element.style.top = oldTop;
-  }}) }}) }}) }}) }}) }});
+    { duration: 0.05, afterFinishInternal: function(effect) { with(Element) {
+        undoPositioned(effect.element);
+        setStyle(effect.element, oldStyle);
+  }}}) }}) }}) }}) }}) }});
 }
 
 Effect.SlideDown = function(element) {
   element = $(element);
   Element.cleanWhitespace(element);
   // SlideDown need to have the content of the element wrapped in a container element with fixed height!
-  var oldInnerBottom = element.firstChild.style.bottom;
+  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
   var elementDimensions = Element.getDimensions(element);
-  return new Effect.Scale(element, 100, 
-   Object.extend({ scaleContent: false, 
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
     scaleX: false, 
     scaleFrom: 0,
-    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},    
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
     restoreAfterFinish: true,
-    afterSetup: function(effect) {
-      Element.makePositioned(effect.element.firstChild);
-      if (window.opera) effect.element.firstChild.style.top = &quot;&quot;;
-      Element.makeClipping(effect.element);
-      element.style.height = '0';
-      Element.show(element); 
-    },  
-    afterUpdateInternal: function(effect) { 
-      effect.element.firstChild.style.bottom = 
-        (effect.originalHeight - effect.element.clientHeight) + 'px'; },
-    afterFinishInternal: function(effect) { 
-      Element.undoClipping(effect.element); 
-      Element.undoPositioned(effect.element.firstChild);
-      effect.element.firstChild.style.bottom = oldInnerBottom; }
+    afterSetup: function(effect) { with(Element) {
+      makePositioned(effect.element);
+      makePositioned(effect.element.firstChild);
+      if(window.opera) setStyle(effect.element, {top: ''});
+      makeClipping(effect.element);
+      setStyle(effect.element, {height: '0px'});
+      show(element); }},
+    afterUpdateInternal: function(effect) { with(Element) {
+      setStyle(effect.element.firstChild, {bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
+    afterFinishInternal: function(effect) { with(Element) {
+      undoClipping(effect.element); 
+      undoPositioned(effect.element.firstChild);
+      undoPositioned(effect.element);
+      setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
     }, arguments[1] || {})
   );
 }
@@ -874,122 +653,111 @@ Effect.SlideDown = function(element) {
 Effect.SlideUp = function(element) {
   element = $(element);
   Element.cleanWhitespace(element);
-  var oldInnerBottom = element.firstChild.style.bottom;
+  var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom');
   return new Effect.Scale(element, 0, 
    Object.extend({ scaleContent: false, 
     scaleX: false, 
     scaleMode: 'box',
     scaleFrom: 100,
     restoreAfterFinish: true,
-    beforeStartInternal: function(effect) { 
-      Element.makePositioned(effect.element.firstChild);
-      if (window.opera) effect.element.firstChild.style.top = &quot;&quot;;
-      Element.makeClipping(effect.element);
-      Element.show(element); 
-    },  
-    afterUpdateInternal: function(effect) { 
-     effect.element.firstChild.style.bottom = 
-       (effect.originalHeight - effect.element.clientHeight) + 'px'; },
-    afterFinishInternal: function(effect) { 
-        Element.hide(effect.element);
-        Element.undoClipping(effect.element); 
-        Element.undoPositioned(effect.element.firstChild);
-        effect.element.firstChild.style.bottom = oldInnerBottom; }
+    beforeStartInternal: function(effect) { with(Element) {
+      makePositioned(effect.element);
+      makePositioned(effect.element.firstChild);
+      if(window.opera) setStyle(effect.element, {top: ''});
+      makeClipping(effect.element);
+      show(element); }},  
+    afterUpdateInternal: function(effect) { with(Element) {
+      setStyle(effect.element.firstChild, {bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); }},
+    afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); 
+        undoPositioned(effect.element.firstChild);
+        undoPositioned(effect.element);
+        setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }}
    }, arguments[1] || {})
   );
 }
 
+// Bug in opera makes the TD containing this element expand for a instance after finish 
 Effect.Squish = function(element) {
-  // Bug in opera makes the TD containing this element expand for a instance after finish 
   return new Effect.Scale(element, window.opera ? 1 : 0, 
     { restoreAfterFinish: true,
-      beforeSetup: function(effect) { 
-        Element.makeClipping(effect.element); },  
-      afterFinishInternal: function(effect) { 
-        Element.hide(effect.element); 
-        Element.undoClipping(effect.element); } 
+      beforeSetup: function(effect) { with(Element) {
+        makeClipping(effect.element); }},  
+      afterFinishInternal: function(effect) { with(Element) {
+        hide(effect.element); 
+        undoClipping(effect.element); }}
   });
 }
 
 Effect.Grow = function(element) {
   element = $(element);
-  var options = arguments[1] || {};
-  
-  var elementDimensions = Element.getDimensions(element);
-  var originalWidth = elementDimensions.width;
-  var originalHeight = elementDimensions.height;
-  var oldTop = element.style.top;
-  var oldLeft = element.style.left;
-  var oldHeight = element.style.height;
-  var oldWidth = element.style.width;
-  var oldOpacity = Element.getInlineOpacity(element);
-  
-  var direction = options.direction || 'center';
-  var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
-  var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
-  var opacityTransition = options.opacityTransition || Effect.Transitions.full;
-  
+  var options = Object.extend({
+    direction: 'center',
+    moveTransistion: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: Element.getInlineOpacity(element) };
+
+  var dims = Element.getDimensions(element);    
   var initialMoveX, initialMoveY;
   var moveX, moveY;
   
-  switch (direction) {
+  switch (options.direction) {
     case 'top-left':
       initialMoveX = initialMoveY = moveX = moveY = 0; 
       break;
     case 'top-right':
-      initialMoveX = originalWidth;
+      initialMoveX = dims.width;
       initialMoveY = moveY = 0;
-      moveX = -originalWidth;
+      moveX = -dims.width;
       break;
     case 'bottom-left':
       initialMoveX = moveX = 0;
-      initialMoveY = originalHeight;
-      moveY = -originalHeight;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
       break;
     case 'bottom-right':
-      initialMoveX = originalWidth;
-      initialMoveY = originalHeight;
-      moveX = -originalWidth;
-      moveY = -originalHeight;
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
       break;
     case 'center':
-      initialMoveX = originalWidth / 2;
-      initialMoveY = originalHeight / 2;
-      moveX = -originalWidth / 2;
-      moveY = -originalHeight / 2;
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
       break;
   }
   
   return new Effect.MoveBy(element, initialMoveY, initialMoveX, { 
     duration: 0.01, 
-    beforeSetup: function(effect) { 
-      Element.hide(effect.element);
-      Element.makeClipping(effect.element);
-      Element.makePositioned(effect.element);
-    },
+    beforeSetup: function(effect) { with(Element) {
+      hide(effect.element);
+      makeClipping(effect.element);
+      makePositioned(effect.element);
+    }},
     afterFinishInternal: function(effect) {
       new Effect.Parallel(
-        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
-          new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }),
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }),
           new Effect.Scale(effect.element, 100, {
-            scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, 
-            sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true})
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
         ], Object.extend({
-             beforeSetup: function(effect) {
-              effect.effects[0].element.style.height = 0;
-              Element.show(effect.effects[0].element);
-             },              
-             afterFinishInternal: function(effect) {
-               var el = effect.effects[0].element;
-               var els = el.style;
-               Element.undoClipping(el); 
-               Element.undoPositioned(el);
-               els.top = oldTop;
-               els.left = oldLeft;
-               els.height = oldHeight;
-               els.width = originalWidth;
-               Element.setInlineOpacity(el, oldOpacity);
-             }
+             beforeSetup: function(effect) { with(Element) {
+               setStyle(effect.effects[0].element, {height: '0px'});
+               show(effect.effects[0].element); }},
+             afterFinishInternal: function(effect) { with(Element) {
+               [undoClipping, undoPositioned].call(effect.effects[0].element); 
+               setStyle(effect.effects[0].element, oldStyle); }}
            }, options)
       )
     }
@@ -998,66 +766,54 @@ Effect.Grow = function(element) {
 
 Effect.Shrink = function(element) {
   element = $(element);
-  var options = arguments[1] || {};
-  
-  var originalWidth = element.clientWidth;
-  var originalHeight = element.clientHeight;
-  var oldTop = element.style.top;
-  var oldLeft = element.style.left;
-  var oldHeight = element.style.height;
-  var oldWidth = element.style.width;
-  var oldOpacity = Element.getInlineOpacity(element);
-
-  var direction = options.direction || 'center';
-  var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
-  var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
-  var opacityTransition = options.opacityTransition || Effect.Transitions.none;
-  
+  var options = Object.extend({
+    direction: 'center',
+    moveTransistion: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: Element.getInlineOpacity(element) };
+
+  var dims = Element.getDimensions(element);
   var moveX, moveY;
   
-  switch (direction) {
+  switch (options.direction) {
     case 'top-left':
       moveX = moveY = 0;
       break;
     case 'top-right':
-      moveX = originalWidth;
+      moveX = dims.width;
       moveY = 0;
       break;
     case 'bottom-left':
       moveX = 0;
-      moveY = originalHeight;
+      moveY = dims.height;
       break;
     case 'bottom-right':
-      moveX = originalWidth;
-      moveY = originalHeight;
+      moveX = dims.width;
+      moveY = dims.height;
       break;
     case 'center':  
-      moveX = originalWidth / 2;
-      moveY = originalHeight / 2;
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
       break;
   }
   
   return new Effect.Parallel(
-    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
-      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}),
-      new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition })
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition })
     ], Object.extend({            
-         beforeStartInternal: function(effect) { 
-           Element.makePositioned(effect.effects[0].element);
-           Element.makeClipping(effect.effects[0].element);
-         },
-         afterFinishInternal: function(effect) {
-           var el = effect.effects[0].element;
-           var els = el.style;
-           Element.hide(el);
-           Element.undoClipping(el); 
-           Element.undoPositioned(el);
-           els.top = oldTop;
-           els.left = oldLeft;
-           els.height = oldHeight;
-           els.width = oldWidth;
-           Element.setInlineOpacity(el, oldOpacity);
-         }
+         beforeStartInternal: function(effect) { with(Element) {
+           [makePositioned, makeClipping].call(effect.effects[0].element) }},
+         afterFinishInternal: function(effect) { with(Element) {
+           [hide, undoClipping, undoPositioned].call(effect.effects[0].element);
+           setStyle(effect.effects[0].element, oldStyle); }}
        }, options)
   );
 }
@@ -1071,16 +827,17 @@ Effect.Pulsate = function(element) {
   reverser.bind(transition);
   return new Effect.Opacity(element, 
     Object.extend(Object.extend({  duration: 3.0, from: 0,
-      afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); }
+      afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); }
     }, options), {transition: reverser}));
 }
 
 Effect.Fold = function(element) {
   element = $(element);
-  var originalTop = element.style.top;
-  var originalLeft = element.style.left;
-  var originalWidth = element.style.width;
-  var originalHeight = element.style.height;
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
   Element.makeClipping(element);
   return new Effect.Scale(element, 5, Object.extend({   
     scaleContent: false,
@@ -1089,13 +846,9 @@ Effect.Fold = function(element) {
     new Effect.Scale(element, 1, { 
       scaleContent: false, 
       scaleY: false,
-      afterFinishInternal: function(effect) { 
-        Element.hide(effect.element);  
-        Element.undoClipping(effect.element); 
-        effect.element.style.top = originalTop;
-        effect.element.style.left = originalLeft;
-        effect.element.style.width = originalWidth;
-        effect.element.style.height = originalHeight;
-      } });
+      afterFinishInternal: function(effect) { with(Element) {
+        [hide, undoClipping].call(effect.element); 
+        setStyle(effect.element, oldStyle);
+      }} });
   }}, arguments[1] || {}));
 }</diff>
      <filename>public/javascripts/effects.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,4 @@
-/*  Prototype JavaScript framework, version 1.4.0_rc0
+/*  Prototype JavaScript framework, version 1.4.0
  *  (c) 2005 Sam Stephenson &lt;sam@conio.net&gt;
  *
  *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
@@ -11,7 +11,8 @@
 /*--------------------------------------------------------------------------*/
 
 var Prototype = {
-  Version: '1.4.0_rc0',
+  Version: '1.4.0',
+  ScriptFragment: '(?:&lt;script.*?&gt;)((\n|\r|.)*?)(?:&lt;\/script&gt;)',
 
   emptyFunction: function() {},
   K: function(x) {return x}
@@ -45,10 +46,10 @@ Object.inspect = function(object) {
   }
 }
 
-Function.prototype.bind = function(object) {
-  var __method = this;
+Function.prototype.bind = function() {
+  var __method = this, args = $A(arguments), object = args.shift();
   return function() {
-    return __method.apply(object, arguments);
+    return __method.apply(object, args.concat($A(arguments)));
   }
 }
 
@@ -143,6 +144,22 @@ Object.extend(String.prototype, {
     return this.replace(/&lt;\/?[^&gt;]+&gt;/gi, '');
   },
 
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(eval);
+  },
+
   escapeHTML: function() {
     var div = document.createElement('div');
     var text = document.createTextNode(this);
@@ -214,8 +231,8 @@ var Enumerable = {
   all: function(iterator) {
     var result = true;
     this.each(function(value, index) {
-      if (!(result &amp;= (iterator || Prototype.K)(value, index)))
-        throw $break;
+      result = result &amp;&amp; !!(iterator || Prototype.K)(value, index);
+      if (!result) throw $break;
     });
     return result;
   },
@@ -223,7 +240,7 @@ var Enumerable = {
   any: function(iterator) {
     var result = true;
     this.each(function(value, index) {
-      if (result &amp;= (iterator || Prototype.K)(value, index))
+      if (result = !!(iterator || Prototype.K)(value, index))
         throw $break;
     });
     return result;
@@ -376,6 +393,7 @@ Object.extend(Enumerable, {
   entries: Enumerable.toArray
 });
 var $A = Array.from = function(iterable) {
+  if (!iterable) return [];
   if (iterable.toArray) {
     return iterable.toArray();
   } else {
@@ -388,12 +406,19 @@ var $A = Array.from = function(iterable) {
 
 Object.extend(Array.prototype, Enumerable);
 
+Array.prototype._reverse = Array.prototype.reverse;
+
 Object.extend(Array.prototype, {
   _each: function(iterator) {
     for (var i = 0; i &lt; this.length; i++)
       iterator(this[i]);
   },
 
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
   first: function() {
     return this[0];
   },
@@ -425,13 +450,18 @@ Object.extend(Array.prototype, {
   indexOf: function(object) {
     for (var i = 0; i &lt; this.length; i++)
       if (this[i] == object) return i;
-    return false;
+    return -1;
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
   },
 
-  reverse: function() {
-    var result = [];
-    for (var i = this.length; i &gt; 0; i--)
-      result.push(this[i-1]);
+  shift: function() {
+    var result = this[0];
+    for (var i = 0; i &lt; this.length - 1; i++)
+      this[i] = this[i + 1];
+    this.length--;
     return result;
   },
 
@@ -486,9 +516,9 @@ function $H(object) {
   Object.extend(hash, Hash);
   return hash;
 }
-var Range = Class.create();
-Object.extend(Range.prototype, Enumerable);
-Object.extend(Range.prototype, {
+ObjectRange = Class.create();
+Object.extend(ObjectRange.prototype, Enumerable);
+Object.extend(ObjectRange.prototype, {
   initialize: function(start, end, exclusive) {
     this.start = start;
     this.end = end;
@@ -513,7 +543,7 @@ Object.extend(Range.prototype, {
 });
 
 var $R = function(start, end, exclusive) {
-  return new Range(start, end, exclusive);
+  return new ObjectRange(start, end, exclusive);
 }
 
 var Ajax = {
@@ -549,8 +579,7 @@ Ajax.Responders = {
       if (responder[callback] &amp;&amp; typeof responder[callback] == 'function') {
         try {
           responder[callback].apply(responder, [request, transport, json]);
-        } catch (e) {
-        }
+        } catch (e) {}
       }
     });
   }
@@ -607,8 +636,8 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
 
     try {
       this.url = url;
-      if (this.options.method == 'get')
-        this.url += '?' + parameters;
+      if (this.options.method == 'get' &amp;&amp; parameters.length &gt; 0)
+        this.url += (this.url.match(/\?/) ? '&amp;' : '?') + parameters;
 
       Ajax.Responders.dispatch('onCreate', this, this.transport);
 
@@ -626,6 +655,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
       this.transport.send(this.options.method == 'post' ? body : null);
 
     } catch (e) {
+      this.dispatchException(e);
     }
   },
 
@@ -659,12 +689,23 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
       this.respondToReadyState(this.transport.readyState);
   },
 
+  header: function(name) {
+    try {
+      return this.transport.getResponseHeader(name);
+    } catch (e) {}
+  },
+
   evalJSON: function() {
     try {
-      var json = this.transport.getResponseHeader('X-JSON'), object;
-      object = eval(json);
-      return object;
+      return eval(this.header('X-JSON'));
+    } catch (e) {}
+  },
+
+  evalResponse: function() {
+    try {
+      return eval(this.transport.responseText);
     } catch (e) {
+      this.dispatchException(e);
     }
   },
 
@@ -672,22 +713,38 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
     var event = Ajax.Request.Events[readyState];
     var transport = this.transport, json = this.evalJSON();
 
-    if (event == 'Complete')
-      (this.options['on' + this.transport.status]
-       || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
-       || Prototype.emptyFunction)(transport, json);
+    if (event == 'Complete') {
+      try {
+        (this.options['on' + this.transport.status]
+         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(transport, json);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+        this.evalResponse();
+    }
 
-    (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
-    Ajax.Responders.dispatch('on' + event, this, transport, json);
+    try {
+      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
+      Ajax.Responders.dispatch('on' + event, this, transport, json);
+    } catch (e) {
+      this.dispatchException(e);
+    }
 
     /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
     if (event == 'Complete')
       this.transport.onreadystatechange = Prototype.emptyFunction;
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
   }
 });
 
 Ajax.Updater = Class.create();
-Ajax.Updater.ScriptFragment = '(?:&lt;script.*?&gt;)((\n|.)*?)(?:&lt;\/script&gt;)';
 
 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
   initialize: function(container, url, options) {
@@ -712,16 +769,16 @@ Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
   updateContent: function() {
     var receiver = this.responseIsSuccess() ?
       this.containers.success : this.containers.failure;
+    var response = this.transport.responseText;
 
-    var match    = new RegExp(Ajax.Updater.ScriptFragment, 'img');
-    var response = this.transport.responseText.replace(match, '');
-    var scripts  = this.transport.responseText.match(match);
+    if (!this.options.evalScripts)
+      response = response.stripScripts();
 
     if (receiver) {
       if (this.options.insertion) {
         new this.options.insertion(receiver, response);
       } else {
-        receiver.innerHTML = response;
+        Element.update(receiver, response);
       }
     }
 
@@ -729,14 +786,6 @@ Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
       if (this.onComplete)
         setTimeout(this.onComplete.bind(this), 10);
     }
-
-    if (this.options.evalScripts &amp;&amp; scripts) {
-      match = new RegExp(Ajax.Updater.ScriptFragment, 'im');
-      setTimeout((function() {
-        for (var i = 0; i &lt; scripts.length; i++)
-          eval(scripts[i].match(match)[1]);
-      }).bind(this), 10);
-    }
   }
 });
 
@@ -783,9 +832,9 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
   }
 });
 document.getElementsByClassName = function(className, parentElement) {
-  var children = (document.body || $(parentElement)).getElementsByTagName('*');
+  var children = ($(parentElement) || document.body).getElementsByTagName('*');
   return $A(children).inject([], function(elements, child) {
-    if (Element.hasClassName(child, className))
+    if (child.className.match(new RegExp(&quot;(^|\\s)&quot; + className + &quot;(\\s|$)&quot;)))
       elements.push(child);
     return elements;
   });
@@ -828,6 +877,11 @@ Object.extend(Element, {
     element.parentNode.removeChild(element);
   },
 
+  update: function(element, html) {
+    $(element).innerHTML = html.stripScripts();
+    setTimeout(function() {html.evalScripts()}, 10);
+  },
+
   getHeight: function(element) {
     element = $(element);
     return element.offsetHeight;
@@ -891,6 +945,12 @@ Object.extend(Element, {
     return value == 'auto' ? null : value;
   },
 
+  setStyle: function(element, style) {
+    element = $(element);
+    for (name in style)
+      element.style[name.camelize()] = style[name];
+  },
+
   getDimensions: function(element) {
     element = $(element);
     if (Element.getStyle(element, 'display') != 'none')
@@ -967,7 +1027,7 @@ Abstract.Insertion = function(adjacency) {
 Abstract.Insertion.prototype = {
   initialize: function(element, content) {
     this.element = $(element);
-    this.content = content;
+    this.content = content.stripScripts();
 
     if (this.adjacency &amp;&amp; this.element.insertAdjacentHTML) {
       try {
@@ -984,6 +1044,8 @@ Abstract.Insertion.prototype = {
       if (this.initializeRange) this.initializeRange();
       this.insertContent([this.range.createContextualFragment(this.content)]);
     }
+
+    setTimeout(function() {content.evalScripts()}, 10);
   },
 
   contentFromAnonymousTable: function() {
@@ -1016,7 +1078,7 @@ Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
   },
 
   insertContent: function(fragments) {
-    fragments.reverse().each((function(fragment) {
+    fragments.reverse(false).each((function(fragment) {
       this.element.insertBefore(fragment, this.element.firstChild);
     }).bind(this));
   }
@@ -1077,7 +1139,7 @@ Element.ClassNames.prototype = {
     if (!this.include(classNameToRemove)) return;
     this.set(this.select(function(className) {
       return className != classNameToRemove;
-    }));
+    }).join(' '));
   },
 
   toString: function() {
@@ -1107,8 +1169,10 @@ var Field = {
   },
 
   activate: function(element) {
-    $(element).focus();
-    $(element).select();
+    element = $(element);
+    element.focus();
+    if (element.select)
+      element.select();
   }
 }
 
@@ -1129,7 +1193,7 @@ var Form = {
   },
 
   getElements: function(form) {
-    var form = $(form);
+    form = $(form);
     var elements = new Array();
 
     for (tagName in Form.Element.Serializers) {
@@ -1141,7 +1205,7 @@ var Form = {
   },
 
   getInputs: function(form, typeName, name) {
-    var form = $(form);
+    form = $(form);
     var inputs = form.getElementsByTagName('input');
 
     if (!typeName &amp;&amp; !name)
@@ -1176,16 +1240,15 @@ var Form = {
     }
   },
 
+  findFirstElement: function(form) {
+    return Form.getElements(form).find(function(element) {
+      return element.type != 'hidden' &amp;&amp; !element.disabled &amp;&amp;
+        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
   focusFirstElement: function(form) {
-    var form = $(form);
-    var elements = Form.getElements(form);
-    for (var i = 0; i &lt; elements.length; i++) {
-      var element = elements[i];
-      if (element.type != 'hidden' &amp;&amp; !element.disabled) {
-        Field.activate(element);
-        break;
-      }
-    }
+    Field.activate(Form.findFirstElement(form));
   },
 
   reset: function(form) {
@@ -1195,17 +1258,25 @@ var Form = {
 
 Form.Element = {
   serialize: function(element) {
-    var element = $(element);
+    element = $(element);
     var method = element.tagName.toLowerCase();
     var parameter = Form.Element.Serializers[method](element);
 
-    if (parameter)
-      return encodeURIComponent(parameter[0]) + '=' +
-        encodeURIComponent(parameter[1]);
+    if (parameter) {
+      var key = encodeURIComponent(parameter[0]);
+      if (key.length == 0) return;
+
+      if (parameter[1].constructor != Array)
+        parameter[1] = [parameter[1]];
+
+      return parameter[1].map(function(value) {
+        return key + '=' + encodeURIComponent(value);
+      }).join('&amp;');
+    }
   },
 
   getValue: function(element) {
-    var element = $(element);
+    element = $(element);
     var method = element.tagName.toLowerCase();
     var parameter = Form.Element.Serializers[method](element);
 
@@ -1347,24 +1418,14 @@ Abstract.EventObserver.prototype = {
       switch (element.type.toLowerCase()) {
         case 'checkbox':
         case 'radio':
-          element.target = this;
-          element.prev_onclick = element.onclick || Prototype.emptyFunction;
-          element.onclick = function() {
-            this.prev_onclick();
-            this.target.onElementEvent();
-          }
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
           break;
         case 'password':
         case 'text':
         case 'textarea':
         case 'select-one':
         case 'select-multiple':
-          element.target = this;
-          element.prev_onchange = element.onchange || Prototype.emptyFunction;
-          element.onchange = function() {
-            this.prev_onchange();
-            this.target.onElementEvent();
-          }
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
           break;
       }
     }</diff>
      <filename>public/javascripts/prototype.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,15 +1,80 @@
-#CurrentUserInfo {
+a, a:visited {
+  color: #338833;
+  border-bottom-color: #449944;
+}
+
+.odd_row td {
+  background-color: #FFFFCC;
+  border-color: #FFFFFF;
+}
+
+.even_row td {
+  background-color: #FFFFAA;
+  border-color: #FFFFFF;
+}
+
+.odd_row:hover td,
+.even_row:hover td {
+  background-color: #DDDDFF;
+}
+
+div.contact_card {
+  border-color: #CCCCCC;
+}
+
+div.contact_card h3 {
+  background-color: #FFFF99;
+}
+
+div.contact_card h3.admin {
+  background-image: url('/images/admin_dot.gif');
+  background-position: right 3px;
+  background-repeat: no-repeat;
+}
+
+.action_links a {
+  color: #FF0000;
+}
+
+div.fieldWithErrors input, div.fieldWithErrors select {
+  background-color: #FFDDDD;
+  border-color: #FF0000;
+}
+
+table.calendar td {
+  background-color: #FFFFCC;
+  color: black;
+  border-color: #FFFFFF;
+}
+
+table.calendar a {
+  color: #0000FF;
+}
+
+table.calendar a:hover {
+  background-color: #DDDDFF;
+}
+
+dt.project_header {
+  background-color: #CCCCFF;
+}
+
+dt.project_header a:hover {
+  color: #FF0000;
+}
+
+#topbar {
   color: #FFFFFF;
-  background-color: #009900;
+  background-color: #6dae20;
 }
 
-#CurrentUserInfo a, #CurrentUserInfo a:visited,
-#CurrentUserInfo a:hover {
+#topbar a, #topbar a:visited,
+#topbar a:hover {
   color: #FFFFFF;
 }
 
 #Header {
-  border-bottom-color: #009900;
+  border-bottom-color: #6dae20;
 }
 
 #MyProjects, #UpcomingMilestones {
@@ -18,5 +83,43 @@
 
 #MyProjects h2, #UpcomingMilestones h2 {
   color: #FFFFFF;
-  background-color: #009900;
+  background-color: #6dae20;
+}
+
+#SectionTitle {
+  background-color: #dfdcd5;
+  color: #908360;
+}
+#SectionTitle a {
+  color: #908360;
 }
+#SectionMenu {
+  background-color: #f0efed;
+  color: #716e69;
+}
+#SectionMenu a {
+  color: #716e69;
+}
+#SectionMenu a.current {
+  color: #413f33;
+}
+
+#TopMenu a {
+  color: #716e69;
+}
+#TopMenu a.current {
+  color: #413f33;
+  border-bottom-color: #716e69;
+}
+
+#SystemError {
+  color: #990000;
+  background-color: #FFCCCC;
+  border-color: #FF0000;
+}
+
+#SystemStatus {
+  color: #006600;
+  background-color: #CCFFCC;
+  border-color: #00FF00;
+}
\ No newline at end of file</diff>
      <filename>public/stylesheets/colors.css</filename>
    </modified>
    <modified>
      <diff>@@ -1,13 +1,34 @@
 html {
-	font-size: 100%;
+  font-size: 100%;
 }
 
 body {
-	font-family: Verdana, Arial, Helvetica, sans-serif;
-	font-size: 0.85em;
+  font-family: Verdana, Arial, Helvetica, sans-serif;
+  font-size: 0.85em;
 }
 
-#CurrentUserInfo {
+.important {
+  font-weight: bold;
+}
+
+div.contact_card dt {
+  font-weight: bold;
+}
+
+dt.project_header {
+  font-weight: bold;
+}
+
+#topbar {
   font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif;
   font-size: 0.8em;
 }
+
+#SectionTitle {
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 1.2em;
+}
+#SectionMenu {
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 0.9em;
+}</diff>
      <filename>public/stylesheets/fonts.css</filename>
    </modified>
    <modified>
      <diff>@@ -1,6 +1,6 @@
 body {
   display: block;
-  margin: 0;
+  margin: 8px;
   padding: 0;
 }
 
@@ -13,28 +13,206 @@ th, td {
   vertical-align: top;
 }
 
-#Header {
+a, a:visited {
+  border-bottom: 1px dotted;
+}
+
+p {
+  margin: 0.5em 0 1em 0;
+}
+
+.odd_row td {
+  border: 1px solid;
+}
+
+.even_row td {
+  border: 1px solid;
+}
+
+.important {
+  font-size: 110%;
+}
+
+p.clear_both {
+  clear: both;
+  margin: -1px 0 0 0;
+  padding: 0;
+  width: 0;
+  height: 1px;
+  overflow: hidden;
+}
+
+table.form_layout {
+  margin: 0 auto;
+}
+
+table.form_layout th {
+  margin: 0;
+  padding: 0 5px 10px 0;
+  text-align: right;
+  vertical-align: top;
+}
+
+table.form_layout td {
+  margin: 0;
+  padding: 0 0 10px 5px;
+  text-align: left;
+  vertical-align: top;
+}
+
+table.form_layout td.form_actions {
+  text-align: right;
+}
+
+div.contact_card {
+  float: left;
+  width: 15em;
+  height: 8em;
+  overflow: hidden;
+  border: 1px solid;
+  margin: 2px;
+  padding: 3px;
+  font-size: 85%;
+}
+
+div.contact_card h3 {
+  font-size: 15px;
+  height: 21px;
+  margin: 0 0 5px 0;
+  padding: 0 17px 0 0;
+}
+
+div.contact_card dl, div.contact_card dt, div.contact_card dd {
   margin: 0;
   padding: 0;
-  border-bottom: 1px solid #999999;
 }
 
-#CurrentUserInfo {
+div.contact_card dt, div.contact_card dd {
+  display: inline;
+}
+
+div.contact_card dt {
+  margin-top: 3px;
+}
+
+ul.action_links, ul.action_links li {
+  display: inline;
   margin: 0;
-  padding: 3px 1em;
+  padding: 0;
+  list-style: none;
+}
+
+div.contact_card ul.action_links {
+  display: block;
+  margin-top: 10px;
   text-align: right;
 }
 
-#Header h1 {
-  margin: 0 1em 0 1em;
+table.calendar td {
+  border: 1px solid;
+  vertical-align: top;
+}
+
+table.calendar ul {
+  list-style: none;
+  margin: 0 0 0 5px;
   padding: 0;
-  width: 208px;
-  height: 56px;
-  text-indent: -5000px;
-  overflow: hidden;
-  background-image: url('../images/logo.png');
-  background-position: left top;
-  background-repeat: none;
+}
+
+table.calendar li {
+  list-style: none;
+  margin: 1px 0 3px 0;
+  padding: 0px;
+  font-size: 80%;
+}
+
+table.calendar a {
+  display: block;
+  margin: 0;
+  padding: 2px;
+  text-decoration: none;
+  border: 0;
+}
+
+div.milestone_list {
+  margin: 10px;
+}
+
+dt.project_header {
+  margin: 20px 0 0 0;
+  padding: 3px 1em 3px 1em;
+}
+
+dt.project_header a {
+  text-decoration: none;
+}
+
+table.collection_table {
+  width: 100%;
+}
+
+table.collection_table th,
+table.collection_table td {
+  margin: 0;
+  border: none;
+  padding: 3px;
+}
+
+table.collection_table th {
+  text-align: center;
+}
+
+.action_links a, dt.project_header a {
+  border: none;
+}
+
+#Header {
+  margin: 0;
+}
+
+#Header img {
+  padding-top: 10px;
+  padding-left: 15px;
+  padding-bottom: 10px;
+}
+
+#Header a, #SectionTitle a, #MainMenu a {
+  border: none;
+}
+
+#MainMenu a.current {
+  border-bottom: 3px solid;
+}
+
+#topbar {
+  margin: 0;
+  padding: 5px 1em;
+  text-align: right;
+}
+
+#SectionTitle {
+  padding: 8px;
+}
+#SectionMenu {
+  padding: 8px;
+}
+#TopMenu {
+  padding: 8px;
+  float: right;
+}
+
+#StatusBar {
+  position: absolute;
+  top: 8px;
+  left: 15px;
+  font-size: 10px;
+}
+#StatusBar select {
+  height: 15px;
+  font-size: 10px;
+}
+#StatusBar input {
+  font-size: 10px;
 }
 
 #MyProjects, #UpcomingMilestones {
@@ -56,10 +234,6 @@ th, td {
   padding: 0.25em 1em;
 }
 
-#MyProjects li a:hover {
-  background-color: #EEEEEE;
-}
-
 #MyStoryCards, #RecentActivity {
   margin: 1em 16em 0 0;
 }
@@ -67,3 +241,77 @@ th, td {
 #MyStoryCards table, #RecentActivity table {
   width: 100%;
 }
+
+#MainMenu {
+  white-space: nowrap;
+}
+#MainMenu li {
+  display: inline; 
+  list-style: none;
+  margin-right: 10px;
+}
+
+#ProjectDescription {
+  border: 1px solid;
+  margin: 3px 0px;
+  padding: 3px;
+  font-size: 0.8em;
+}
+
+#Main {
+  padding: 10px;
+  margin: 0;
+}
+
+#SystemError {
+   position: relative;
+   border-top: 1px solid;
+   border-bottom: 1px solid;
+   padding: 3px 2em;
+   margin: 0 0 10px 0;
+}
+
+#SystemStatus {
+  position: relative;
+  border-top: 1px solid;
+  border-bottom: 1px solid;
+  padding: 3px 2em;
+  margin: 0 0 10px 0;
+}
+
+#Footer {
+  text-align: center;
+  font-size: 75%;
+}
+
+#SmallLeftColumn {
+  width: 12em;
+  padding-right: 10px;
+  border-right: 1px solid;
+  vertical-align: top;
+}
+
+#RightColumn {
+  padding-left: 10px;
+  vertical-align: top;
+}
+
+#ProjectsList li {
+  margin: 0 0 5px 10px;
+}
+
+#RecentActivity td {
+  border: 1px solid;
+  font-size: 80%;
+}
+
+#IterationSummary h3 {
+  margin: 10px 0 5px 0;
+  padding: 0;
+}
+
+#IterationSummary table {
+  float: left;
+  padding: 3px;
+  border: 1px solid;
+}
\ No newline at end of file</diff>
      <filename>public/stylesheets/layout.css</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,4 @@
 @import &quot;undohtml.css&quot;;
 @import &quot;fonts.css&quot;;
 @import &quot;layout.css&quot;;
-@import &quot;colors.css&quot;;
+@import &quot;colors.css&quot;;
\ No newline at end of file</diff>
      <filename>public/stylesheets/main.css</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
 require 'commands/breakpointer'
\ No newline at end of file</diff>
      <filename>script/breakpointer</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
 require 'commands/console'
\ No newline at end of file</diff>
      <filename>script/console</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
 require 'commands/destroy'
\ No newline at end of file</diff>
      <filename>script/destroy</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
 require 'commands/generate'
\ No newline at end of file</diff>
      <filename>script/generate</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../../config/boot'
 require 'commands/performance/benchmarker'</diff>
      <filename>script/performance/benchmarker</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../../config/boot'
 require 'commands/performance/profiler'</diff>
      <filename>script/performance/profiler</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../../config/boot'
 require 'commands/process/reaper'</diff>
      <filename>script/process/reaper</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../../config/boot'
 require 'commands/process/spawner'</diff>
      <filename>script/process/spawner</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../../config/boot'
 require 'commands/process/spinner'</diff>
      <filename>script/process/spinner</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
 require 'commands/runner'
\ No newline at end of file</diff>
      <filename>script/runner</filename>
    </modified>
    <modified>
      <diff>@@ -1,3 +1,3 @@
-#!/usr/local/bin/ruby
+#!/usr/bin/env ruby
 require File.dirname(__FILE__) + '/../config/boot'
 require 'commands/server'
\ No newline at end of file</diff>
      <filename>script/server</filename>
    </modified>
    <modified>
      <diff>@@ -5,203 +5,180 @@ require 'projects_controller'
 class ProjectsController; def rescue_action(e) raise e end; end
 
 class ProjectsControllerTest &lt; Test::Unit::TestCase
-  fixtures :projects
-
+  FULL_PAGES = [:index]
+  POPUPS = [:new,:create,:add_users,:update_users,:edit,:update]
+  NO_RENDERS = [:remove_user,:delete]
+  ALL_ACTIONS = FULL_PAGES + POPUPS + NO_RENDERS
+  
   def setup
+    Project.destroy_all
+    User.destroy_all
+    create_common_fixtures :admin, :user_one, :project_one
+    @user_two = User.create 'username' =&gt; 'usertwo',
+                            'password' =&gt; 'usertwopass',
+                            'email' =&gt; 'usertwo@example.com',
+                            'first_name' =&gt; 'User', 'last_name' =&gt; 'Two'
     @controller = ProjectsController.new
-    @request    = ActionController::TestRequest.new
-    @response   = ActionController::TestResponse.new
-    @admin      = User.find(1)
-    @user       = User.find(2)
+    @request = ActionController::TestRequest.new
+    @response = ActionController::TestResponse.new
+    @request.session[:current_user] = @admin
   end
 
-  def test_index
-    @request.session[ :current_user_id ] = @user.id
-    get :index
-    assert_response :success
-    assert_template 'list'
-  end
-  
-  def test_index_no_auth
-    get :index
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'login'
-    assert_equal &quot;Please log in, and we'll send you right along.&quot;, 
-                                                        flash[ :status ]
+  def test_authentication_required
+    @request.session[:current_user] = nil
+    ALL_ACTIONS.each do |a|
+      process a
+      assert_redirected_to :controller =&gt; 'session', :action =&gt; 'login'
+      assert session[:return_to]
+    end
   end
-  
 
-  def test_list
-    @request.session[ :current_user_id ] = @user.id
-    get :list
-    assert_response :success
-    assert_template 'list'
-    assert_not_nil assigns(:projects)
+  def test_admin_required
+    @request.session[:current_user] = @user_one
+    ALL_ACTIONS.each do |a|
+      process a
+      assert_redirected_to :controller =&gt; 'error', :action =&gt; 'index'
+      assert_equal &quot;You must be logged in as an administrator to &quot; +
+                   &quot;perform the requested action.&quot;,
+                   flash[:error]
+    end
   end
 
-  def test_show
-    @request.session[ :current_user_id ] = @user.id
-    get :show, :id =&gt; 1
+  def test_index
+    get :index
     assert_response :success
-    assert_template 'show'
-    assert_not_nil assigns(:project)
-    assert assigns(:project).valid?
+    assert_template 'index'
+    assert assigns(:projects)
+    assert_equal Project.find_all(nil,'name ASC'), assigns(:projects)
   end
 
-  def test_new_as_user
-    @request.session[ :current_user_id ] = @user.id
+  def test_new
     get :new
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+    assert_response :success
+    assert_template 'new'
+    assert assigns(:project)
+    assert_kind_of Project, assigns(:project)
+    assert assigns(:project).new_record?
   end
 
-  def test_new_as_admin
-    @request.session[ :current_user_id ] = @admin.id
+  def test_new_from_error
+    project = Project.create
+    assert !project.valid?
+    @request.session[:new_project] = project
     get :new
     assert_response :success
     assert_template 'new'
-    assert_not_nil assigns(:project)
+    assert assigns(:project)
+    assert_equal project, assigns(:project)
+    assert_nil session[:new_project]
   end
 
-
-  def test_create_as_user
-    @request.session[ :current_user_id ] = @user.id
-    num_projects = Project.count
-    post :create, :project =&gt; {}
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+  def test_create_no_membership
+    num_before_create = Project.count
+    mem_num_before_create = current_user.projects.size
+    post :create, 'project' =&gt; { 'name' =&gt; 'Test Create',
+                                 'description' =&gt; '' }
+    assert_response :success
+    assert_template 'layouts/refresh_parent_close_popup'
+    assert_equal num_before_create + 1, Project.count
+    assert_equal mem_num_before_create, current_user.projects.size
   end
 
-  def test_create_as_admin
-    @request.session[ :current_user_id ] = @admin.id
-    num_projects = Project.count
-    post :create, :project =&gt; {
-      'name' =&gt; 'New Test Project'
-    }
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'list'
-    assert_equal num_projects + 1, Project.count
+  def test_create_add_membership
+    num_before_create = Project.count
+    mem_num_before_create = current_user.projects.size
+    post :create, 'add_me' =&gt; '1', 'project' =&gt; { 'name' =&gt; 'Test Create',
+                                                  'description' =&gt; '' }
+    assert_response :success
+    assert_template 'layouts/refresh_parent_close_popup'
+    assert_equal num_before_create + 1, Project.count
+    assert_equal mem_num_before_create + 1, current_user.projects.size
   end
 
-  def test_edit_as_user
-    @request.session[ :current_user_id ] = @user.id
-    get :edit, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+  def test_create_with_errors
+    num_before_create = Project.count
+    post :create
+    assert_redirected_to :controller =&gt; 'projects', :action =&gt; 'new'
+    assert session[:new_project]
+    assert_equal num_before_create, Project.count
   end
 
-  def test_edit_as_admin
-    @request.session[ :current_user_id ] = @admin.id
-    get :edit, :id =&gt; 1
+  def test_add_users
+    get :add_users, 'project_id' =&gt; @project_one.id
     assert_response :success
-    assert_template 'edit'
-    assert_not_nil assigns(:project)
-    assert assigns(:project).valid?
+    assert assigns(:project)
+    assert_equal @project_one, assigns(:project)
+    assert_template 'add_users'
+    assert assigns(:available_users)
+    assert_equal [@user_one,@user_two,@admin], assigns(:available_users)
   end
 
-  def test_update_as_user
-    @request.session[ :current_user_id ] = @user.id
-    post :update, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+  def test_update_users
+    post :update_users, 'project_id' =&gt; @project_one.id,
+         'selected_users' =&gt; [@user_one.id, @user_two.id]                         
+    assert_response :success
+    assert_template 'layouts/refresh_parent_close_popup'
+    assert flash[:status]
+    [@user_one, @user_two].each do |u|
+      assert @project_one.users.include?(u)
+    end
   end
 
-  def test_update_as_admin
-    @request.session[ :current_user_id ] = @admin.id
-    post :update, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'show', :id =&gt; 1
+  def test_remove_user
+    @project_one.users &lt;&lt; @user_one
+    get :remove_user, 'project_id' =&gt; @project_one.id, 'id' =&gt; @user_one.id
+    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'index',
+                         :project_id =&gt; @project_one.id
+    assert flash[:status]
+    assert !@project_one.users(true).include?(@user_one)
   end
 
+  def test_delete
+    get :delete, 'id' =&gt; @project_one.id
+    assert_redirected_to :controller =&gt; 'projects', :action =&gt; 'index'
+    assert flash[:status]
+    assert_raise(ActiveRecord::RecordNotFound) { Project.find(@project_one.id) }
+  end
 
-  def test_destroy_as_user
-    @request.session[ :current_user_id ] = @user.id
-    assert_not_nil Project.find(1)
-    post :destroy, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+  def test_edit
+    get :edit, 'id' =&gt; @project_one.id
+    assert_response :success
+    assert_template 'edit'
+    assert assigns(:project)
+    assert_equal @project_one, assigns(:project)
   end
-  
-  def test_destroy_as_admin
-    @request.session[ :current_user_id ] = @admin.id
-    assert_not_nil Project.find(2)
-    post :destroy, :id =&gt; 2
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'list'
-    assert_raise(ActiveRecord::RecordNotFound) {
-      Project.find(2)
-    }
+
+  def test_edit_from_invalid
+    @request.session[:edit_project] = @project_one
+    get :edit, 'id' =&gt; @project_one.id
+    assert_kind_of Project, assigns(:project)
+    assert_equal @project_one.id, assigns(:project).id
+    assert_nil session[:edit_project]
   end
-  
-  def test_create_with_no_name
-    @request.session[ :current_user_id ] = @admin.id
-    num_projects = Project.count
-    post :create, :project =&gt; {
-      'name' =&gt; nil
-    }
-    # The response is success because this is calling the method 'create' and
-    # the validation is rejecting the nil and then reloading the 'create'
-    # method.
+
+  def test_update
+    post :update, 'id' =&gt; @project_one.id, 'project' =&gt; { 'name' =&gt; 'Test' }
     assert_response :success
-    
-    assert_tag :tag =&gt; &quot;div&quot;, :attributes =&gt; { :class =&gt; &quot;errorExplanation&quot;}
-    assert_tag :tag =&gt; &quot;div&quot;, :attributes =&gt; { :class =&gt; &quot;fieldWithErrors&quot;}
-    assert_not_equal num_projects + 1, Project.count
+    assert_template 'layouts/refresh_parent_close_popup'
+    project = Project.find(@project_one.id)
+    assert_equal 'Test', project.name
   end
-  
-  def test_view_project_team
-    @request.session[ :current_user_id ] = @user.id
-    get :team, :id =&gt; 1
+
+  def test_my_projects_list
+    @project_one.users &lt;&lt; @user_one
+    project2 = Project.create('name' =&gt; 'Project Two')
+    project2.users &lt;&lt; @user_one
+    @request.session[:current_user] = @user_one
+    process :my_projects_list
     assert_response :success
-    assert_template 'team'
-    assert_tag :tag =&gt; &quot;td&quot;, :attributes =&gt; { :class =&gt; &quot;name&quot;}
-    assert_tag :tag =&gt; &quot;td&quot;, :attributes =&gt; { :class =&gt; &quot;email&quot;}
-  end
-
-### Implemented before the change to the story card.  Currently working on
-###  -- Eric     
-#  def test_show_users_to_add_to_project_team
-#    @request.session[ :current_user_id ] = @admin.id
-#    get :add_user, :id =&gt; 1
-#    assert_response :success
-#    assert_template 'add_user'    
-#  end
-
-### Implemented before the change to the story card.  Currently working on
-###  -- Eric   
-#  def test_add_user_to_project_team
-#    @request.session[ :current_user_id ] = @admin.id
-#    post :add_user, :id =&gt; 1
-#    assert_response :redirect
-#    assert_redirected_to :action =&gt; 'team'
-#    assert_equal 'Added user to project team', flash[ :notice ]
-#  end
-
-  def test_remove_user_from_project_team
-    @request.session[ :current_user_id ] = @admin.id
-    post :remove_user, :id =&gt; 1, :user_id =&gt; 2
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'team'
-    assert_equal 'User was successfully removed from the project.',
-                     flash[ :notice ]
-  end   
-
-  def test_remove_user_from_project_team_as_user
-    @request.session[ :current_user_id ] = @user.id
-    post :remove_user, :id =&gt; 1, :user_id =&gt; 2
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+    assert_template '_my_projects_list'
+    assert assigns(:projects).include?(@project_one)
+    assert assigns(:projects).include?(project2)
+  end
+
+  private
+
+  def current_user
+    User.find(@request.session[:current_user].id)
   end
 end</diff>
      <filename>test/functional/projects_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,205 +5,196 @@ require 'users_controller'
 class UsersController; def rescue_action(e) raise e end; end
 
 class UsersControllerTest &lt; Test::Unit::TestCase
-  fixtures :users
-
   def setup
+    User.destroy_all
+    Project.destroy_all
+    create_common_fixtures :admin, :user_one
+    @project_one = Project.create('name' =&gt; 'Project One')
     @controller = UsersController.new
-    @request    = ActionController::TestRequest.new
-    @response   = ActionController::TestResponse.new
-    @admin      = User.find(1)
-    @user       = User.find(2)
+    @request = ActionController::TestRequest.new
+    @response = ActionController::TestResponse.new
+    @request.session[:current_user] = @user_one
+  end
+
+  def test_authentication_required
+    @request.session[:current_user] = nil
+    actions = [:index, :new, :create, :edit, :update, :delete]
+    actions.each do |a|
+      process a
+      assert_redirected_to :controller =&gt; 'session', :action =&gt; 'login'
+      assert session[:return_to]
+      assert_equal &quot;Please log in, and we'll send you right along.&quot;,
+                   flash[:status]
+    end
+  end
+
+  def test_admin_required
+    actions = [:new, :create, :edit, :update, :delete]
+    actions.each do |a|
+      process a
+      assert_redirected_to :controller =&gt; 'error', :action =&gt; 'index'
+      assert_equal &quot;You must be logged in as an administrator to perform &quot; +
+                   &quot;the requested action.&quot;, flash[:error]
+    end
+  end
+
+  def test_admin_not_required_on_edit_if_id_is_session_user
+    get :edit, 'id' =&gt; @user_one.id
+    assert_response :success
+    post :update, 'id' =&gt; @user_one.id, 'user' =&gt; {}
+    assert_response :success
+    user = User.find(@user_one.id)
+    assert !user.admin?
   end
 
-  def test_index_for_admin
-    @request.session[ :current_user_id ] = @admin.id
-    get :index
+  def test_admin_cannot_remove_own_admin_privileges
+    @request.session[:current_user] = @admin
+    get :edit, 'id' =&gt; @admin.id, 'user' =&gt; {'admin' =&gt; '0'}
     assert_response :success
-    assert_template 'list'
+    user = User.find(@admin.id)
+    assert user.admin?
   end
 
-  def test_index_for_user
-    @request.session[ :current_user_id ] = @user.id
+  def test_index
     get :index
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+    assert_template 'index'
+    assert_equal User.find_all(nil, 'last_name ASC, first_name ASC'),
+                 assigns(:users)
   end
 
-  def test_list
-    @request.session[ :current_user_id ] = @admin.id
-    get :list
-
-    assert_response :success
-    assert_template 'list'
-
-    assert_not_nil assigns(:users)
+  def test_index_with_project_id
+    @request.session[:current_user] = @admin
+    get :index, 'project_id' =&gt; @project_one.id
+    assert_template 'project'
+    assert_equal @project_one, assigns(:project)
   end
 
-  def test_show_to_user
-    @request.session[ :current_user_id ] = @user.id
-    get :show, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]  
-  end
-  
-  def test_show_self_user
-    @request.session[ :current_user_id ] = @user.id
-    get :show, :id =&gt; 2
+  def test_new
+    @request.session[:current_user] = @admin
+    get :new
     assert_response :success
-    assert_template 'show'
-    assert_not_nil assigns(:user)
-    assert assigns(:user).valid? 
+    assert_template 'new'
+    assert_kind_of User, assigns(:user)
+    assert assigns(:user).new_record?
   end
 
-  def test_show_to_admin
-    @request.session[ :current_user_id ] = @admin.id
-    get :show, :id =&gt; 1
-    assert_response :success
-    assert_template 'show'
-    assert_not_nil assigns(:user)
-    assert assigns(:user).valid?  
+  def test_new_from_invalid
+    @request.session[:current_user] = @admin
+    @request.session[:new_user] = User.new('last_name' =&gt; 'Foo')
+    get :new
+    assert_kind_of User, assigns(:user)
+    assert assigns(:user).new_record?
+    assert_equal 'Foo', assigns(:user).last_name
+    assert_nil session[:new_user]
   end
 
-  def test_new_using_admin
-    @request.session[ :current_user_id ] = @admin.id
-    get :new
+  def test_new_with_project_id
+    @request.session[:current_user] = @admin
+    get :new, 'project_id' =&gt; @project_one.id
     assert_response :success
     assert_template 'new'
-    assert_not_nil assigns(:user)
-  end
-  
-  def test_new_using_user
-    @request.session[ :current_user_id ] = @user.id
-    get :new
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+    assert_kind_of User, assigns(:user)
+    assert assigns(:user).new_record?
+    assert_equal @project_one, assigns(:project)
+    assert_tag :tag =&gt; 'input', :attributes =&gt; { :type =&gt; 'hidden',
+      :name =&gt; 'project_id', :value =&gt; @project_one.id }
   end
 
   def test_create
-    @request.session[ :current_user_id ] = @admin.id
-    
+    @request.session[:current_user] = @admin
     num_users = User.count
+    post :create, 'user' =&gt; { 'username' =&gt; 'test_create',
+                              'password' =&gt; 'test_create_password',
+                              'password_confirmation' =&gt;
+                              'test_create_password',
+                              'email' =&gt; 'test_create@example.com',
+                              'first_name' =&gt; 'Test',
+                              'last_name' =&gt; 'Create' }
+    assert_response :success
+    assert_template 'layouts/refresh_parent_close_popup'
+    assert_equal num_users + 1, User.count
+  end
 
-    post :create, :user =&gt; {
-      'login' =&gt; 'test_create', 'password' =&gt; 'password',
-      'password_confirmation' =&gt; 'password', 'email' =&gt; 'test@example.com',
-      'name' =&gt; 'Test Create'
-    }
-
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'list'
+  def test_create_invalid
+    @request.session[:current_user] = @admin
+    num_users = User.count
+    post :create, 'user' =&gt; {}
+    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'new'
+    assert_kind_of User, session[:new_user]
+    assert session[:new_user].new_record?
+    assert_equal num_users, User.count
+  end
 
+  def test_create_with_project_id
+    @request.session[:current_user] = @admin
+    num_users = User.count
+    post :create, 'project_id' =&gt; @project_one.id,
+         'user' =&gt; { 'username' =&gt; 'test_create_with_project',
+                     'password' =&gt; 'test_create_password',
+                     'password_confirmation' =&gt;
+                     'test_create_password',
+                     'email' =&gt; 'test_create@example.com',
+                     'first_name' =&gt; 'Test',
+                     'last_name' =&gt; 'Create' }         
+    assert_response :success
+    assert_template 'layouts/refresh_parent_close_popup'
     assert_equal num_users + 1, User.count
+    user = User.find(:first,
+                     :conditions =&gt; [ 'username = ?',
+                                      'test_create_with_project' ]) 
+    assert @project_one.users.include?(user)
   end
 
-  def test_edit_from_user
-    @request.session[ :current_user_id ] = @user.id
-    get :edit, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
+  def test_create_invalid_with_project_id
+    @request.session[:current_user] = @admin
+    num_users = User.count
+    post :create, 'user' =&gt; {}, 'project_id' =&gt; @project_one.id
+    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'new',
+                         :project_id =&gt; @project_one.id
+    assert_kind_of User, session[:new_user]
+    assert session[:new_user].new_record?
+    assert_equal num_users, User.count
   end
-  
-  def test_edit_from_admin
-    @request.session[ :current_user_id ] = @admin.id
-    get :edit, :id =&gt; 1
+
+  def test_edit
+    @request.session[:current_user] = @admin
+    get :edit, 'id' =&gt; @user_one.id
     assert_response :success
     assert_template 'edit'
-    assert_not_nil assigns(:user)
-    assert assigns(:user).valid?
+    assert_equal @user_one, assigns(:user)
   end
-  
-  def test_edit_self_from_user
-    @request.session[ :current_user_id ] = @user.id
-    get :edit, :id =&gt; 2
-    assert_response :success
-    assert_template 'edit'
-    assert_not_nil assigns(:user)
-    assert assigns(:user).valid?
+
+  def test_edit_from_invalid
+    @request.session[:current_user] = @admin
+    @user_one.username = nil
+    @request.session[:edit_user] = @user_one
+    get :edit, 'id' =&gt; @user_one.id
+    assert_equal @user_one, assigns(:user)
+    assert_nil session[:edit_user]
   end
 
   def test_update
-    @request.session[ :current_user_id ] = @admin.id
-    post :update, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'show', :id =&gt; 1
+    @request.session[:current_user] = @admin
+    post :update, 'id' =&gt; @user_one.id, 'user' =&gt; {'last_name' =&gt; 'Foo'}
+    assert_response :success
+    assert_template 'layouts/refresh_parent_close_popup'
+    assert_equal 'Foo', User.find(@user_one.id).last_name
   end
 
-  def test_destroy
-    @request.session[ :current_user_id ] = @admin.id
-
-    assert_not_nil User.find(3)
-
-    post :destroy, :id =&gt; 3
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'list'
+  def test_update_invalid
+    @request.session[:current_user] = @admin
+    post :update, 'id' =&gt; @user_one.id, 'user' =&gt; {'username' =&gt; ''}
+    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'edit',
+                         :id =&gt; @user_one.id
+    @user_one.username = ''
+    assert_equal @user_one, session[:edit_user]
+  end
 
+  def test_delete
+    @request.session[:current_user] = @admin
+    get :delete, :id =&gt; @user_one.id
     assert_raise(ActiveRecord::RecordNotFound) {
-      User.find(3)
+      User.find(@user_one.id)
     }
   end
-  
-  def test_destroy_self_from_user
-    @request.session[ :current_user_id ] = @user.id
-    assert_not_nil User.find(2)
-    post :destroy, :id =&gt; 2
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'no_admin'
-    assert_equal 'You must be logged in as an administrator to perform'+
-                  ' this action', flash[ :error ]
-    assert_equal &quot;user&quot;, User.find(2).login  
-  end
-
-  def test_destroy_self_from_admin
-    @request.session[ :current_user_id ] = @admin.id
-    assert_not_nil User.find(1)
-    post :destroy, :id =&gt; 1
-    assert_response :redirect
-    assert_redirected_to :action =&gt; 'list'
-    assert_equal 'You may not delete your own account', flash[ :error ]
-    assert_equal &quot;admin&quot;, User.find(1).login  
-  end
-  
-  def test_login
-    get :login
-    assert_template 'login'
-  end
-  
-  def test_authenticate
-    post :authenticate, 'login' =&gt; @admin.login, 'password' =&gt; @admin.password
-    assert_equal @admin.id, session[ :current_user_id ]
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'main', :action =&gt; 'dashboard'
-  end
-  
-  def test_fail_authenticate
-    post :authenticate, 'login' =&gt; @admin.login, 'password' =&gt; 'foo'
-    assert_nil session[ :current_user_id ]
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'login'
-    assert_equal 'You entered an invalid username and/or password.', 
-                 flash[ :error]
-  end
-  
-  def test_logout
-    @request.session[ :current_user_id ] = @user.id
-    get :logout
-    assert_nil session[ :current_user_id ]
-    assert_response :redirect
-    assert_redirected_to :controller =&gt; 'users', :action =&gt; 'login'
-    assert_equal 'You have been logged out', flash[ :notice ]
-  end
-  
-  def test_no_admin_error
-    @request.session[ :current_user_id ] = @user.id
-    get :no_admin
-    assert_template 'no_admin'
-  end
 end</diff>
      <filename>test/functional/users_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -4,10 +4,92 @@ require 'test_help'
 
 class Test::Unit::TestCase
   # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
-  self.use_transactional_fixtures = true
-  
+  self.use_transactional_fixtures = false
+
   # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
-  self.use_instantiated_fixtures  = false
+  self.use_instantiated_fixtures  = true
 
   # Add more helper methods to be used by all tests here...
-end
\ No newline at end of file
+
+  private
+  def create_common_fixtures(*fixture_names)
+    fixture_names.each do |name|
+      self.send(&quot;fixture_#{name}&quot;.to_sym)
+    end
+  end
+
+  def fixture_admin
+    @admin = User.create('username' =&gt; 'admin', 'password' =&gt; 'adminpass',
+                         'email' =&gt; 'admin@example.com', 'admin' =&gt; 1,
+                         'first_name' =&gt; 'Admin', 'last_name' =&gt; 'User')
+  end
+
+  def fixture_user_one
+    @user_one = User.create('username' =&gt; 'user_one',
+                            'password' =&gt; 'user_onepassword',
+                            'email' =&gt; 'user_one@example.com',
+                            'first_name' =&gt; 'User', 'last_name' =&gt; 'One',
+                            'admin' =&gt; 0)
+  end
+
+  def fixture_project_one
+    @project_one = Project.create('name' =&gt; 'Project One')
+  end
+
+  def fixture_project_two
+    @project_two = Project.create('name' =&gt; 'Project Two')
+  end
+
+  def fixture_story_one
+    @story_one = Story.create('title' =&gt; 'Story One',
+                              'description' =&gt; 'Story One',
+                              'priority' =&gt; Story::Priority::High,
+                              'risk' =&gt; Story::Risk::High,
+                              'points' =&gt; 1,
+                              'status' =&gt; Story::Status::Defined)
+  end
+
+  def fixture_story_two
+    @story_two = Story.create('title' =&gt; 'Story Two',
+                              'description' =&gt; 'Story Two',
+                              'priority' =&gt; Story::Priority::High,
+                              'risk' =&gt; Story::Risk::High,
+                              'points' =&gt; 1,
+                              'status' =&gt; Story::Status::Defined)
+  end
+
+  def fixture_iteration_one
+    @iteration_one = Iteration.create('start_date' =&gt; Date.today,
+                                      'length' =&gt; 14)
+  end
+
+  def fixture_past_milestone1
+    @past_milestone1 = @project_one.milestones.create('name' =&gt; 'Milestone1',
+                                                      'date' =&gt; Date.today - 365)
+  end
+
+  def fixture_past_milestone2
+    @past_milestone2 = @project_one.milestones.create('name' =&gt; 'Milestone1',
+                                                      'date' =&gt; Date.today - 15)
+  end
+
+  def fixture_recent_milestone1
+    @recent_milestone1 = @project_one.milestones.create('name' =&gt; 'Milestone1',
+                                                        'date' =&gt; Date.today - 14)
+  end
+
+  def fixture_recent_milestone2
+    @recent_milestone2 = @project_one.milestones.create('name' =&gt; 'Milestone1',
+                                                        'date' =&gt; Date.today - 1)
+  end
+
+  def fixture_future_milestone1
+    @future_milestone1 = @project_one.milestones.create('name' =&gt; 'Milestone1',
+                                                        'date' =&gt; Date.today)
+  end
+
+  def fixture_future_milestone2
+    @future_milestone2 = @project_one.milestones.create('name' =&gt; 'Milestone1',
+                                                        'date' =&gt; Date.today + 365)
+  end
+end</diff>
      <filename>test/test_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,9 +1,36 @@
-require File.dirname(__FILE__) + '/../test_helper'
-
-class MilestoneTest &lt; Test::Unit::TestCase
-  fixtures :projects, :milestones
-
-  def test_project_relationship
-    assert_equal Project.find( 1 ), Milestone.find( 1 ).project
-  end
-end
+require File.dirname(__FILE__) + '/../test_helper'
+
+class MilestoneTest &lt; Test::Unit::TestCase
+  def setup
+    create_common_fixtures :project_one, :past_milestone1, :past_milestone2,
+                           :recent_milestone1, :recent_milestone2,
+                           :future_milestone1, :future_milestone2
+  end
+  def test_future_eh
+    assert !@past_milestone1.future?
+    assert !@past_milestone2.future?
+    assert !@recent_milestone1.future?
+    assert !@recent_milestone2.future?
+    assert @future_milestone1.future?
+    assert @future_milestone2.future?
+  end
+  
+  def test_recent_eh
+    assert !@past_milestone1.recent?
+    assert !@past_milestone2.recent?
+    assert @recent_milestone1.recent?
+    assert @recent_milestone2.recent?
+    assert !@future_milestone1.recent?
+    assert !@future_milestone2.recent?
+  end
+  
+  def test_past_eh
+    assert @past_milestone1.past?
+    assert @past_milestone2.past?
+    assert @recent_milestone1.past?
+    assert @recent_milestone2.past?
+    assert !@future_milestone1.past?
+    assert !@future_milestone2.past?
+  end
+end
+</diff>
      <filename>test/unit/milestone_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,65 +1,140 @@
 require File.dirname(__FILE__) + '/../test_helper'
 
 class ProjectTest &lt; Test::Unit::TestCase
-  fixtures :projects, :users, :projects_users, :story_cards
-
   def setup
-    @project_one = Project.find 1
-    @project_two = Project.find 2
+    Project.destroy_all
+    create_common_fixtures :project_one, :past_milestone1, :past_milestone2,
+                           :recent_milestone1, :recent_milestone2,
+                           :future_milestone1, :future_milestone2
   end
+  
+  def test_current_iteration
+    assert_nil @project_one.current_iteration(true)
+    assert_nil @project_one.current_iteration
 
-
-  def test_users_relationship
-    user_1, user_2, user_3 = User.find :all, :order =&gt; 'id', :limit =&gt; 3
-    project_1, project_2 = Project.find :all, :order =&gt; 'id', :limit =&gt; 2
-
-    assert_equal [ user_1, user_2 ], project_1.users.sort { |u1,u2| u1.id &lt;=&gt; u2.id }
-    assert_equal [ user_2, user_3 ], project_2.users.sort { |u1,u2| u1.id &lt;=&gt; u2.id }
+    iteration = @project_one.iterations.create('start_date' =&gt; Date.today,
+                                               'length' =&gt; 14)
+    iteration.save
+    assert_equal iteration, @project_one.current_iteration(true)
+    assert_equal iteration, @project_one.current_iteration
+    iteration.start_date = (Date.today - 10)
+    iteration.save
+    assert_equal iteration, @project_one.current_iteration(true)
+    assert_equal iteration, @project_one.current_iteration
+    iteration.start_date = (Date.today + 1)
+    iteration.save
+    assert_nil @project_one.current_iteration(true)
+    assert_nil @project_one.current_iteration
+  end
+  
+  def test_previous_iteration
+    assert_nil @project_one.previous_iteration(true)
+    assert_nil @project_one.previous_iteration
+    iteration = @project_one.iterations.create('start_date' =&gt; (Date.today - 1),
+                                               'length' =&gt; 2)
+    assert_nil @project_one.previous_iteration(true)
+    assert_nil @project_one.previous_iteration
+    iteration.length = 1
+    iteration.save
+    assert_equal iteration, @project_one.previous_iteration(true)
+    assert_equal iteration, @project_one.previous_iteration
+    iteration2 = @project_one.iterations.create(
+      'start_date' =&gt; (Date.today - 30),
+      'length' =&gt; 1
+    )
+    @project_one.iterations.create('start_date' =&gt; Date.today, 'length' =&gt; 1)
+    assert_equal iteration, @project_one.previous_iteration(true)
+    assert_equal iteration, @project_one.previous_iteration
+    iteration.destroy
+    assert_equal iteration2, @project_one.previous_iteration(true)
+    assert_equal iteration2, @project_one.previous_iteration
+    iteration2.destroy
+    assert_nil @project_one.previous_iteration(true)
+    assert_nil @project_one.previous_iteration
   end
 
-  def test_story_cards_relationship
-    sca, scb, scc, scd = StoryCard.find :all, :order =&gt; 'id', :limit =&gt; 4
-    p1, p2 = Project.find :all, :order =&gt; 'id', :limit =&gt; 2
+  def test_next_iteration
+    assert_nil @project_one.next_iteration(true)
+    assert_nil @project_one.next_iteration
+    iteration = @project_one.iterations.create('start_date' =&gt; Date.today,
+                                               'length' =&gt; 1)
+    assert_nil @project_one.next_iteration(true)
+    assert_nil @project_one.next_iteration
+    iteration.start_date = Date.today + 1
+    iteration.save
+    assert_equal iteration, @project_one.next_iteration(true)
+    assert_equal iteration, @project_one.next_iteration
+    @project_one.iterations.create('start_date' =&gt; Date.today, 'length' =&gt; 1)
+    @project_one.iterations.create('start_date' =&gt; Date.today - 1,
+                                   'length' =&gt; 1)
+    assert_equal iteration, @project_one.next_iteration(true)
+    assert_equal iteration, @project_one.next_iteration
+    iteration.destroy
+    assert_nil @project_one.next_iteration(true)
+    assert_nil @project_one.next_iteration
+    iteration = @project_one.iterations.create(iteration.attributes)
+    @project_one.iterations.create('start_date' =&gt; Date.today + 5,
+                                   'length' =&gt; 1)
+    assert_equal iteration, @project_one.next_iteration(true)
+    assert_equal iteration, @project_one.next_iteration
+  end
 
-    assert_equal [ sca, scb ], p1.story_cards.sort { |a,b| a.id &lt;=&gt; b.id }
-    assert_equal [ scc, scd ], p2.story_cards.sort { |a,b| a.id &lt;=&gt; b.id }
+  def test_backlog
+    assert @project_one.backlog(true).empty?
+    4.times do |i|
+      @project_one.stories.create('title' =&gt; &quot;Test Backlog #{i}&quot;,
+                                  'status' =&gt; Story::Status::New)
+    end
+    assert_equal 4, @project_one.backlog(true).size
+    iteration = @project_one.iterations.create('start_date' =&gt; Date.today,
+                                               'length' =&gt; 1)
+    story = @project_one.stories.create('title' =&gt; &quot;Test Backlog&quot;)
+    story.priority = Story::Priority::High
+    story.risk = Story::Risk::Low
+    story.points = 4
+    story.description = 'description'
+    story.save
+    assert_equal 5, @project_one.backlog(true).size
+    story.iteration = iteration
+    story.save
+    assert_equal 4, @project_one.backlog(true).size
   end
 
-  def test_milestones_relationship
-    milestones = Milestone.find :all, :order =&gt; 'id',
-      :conditions =&gt; [ 'project_id = ?', 1 ]
-    project = Project.find 1
-    assert_equal milestones, project.milestones.sort { |a,b| a.id &lt;=&gt; b.id }
+  def test_future_iterations
+    assert @project_one.future_iterations(true).empty?
+    assert @project_one.future_iterations.empty?
+    (-1..3).to_a.each do |i|
+      @project_one.iterations.create('start_date' =&gt; Date.today + i,
+                                     'length' =&gt; 1)
+    end
+    assert_equal 3, @project_one.future_iterations(true).size
+    assert_equal 3, @project_one.future_iterations.size
   end
   
-  # Start CRUD
-  def test_create_project
-    new_project = Project.new
-    new_project.id = 3
-    new_project.name = &quot;Third Project&quot;  
-    assert new_project.save
-    assert_equal 3, Project.find(3).id
-    assert_equal &quot;Third Project&quot;, Project.find(3).name
+  def test_past_iterations
+    assert @project_one.past_iterations(true).empty?
+    assert @project_one.past_iterations.empty?
+    (-3..1).to_a.each do |i|
+      @project_one.iterations.create('start_date' =&gt; Date.today + i,
+                                     'length' =&gt; 1)
+    end
+    assert_equal 3, @project_one.past_iterations(true).size
+    assert_equal 3, @project_one.past_iterations.size
   end
   
-  def test_read_project
-    assert_equal &quot;First Project&quot;, @project_one.name
-    assert @project_one.id, 1
+  def test_future_milestones
+    assert_equal [ @future_milestone1, @future_milestone2 ],
+                 @project_one.future_milestones
   end
-
-  def test_update_project
-    assert_equal &quot;First Project&quot;, @project_one.name
-    @project_one.name = &quot;Changed Project&quot;
-    assert @project_one.save
-    assert &quot;Changed Project&quot;, @project_one.name
+  
+  def test_recent_milestones
+    assert_equal [ @recent_milestone2, @recent_milestone1 ],
+                 @project_one.recent_milestones
   end
-
-  def test_destroy_project
-    @project_one.destroy
-    assert_raise(ActiveRecord::RecordNotFound) { Project.find(@project_one.id) }
-    @project_two.destroy
-    assert_raise(ActiveRecord::RecordNotFound) { Project.find(@project_two.id) }
+  
+  def test_past_milestones
+    assert_equal [ @recent_milestone2, @recent_milestone1, @past_milestone2,
+                   @past_milestone1 ], @project_one.past_milestones
   end
-  # End CRUD
 end
 </diff>
      <filename>test/unit/project_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,85 +1,38 @@
 require File.dirname(__FILE__) + '/../test_helper'
 
 class UserTest &lt; Test::Unit::TestCase
-  fixtures :users, :projects, :projects_users, :story_cards
-
   def setup
-    @admin = User.find 1
-    @user = User.find 2
-    @admin2 = User.find 3
-  end
-
-  # Replace this with your real tests.
-  def test_truth
-    assert_kind_of User,  @user
-  end
-
-  def test_create_user
-    new_user = User.new
-    new_user.id = 4
-    new_user.admin = 0
-    new_user.login = &quot;user_four&quot;
-    new_user.password = &quot;user_four&quot;
-    new_user.email = &quot;userfour@localhost&quot;
-    new_user.name = &quot;User Four&quot;  
-    assert new_user.save
-    assert_equal 4, User.find(4).id
-    assert_equal &quot;User Four&quot;, User.find(4).name
+    User.destroy_all
+    create_common_fixtures :user_one
   end
   
-  def test_read_user
-    assert_equal &quot;admin&quot;, @admin.login
-    assert @admin.admin
-    assert_equal &quot;Admin&quot;, @admin.name
-  end
-
-  def test_update_user
-    assert_equal &quot;user&quot;, @user.login
-    assert_equal &quot;User&quot;, @user.name
-    @user.login = &quot;changed&quot;
-    @user.name = &quot;Who's name?&quot;
-    assert @user.save
-    assert &quot;changed&quot;, @user.login
-    assert &quot;Who's name?&quot;, @user.name  
+  def test_full_name
+    assert_equal(&quot;#{@user_one.first_name} #{@user_one.last_name}&quot;,
+                 @user_one.full_name)
+    assert_equal(&quot;#{@user_one.last_name}, #{@user_one.first_name}&quot;,
+                 @user_one.full_name(User::LastFirst))
   end
 
-  def test_destroy_user
-    @user.destroy
-    assert_raise(ActiveRecord::RecordNotFound) { User.find(@user.id) }
-  end
-  
   def test_authenticate
-    assert_equal @admin, User.authenticate( @admin.login, 'admin' )
-    assert_equal @user, User.authenticate( @user.login, 'user' )
+    assert_equal(@user_one,
+                 User.authenticate(@user_one.username, @user_one.password))
   end
 
-  def test_not_able_to_delete_last_admin_account
-    @admin.destroy
-    assert_equal 1, User.count( [ 'admin = ?', true ] )
-    assert !@admin2.destroy
-    assert_equal 1, User.count( [ 'admin = ?', true ] )
+  def test_authenticate_bad_password
+    assert_nil User.authenticate(@user_one.username, 'bad_password')
+    assert_nil User.authenticate(@user_one.username, nil)
   end
 
-  def test_project_relationship
-    project_1 = Project.find 1
-    project_2 = Project.find 2
-    
-    projects = @admin.projects.sort { |p1,p2| p1.id &lt;=&gt; p2.id }
-    assert_equal [ project_1 ], projects
-
-    projects = @user.projects.sort { |p1,p2| p1.id &lt;=&gt; p2.id }
-    assert_equal [ project_1, project_2 ], projects
-
-    projects = @admin2.projects.sort { |p1,p2| p1.id &lt;=&gt; p2.id }
-    assert_equal [ project_2 ], projects
+  def test_authenticate_bad_username
+    assert_nil User.authenticate('bad_username', @user_one.password)
+    assert_nil User.authenticate(nil, @user_one.password)
   end
 
-  def test_story_card_relationship
-    sca, scb, scc, scd = StoryCard.find :all, :order =&gt; 'id', :limit =&gt; 4
-    ua, ub, uc = User.find :all, :order =&gt; 'id', :limit =&gt; 3
-
-    assert_equal [ sca ], ua.story_cards
-    assert_equal [ scb, scc ], ub.story_cards.sort { |a,b| a.id &lt;=&gt; b.id }
-    assert_equal [ scd ], uc.story_cards
+  def test_authenticate_bad_username_and_password
+    assert_nil User.authenticate('bad_username', 'bad_password')
+    assert_nil User.authenticate(nil, nil)
   end
 end
+
+
+</diff>
      <filename>test/unit/user_test.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>app/controllers/main_controller.rb</filename>
    </removed>
    <removed>
      <filename>app/controllers/story_cards_controller.rb</filename>
    </removed>
    <removed>
      <filename>app/helpers/main_helper.rb</filename>
    </removed>
    <removed>
      <filename>app/helpers/story_cards_helper.rb</filename>
    </removed>
    <removed>
      <filename>app/models/story_card.rb</filename>
    </removed>
    <removed>
      <filename>app/views/layouts/projects.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/layouts/story_cards.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/layouts/users.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/main/dashboard.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/projects/_form.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/projects/list.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/projects/show.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/projects/team.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/story_cards/_form.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/story_cards/new.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/story_cards/show.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/users/_form.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/users/list.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/users/login.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/users/no_admin.rhtml</filename>
    </removed>
    <removed>
      <filename>app/views/users/show.rhtml</filename>
    </removed>
    <removed>
      <filename>db/migrate/1_add_users_table.rb</filename>
    </removed>
    <removed>
      <filename>db/migrate/2_add_projects_table.rb</filename>
    </removed>
    <removed>
      <filename>db/migrate/3_add_story_cards_table.rb</filename>
    </removed>
    <removed>
      <filename>db/migrate/4_add_milestones_table.rb</filename>
    </removed>
    <removed>
      <filename>lib/tasks/test_aliases.rake</filename>
    </removed>
    <removed>
      <filename>lib/tasks/test_all_dbs.rake</filename>
    </removed>
    <removed>
      <filename>lib/tasks/test_migrations.rake</filename>
    </removed>
    <removed>
      <filename>test/fixtures/milestones.yml</filename>
    </removed>
    <removed>
      <filename>test/fixtures/projects.yml</filename>
    </removed>
    <removed>
      <filename>test/fixtures/projects_users.yml</filename>
    </removed>
    <removed>
      <filename>test/fixtures/story_cards.yml</filename>
    </removed>
    <removed>
      <filename>test/fixtures/users.yml</filename>
    </removed>
    <removed>
      <filename>test/functional/main_controller_test.rb</filename>
    </removed>
    <removed>
      <filename>test/functional/routing_test.rb</filename>
    </removed>
    <removed>
      <filename>test/functional/story_cards_controller_test.rb</filename>
    </removed>
    <removed>
      <filename>test/unit/story_card_test.rb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>ad4ff571644c1f967a33d765359545d0f91bf095</id>
    </parent>
  </parents>
  <author>
    <name>John Wilger</name>
    <email>johnwilger@gmail.com</email>
  </author>
  <url>http://github.com/explainpmt/explainpmt/commit/175daa753fea9c939b1fe10ee2ce487ebbab51de</url>
  <id>175daa753fea9c939b1fe10ee2ce487ebbab51de</id>
  <committed-date>2006-10-26T22:31:26-07:00</committed-date>
  <authored-date>2006-10-26T22:31:26-07:00</authored-date>
  <message> r103@Woden (orig r99):  jwilger | 2005-12-16 06:38:17 -0800
 Moving the 1.x trunkline back to the main trunkline.
 


git-svn-id: http://explainpmt.googlecode.com/svn/trunk@101 1861e259-8220-0410-84d7-1f84cab42028</message>
  <tree>63ac62454a602fd6c9c229ff9e4fa6be677efae6</tree>
  <committer>
    <name>John Wilger</name>
    <email>johnwilger@gmail.com</email>
  </committer>
</commit>
