Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 0000000..799ee28
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,60 @@
+# Monorail Issue Tracker
+
+## What is Monorail?
+
+Monorail is the issue tracker used by Chromium-related projects on
+<https://bugs.chromium.org>.
+
+* Monorail is a port of the Google Code issue tracker to AppEngine. It provides
+continuity for the chrome team with the least number of unknowns, while achieving
+these goals:
+  * Vastly simpler to develop and operate:
+  * Runs on Google App Engine, data in Google Cloud SQL and GCS.
+  * Built on simpler GAE APIs.
+  * Pure python, greatly simplified code, supported by Chrome Operations team.
+* Better product focus:
+  * Focused on needs of larger teams: e.g., built-in support for user groups,
+    custom fields, more control over issue visibility, better validation to
+    improve issue data quality.
+  * Closely aligned with the chrome development process and dedicated to the
+    future needs of the chrome team.
+* Scaleable by design:
+  * Designed from the start to handle Chrome's future issue tracking needs.
+  * Efficient implementation of queries like issue ID and date ranges.
+  * Improved security and spam defenses.
+* Ease of transition:
+  * Same data model as used by our existing issues for an exact import.
+  * Our v1 API is very close to the old Google Code API.
+  * Same ACLs and project administration controls.
+  * Familiar UI, but streamlined.  One-to-one redirects from Google Code.
+
+## Monorail key features
+
+* Developed as open source.
+* Flexible issue representation that allows for creation of one-off labels.
+* Custom fields defined by project owners.
+* Support for components.  Issues can be in multiple components.
+* Filter rules are IF-THEN rules that automatically add labels or CC users.
+* Fine-grained permission system allows control over visiblity and individual
+  actions on individual issues.
+* Saved queries per user and per project.
+* Users can subscribe to their saved queries (not just labels) and get mail on updates.
+* User groups are built into Monorail, so no update lag on built-in groups.
+  We can also sync from Google Groups.
+* Support for custom fields that are treated very much like built-in fields.
+  Custom fields can be multi-valued.
+* User-valued custom fields can be restricted to only allow qualified users that
+  have a given permission.
+* User-valued custom fields can trigger notifications to those users, and/or grant
+  permissions to the named users.
+* Notification emails include detailed footers that give an ordered list of the
+  reasons why the email was sent to you, with specifics such as "You are the on the
+  auto-cc list of Component C".
+* ML-based spam detection applied before notifications are generated. Users can
+  also flag spam.
+* Limited-availability Endpoints API that works like the old Google Code API.
+* Personal hotlists with ranked issues.
+
+## Reporting problems with Monorail
+
+Please file an issue at <https://bugs.chromium.org/p/monorail>.
diff --git a/doc/api.md b/doc/api.md
new file mode 100644
index 0000000..4328d42
--- /dev/null
+++ b/doc/api.md
@@ -0,0 +1,382 @@
+# Monorail API v1
+
+Monorail API v1 aims to provide nearly identical interface to Google Code issue tracker's API for existing clients' smooth transition. You can get a high-level overview from the documents below.
+
+* [Code example in python](example.py)
+
+
+Rate limiting:
+
+* We count requests for each signed in account.
+* The rate limit is currently 450 requests per minute.  We can adjust that per-account if needed.
+* We enforce the limit in a five-minute window, so 2250 requests are allowed within any given 5 minutes.
+* Individual requests that take more than 5s count as 2 requests.  This could happen for complex issue searches, especially free text and negated free text terms.
+* If the client exceeds the rate limit, it will get response code 400, in which case it should wait and try again.
+* These parameters are defined in settings.py and framework/ratelimiter.py.
+
+
+This API provides the following methods to read/write user/issue/comment objects in Monorail:
+
+[TOC]
+
+## monorail.groups.create
+
+* Description: Create a new user group.
+* Permission: The requester needs permission to create groups.
+* Parameters:
+	* groupName(required, string): The name of the group to create.
+	* who_can_view_members(required, string): The visibility setting of the group. Available options are 'ANYONE', 'MEMBERS' and 'OWNERS'.
+	* ext_group_type(string): The type of the source group if the new group is imported from the source. Available options are 'BAGGINS', 'CHROME_INFRA_AUTH' and 'MDB'.
+* Return message:
+	* groupID(int): The ID of the newly created group.
+* Error code:
+	* 400: The group already exists.
+	* 403: The requester has no permission to create a group.
+
+## monorail.groups.get
+
+* Description: Get a group's settings and users.
+* Permission: The requester needs permission to view this group.
+* Parameters:
+	* groupName(required, string): The name of the group to view.
+* Return message:
+	* groupID(int): The ID of the newly created group.
+	* groupSettings(dict):
+		* groupName(string): The name of the group.
+		* who_can_view_members(string): The visibility setting of the group.
+		* ext_group_type(string): The type of the source group.
+		* last_sync_time(int): The timestamp when the group was last synced from the source. This field is only meaningful for groups with ext_group_type set.
+	* groupOwners(list): A list of group owners' emails.
+	* groupMembers(list): A list of group members' emails.
+* Error code:
+	* 403: The requester has no permission to view this group.
+	* 404: The group does not exist.
+
+## monorail.groups.settings.list
+
+* Description: List all group settings.
+* Permission: None.
+* Parameters:
+	* importedGroupsOnly(boolean): A flag indicating whether only fetching settings of imported groups. The default is False.
+* Return message:
+	* groupSettings(list of dict):
+		* groupName(string): The name of the group.
+		* who_can_view_members(string): The visibility setting of the group.
+		* ext_group_type(string): The type of the source group.
+		* last_sync_time(int): The timestamp when the group was last synced from the source. This field is only meaningful for groups with ext_group_type set.
+* Error code: None.
+
+## monorail.groups.update
+
+* Description: Update a group's settings and users.
+* Permission: The requester needs permission to edit this group.
+* Parameters:
+	* groupName(required, string): The name of the group to edit.
+	* who_can_view_members(string): The visibility setting of the group.
+	* ext_group_type(string): The type of the source group.
+	* last_sync_time(int): The timestamp when the group was last synced from the source.
+	* body(dict):
+		* groupOwners(list of string): A list of owner emails.
+		* groupMembers(list of string): A list of member emails.
+* Return message: Empty.
+* Error code:
+	* 403: The requester has no permission to edit this group.
+
+## monorail.issues.comments.delete
+
+* Description: Delete a comment.
+* Permission: The requester needs permission to delete this comment.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* issueId(required, int): The ID of the issue.
+	* commentId(required, int): The ID of the comment.
+* Return message: Empty.
+* Error code:
+	* 403: The requester has no permission to delete this comment.
+	* 404: The issue and/or comment does not exist.
+
+## monorail.issues.comments.insert
+
+* Description: Add a comment.
+* Permission: The requester needs permission to comment an issue.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* issueId(required, int): The ID of the issue.
+	* sendEmail(boolean): A flag indicating whether to send notifications. The default is True.
+	* Request body(dict):
+		* content(string): Content of the comment to add.
+		* updates(dict): Issue fields to update.
+			* summary(string): The new summary of the issue.
+			* status(string): The new status of the issue.
+			* owner(string): The new owner of the issue.
+			* labels(list of string): The labels to add/remove.
+			* cc(list of string): A list of emails to add/remove from cc field.
+			* blockedOn(list of string): The ID of the issues on which the current issue is blocked.
+			* blocking(list of string): The ID of the issues which the current issue is blocking.
+			* mergedInto(string): The ID of the issue to merge into.
+			* components(list of string): The components to add/remove.
+* Return message:
+	* author(dict):
+		* htmlLink(string): The link to the author profile.
+		* name(string): The name of the author.
+	* canDelete(boolean): Whether current requester could delete the new comment.
+	* content(string): Content of the new comment.
+	* id(int): ID of the new comment.
+	* published(string): Published datetime of the new comment.
+	* updates(dict): Issue fields updated by the new comment.
+* Error code:
+	* 403: The requester has no permission to comment this issue.
+	* 404: The issue does not exist.
+
+## monorail.issues.comments.list
+
+* Description: List all comments for an issue.
+* Permission: The requester needs permission to view this issue.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* issueId(required, int): The ID of the issue.
+	* maxResults(int): The max number of comments to retrieve in one request. The default is 100.
+	* startIndex(int): The starting index of comments to retrieve. The default is 0.
+* Return message:
+	* totalResults(int): Total number of comments retrieved.
+	* items(list of dict): A list of comments.
+		* attachments(dict): The attachment of this comment.
+		* author(dict): The author of this comment.
+		* canDelete(boolean): Whether the requester could delete this comment.
+		* content(string): Content of this comment.
+		* deletedBy(dict): The user who has deleted this comment.
+		* id(int): The ID of this comment.
+		* published(string): Published datetime of this comment.
+		* updates(dict): Issue fields updated by this comment.
+* Error code:
+	* 403: The requester has no permission to view this issue.
+	* 404: The issue does not exist.
+
+## monorail.issues.comments.undelete
+
+* Description: Restore a deleted comment.
+* Permission: The requester needs permission to delete this comment.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* issueId(required, int): The ID of the issue.
+	* commentId(required, int): The ID of the comment.
+* Return message: Empty.
+* Error code:
+	* 403: The requester has no permission to delete this comment.
+	* 404: The issue and/or comment does not exist.
+
+## monorail.issues.get
+
+* Description: Get an issue.
+* Permission: The requester needs permission to view this issue.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* issueId(required, int): The ID of the issue.
+* Return message:
+	* id(int): ID of this issue.
+	* summary(string): Summary of this issue.
+	* stars(int): Number of stars of this issue.
+	* starred(boolean): Whether this issue is starred by the requester.
+	* status(string): Status of this issue.
+	* state(string): State of this issue. Available values are 'closed' amd 'open'.
+	* labels(list of string): Labels of this issue.
+	* author(dict): The reporter of this issue.
+	* owner(dict): The owner of this issue.
+	* cc(list of dict): The list of emails to cc.
+	* updated(string): Last updated datetime of this issue.
+	* published(string): Published datetime of this issue.
+	* closed(string): Closed datetime of this issue.
+	* blockedOn(list of dict): The issues on which the current issue is blocked.
+	* blocking(list of dict): The issues which the current issue is blocking.
+	* projectId(string): The name of the project.
+	* canComment(boolean): Whether the requester can comment on this issue.
+	* canEdit(boolean): Whether the requester can edit this issue.
+	* components(list of string): Components of the issue.
+* Error code:
+	* 403: The requester has no permission to view this issue.
+	* 404: The issue does not exist.
+
+## monorail.issues.insert
+
+* Description: Add a new issue.
+* Permission: The requester needs permission to create a issue.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* sendEmail(boolean): A flag indicating whether to send notifications. The default is True.
+	* body(dict):
+		* blockedOn(list of dict): The issues on which the current issue is blocked.
+		* blocking(list of dict): The issues which the current issue is blocking.
+		* cc(list of dict): The list of emails to cc.
+		* description(required, string): Content of the issue.
+		* labels(list of string): Labels of this issue.
+		* owner(dict): The owner of this issue.
+		* status(required, string): Status of this issue.
+		* summary(requred, string): Summary of this issue.
+		* components(list of string): Components of this issue.
+* Return message:
+	* id(int): ID of this issue.
+	* summary(string): Summary of this issue.
+	* stars(int): Number of stars of this issue.
+	* starred(boolean): Whether this issue is starred by the requester.
+	* status(string): Status of this issue.
+	* state(string): State of this issue. Available values are 'closed' and 'open'.
+	* labels(list of string): Labels of this issue.
+	* author(dict): The reporter of this issue.
+	* owner(dict): The owner of this issue.
+	* cc(list of dict): The list of emails to cc.
+	* updated(string): Last updated datetime of this issue.
+	* published(string): Published datetime of this issue.
+	* closed(string): Closed datetime of this issue.
+	* blockedOn(list of dict): The issues on which the current issue is blocked.
+	* blocking(list of dict): The issues which the current issue is blocking.
+	* projectId(string): The name of the project.
+	* canComment(boolean): Whether the requester can comment on this issue.
+	* canEdit(boolean): Whether the requester can edit this issue.
+	* components(list of string): Components of this issue.
+* Error code:
+	* 403: The requester has no permission to create a issue.
+
+## monorail.issues.list
+
+* Description: List issues for projects.
+* Permission: The requester needs permission to view issues in requested projects.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* additionalProject(list of string): Additional projects to search issues.
+	* can(string): Canned query. Available values are 'all', 'new', 'open', 'owned', 'starred' and 'to_verify'.
+	* label(string): Search for issues with this label.
+	* maxResults(int): The max number of issues to retrieve in one request. The default is 100.
+	* owner(string): Search for issues with this owner.
+	* publishedMax(int): Search for issues published before the timestamp.
+	* publishedMin(int): Search for issues published after the timestamp.
+	* q(string): Custom query criteria, e.g. 'status:New'.
+	* sort(string): Fields to sort issues by.
+	* startIndex(int): The starting index of issues to retrieve. The default is 0.
+	* status(string): Search for issues of this status.
+	* updatedMax(int): Search for issues updated before the timestamp.
+	* updatedMin(int): Search for issues updated after the timestamp.
+* Return message:
+	* totalResults(int): Total number of issues retrieved.
+	* items(list of dict): A list of issues.
+		* author(dict): The reporter of this issue.
+		* blockedOn(list of dict): The issues on which the current issue is blocked.
+		* blocking(list of dict): The issues which the current issue is blocking.
+		* canComment(boolean): Whether the requester can comment on this issue.
+		* canEdit(boolean): Whether the requester can edit this issue.
+		* cc(list of dict): The list of emails to cc.
+		* closed(string): Closed datetime of this issue.
+		* description(string): Content of this issue.
+		* id(int): ID of this issue.
+		* labels(list of string): Labels of this issue.
+		* owner(dict): The owner of this issue.
+		* published(string): Published datetime of this issue.
+		* starred(boolean): Whether this issue is starred by the requester.
+		* stars(int): Number of stars of this issue.
+		* state(string): State of this issue. Available values are 'closed' and 'open'.
+		* status(string): Status of this issue.
+		* summary(string): Summary of this issue.
+		* updated(string): Last updated datetime of this issue.
+* Error code:
+	* 403: The requester has no permission to view issues in the projects.
+
+## monorail.users.get
+
+* Description: Get a user.
+* Permission: None.
+* Parameters:
+	* userId(required, string): The email of the user.
+	* ownerProjectsOnly(boolean): Whether to only return projects the user owns. The default is False.
+* Return message:
+	* projects(dict):
+		* name(string): The name of the project.
+		* htmlLink(string): The relative url of this project.
+		* summary(string): Summary of this project.
+		* description(string): Description of this project.
+		* role(string): Role of the user in this project.
+		* issuesConfig(dict): Issue configurations.
+* Error code: None.
+
+## monorail.components.create
+
+* Description:  Create a component.
+* Permission: The requester needs permission to edit the requested project.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* componentName(required, string): The leaf name of the component to create.
+	* body(dict):
+		* parentPath(string): Full path of the parent component.
+		* description(string): Description of the new component.
+		* admin(list of string): A list of user emails who can administer this component.
+		* cc(list of string): A list of user emails who will be added to cc list when this component is added to an issue.
+		* deprecated(boolean): A flag indicating whether this component is deprecated. The default is False.
+* Return message:
+	* componentId(int): The ID of the new component.
+	* projectName(string): The name of the project this new component belongs to.
+	* componentPath(string): The full path of the component.
+	* description(string): Description of the new component.
+	* admin(list of string): A list of user emails who can administer this component.
+	* cc(list of string): A list of user emails who will be added to cc list when this component is added to an issue.
+	* deprecated(boolean): A flag indicating whether this component is deprecated.
+	* created(datetime): Created datetime.
+	* creator(string): Email of the creator.
+	* modified(datetime): Last modified datetime.
+	* modifier(string): Email of last modifier.
+* Error code:
+	* 400: The component name is invalid or already in use.
+	* 403: The requester has no permission to create components in the project.
+	* 404: The parent component does not exist, or the project does not exist.
+
+## monorail.components.delete
+
+* Description:  Delete a component.
+* Permission: The requester needs permission to edit the requested component.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* componentPath(required, string): The full path of the component to delete.
+* Return message: None.
+* Error code:
+	* 403: The requester has no permission to delete this component, or tries to delete component that has subcomponents.
+	* 404: The component does not exist, or the project does not exist.
+
+## monorail.components.list
+
+* Description:  List all components of a given project.
+* Permission: None.
+* Parameters:
+	* projectId(required, string): The name of the project.
+* Return message:
+	* components(list of dict):
+		* componentId(int): The ID of the new component.
+		* projectName(string): The name of the project this new component belongs to.
+		* componentPath(string): The full path of the component.
+		* description(string): Description of the new component.
+		* admin(list of string): A list of user emails who can administer this component.
+		* cc(list of string): A list of user emails who will be added to cc list when this component is added to an issue.
+		* deprecated(boolean): A flag indicating whether this component is deprecated.
+		* created(datetime): Created datetime.
+		* creator(string): Email of the creator.
+		* modified(datetime): Last modified datetime.
+		* modifier(string): Email of last modifier.
+* Error code:
+	* 403: The requester has no permission to delete this component, or tries to delete component that has subcomponents.
+	* 404: The project does not exist.
+
+## monorail.components.update
+
+* Description:  Update a component.
+* Permission: The requester needs permission to edit the requested component.
+* Parameters:
+	* projectId(required, string): The name of the project.
+	* componentPath(required, string): The full path of the component to delete.
+	* updates(list of dict):
+		* field(required, string): Component field to update. Available options are 'LEAF_NAME', 'DESCRIPTION', 'ADMIN', 'CC' and 'DEPRECATED'.
+		* leafName(string): The new leaf name of the component.
+		* description(string): The new description of the component.
+		* admin(list of string): The new list of user emails who can administer this component.
+		* cc (list of string): The new list of user emails who will be added to cc list when this component is added to an issue.
+		* deprecated(boolean): The new boolean value indicating whether this component is deprecated.
+* Return message: None.
+* Error code:
+	* 400: The new component name is invalid or already in use.
+	* 403: The requester has no permission to edit this component.
+	* 404: The component does not exist, or the project does not exist.
diff --git a/doc/code-practices/frontend.md b/doc/code-practices/frontend.md
new file mode 100644
index 0000000..b248cd4
--- /dev/null
+++ b/doc/code-practices/frontend.md
@@ -0,0 +1,239 @@
+# Monorail Frontend Code Practices
+
+This guide documents the code practices used by Chops Workflow team when writing new client code in the Monorail Issue Tracker.
+
+Through this guide, we use [IETF standards language](https://tools.ietf.org/html/bcp14) to represent the requirements level of different recommendations. For example:
+
+
+
+*   **Must** - Code that is written is required to follow this rule.
+*   **Should** - We recommend that new code follow this rule where possible.
+*   **May** - Following this guideline is optional.
+
+[TOC]
+
+
+## JavaScript
+
+
+### Follow the Google JavaScript Style Guide
+
+We enforce the use of the [Google JavaScript style guide](https://google.github.io/styleguide/jsguide.html) through ES Lint, with our rules set by [eslint-config-google](https://github.com/google/eslint-config-google).
+
+
+
+*   New JavaScript code **must** follow the Google style guide rules enforced by our ES Lint config.
+*   In all other cases, JavaScript **should** adhere to the Google style guide.
+
+
+#### Exceptions
+
+
+
+*   While the Google style guide [recommends using a trailing underscore for private JavaScript field names](https://google.github.io/styleguide/jsguide.html#naming-non-constant-field-names), our code **should** start private field names with an underscore instead to be consistent with the convention adopted by open source libraries we depend on.
+
+
+### Use modern browser features
+
+We generally aim to write a modern codebase that makes use of recent JavaScript features to keep our coding conventions fresh.
+
+
+
+*   When using features that are not yet supported in [all supported browsers](#heading=h.s0dpmzuabf7w), we **must** polyfill features to meet our browser support requirements.
+*   New JavaScript code **should not** inject values into the global scope. ES modules should be used for importing variables and functions across files instead.
+*   When writing asynchronous code, JavaScript code **should** favor async/await over Promises.
+    *   Exception: `Promise.all()` **may** be used to simultaneously run multiple await calls.
+*   JavaScript code **should** use the modularized forms of built-in functions rather than the global forms. For example, prefer [Number.parseInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt) over [parseInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt).
+*   String building code **should** prefer [ES template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) over string concatenation for strings built from multiple interpolated variables. Template literals usually produce more readable code than direct concatenation.
+*   JavaScript code **should** prefer using native browser functionality over importing external dependencies. If a native browser function does the same thing as an external library, prefer using the native functionality.
+
+
+#### Browser support guidelines
+
+All code written **must** target the two latest stable versions of each of the following browsers, per the [Chrome Ops Browser Support Guidelines](https://chromium.googlesource.com/infra/infra/+/main/doc/front_end.md#browser-support).
+
+
+### Avoid unexpected Object mutations
+
+
+
+*   Functions **must not** directly mutate any Object or Arrays that are passed into them as parameters, unless the function explicitly documents that it mutates its parameters.
+*   Objects and Arrays declared as constants **should** have their values frozen with `Object.freeze()` to prevent accidental mutations.
+
+
+### Create readable function names
+
+
+
+*   Event handlers **should** be named after the actions they do, not the actions that trigger them. For example `starIssue()` is a better function header than `onClick()`.
+    *   Exception: APIs that allow specifying custom handlers **may** accept event handling functions with generic names like clickHandler.
+
+
+### Code comments
+
+
+
+*   TODOs in code **should** reference a tracking issue in the TODO comment.
+
+
+### Performance
+
+
+
+*   JavaScript code **should not** create extraneous objects in loops or repeated functions. Object initialization is expensive, including for common native objects such as Function, RegExp, and Date. Thus, creating new Objects in every iteration of a loop or other repeated function can lead to unexpected performance hits. Where possible, initialize objects once and re-use them on each iteration rather than recreating them repeatedly.
+
+
+## Web components/LitElement
+
+Monorail’s frontend is written as a single page app (SPA) using the JavaScript framework [LitElement](https://lit-element.polymer-project.org/). LitElement is a lightweight framework built on top of the native Web Components API present in modern browsers.
+
+When creating new elements, we try to follow recommended code practices for both Web Components and LitElement.
+
+
+### Web components practices
+
+Google Web Fundamentals offers a [Custom Elements Best Practices](https://developers.google.com/web/fundamentals/web-components/best-practices) guide. While the recommendations in this guide are solid, many of them are already covered by LitElement’s underlying usage of Web Components. Thus, we avoid explicitly requiring following this guide to avoid confusion.
+
+However, many of the recommendations from this guide are useful, even when using LitElement. We adapt this advice for our purposes here.
+
+
+
+*   Elements **should not** break the hidden attribute when styling the :host element. For example, if you set a custom element to have the style of `:host { display: flex; }`, by default this causes the `:host[hidden]` state to also use `display: flex;`. To overcome this, you can use CSS to explicitly declare that the :host element should be hidden when the hidden attribute is set.
+*   Elements **should** enable Shadow DOM to encapsulate styles. LitElement enables Shadow DOM by default and offers options for disabling it. However, disabling ShadowDOM is discouraged because many Web Components features, such as `<slot>` elements, are built on top of Shadow DOM.
+*   Elements **should not** error when initialized without attributes. For example, adding attribute-less HTML such as `<my-custom-element></my-custom-element>` to the DOM should not cause code errors.
+*   Elements **should not** dispatch events in response to changes made by the parent. The parent already knows about its own activity, so dispatching events in these circumstances is not meaningful.
+*   Elements **should not** accept rich data (ie: Objects and Arrays) as attributes. LitElement provides APIs for setting Arrays and Objects through either attributes or properties, but it is more efficient to set them through properties. Setting these values through attributes requires extra serialization and deserialization.
+    *   Exception: When LitElements are used outside of lit-html rendering, declared HTML **may** pass rich data in through attributes. Outside of lit-html, setting property values for DOM is often inconvenient.
+*   Elements **should not** self apply classes to their own `:host` element. The parent of an element is responsible for applying classes to an element, not the element itself.
+
+
+### Organizing elements
+
+When creating a single page app using LitElement, we render all components in the app through a root component that handles frontend routing and loads additional components for routes the user visits.
+
+
+
+*   Elements **should** be grouped into folders with the same name as the element with tests and element code living together.
+    *   Exception: Related sub-elements of a parent element **may** be grouped into the parent element’s folder if the element is not used outside the parent.
+*   Pages **should** lazily load dependent modules when the user first navigates to that page. To improve initial load time performance, we split element code into bundles divided across routes.
+*   Elements **should** use the mr- prefix if they are specific to Monorail and **may** use the chops- prefix if they implement functionality that is general enough to be shared with other apps. (ie: `<chops-button>`, `<chops-checkbox>`)
+*   Nested routes **may** be subdivided into separate sub-components with their own routing logic. This pattern promotes sharing code between related pages.
+
+
+### LitElement lifecycle
+
+LitElement provides several lifecycle callbacks for elements. When writing code, it is important to be aware of which kinds of work should be done during different phases of an element’s life.
+
+
+
+*   Elements **must** remove any created external side effects before `disconnectedCallback()` finishes running.
+    *   Example: If an element attaches any global event handlers at any time in its lifecycle, those global event handlers **must not** continue to run when the element is removed from the DOM.
+*   Elements **should not** do work dependent on property values in `connectedCallback()` or `constructor()`. These functions will not re-run if property values change, so work done based on property values will become stale when properties change.
+    *   Exception: An element **may** initialize values based on other property values as long as values continue to be updated beyond when the element initializes.
+    *   Use `update()` for functionality meant to run before the `render()` cycle and `updated()` for functionality that runs after.
+*   Elements **should** use the `update()` callback for work that happens _before render_ and the `updated()` callback for work that happens _after render_.
+    *   More strictly, code with significant side effects such as XHR requests **must not** run in the `update()` callback but **may** run in the `updated()` callback.
+
+
+### Sharing functionality
+
+
+
+*   Elements **should not** use mixins for sharing behavior. See: [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)
+    *   Exception: Elements **may** use the `connect()` mixin for sharing Redux functionality.
+
+
+### HTML/CSS
+
+
+
+*   HTML and CSS written in elements **should** follow the [Google HTML/CSS style guide](https://google.github.io/styleguide/htmlcssguide.html).
+*   An element **should** make its `:host` element the main container of the element. When a parent element styles a child element directly through CSS, the `:host` element is the HTMLElement that receives the styles.
+*   Styles in CSS **should** aim to use the minimum specificity required to apply the declared style. This is important because increased specificity is used to overwrite existing styles. In particular:
+    *   CSS **should not** use the `!important` directive.
+    *   Elements **should** be styled with classes rather than with IDs because styles applied to ID selectors are harder to overwrite.
+*   CSS custom properties **should** be used to specify commonly used CSS values, such as shared color palette colors.
+    *   In addition, CSS custom properties **may** be used by individual elements to expose an API for parents to style the element through.
+*   Elements **may** use shared JavaScript constants with lit-html CSS variables to share styles among multiple elements. See: [Sharing styles in LitElement](https://lit-element.polymer-project.org/guide/styles#sharing-styles)
+
+
+### Security recommendations
+
+
+
+*   Code **must not** use LitElement’s `unsafeHTML` or `unsafeCSS` directives.
+*   Code **must not** directly set anchor href values outside of LitElement’s data binding system. LitElement sanitizes variable values when data binding and manually binding data to the DOM outside of LitElement’s sanitization system is a security liability.
+*   Code **must not** directly set `innerHTML` on any elements to values including user-inputted data.
+    *   Note: It is common for [Web Component example code](https://developers.google.com/web/fundamentals/web-components/customelements) to make use of directly setting innerHTML to set HTML templates. In these examples, setting innerHTML is often safe because the sample code does not add any variables into the rendered HTML. However, setting innerHTML directly is still risky and can be completely avoided as a pattern when writing LitElement elements.
+
+
+## Redux/Data Binding
+
+We use [Redux](https://redux.js.org/) on our LitElement frontend to manage state.
+
+
+
+*   JavaScript code **must** maintain unidirectional data flow. Unidirectional data flow could also be referred to as “props down, events up” and other names. See: [Redux Data Flow](https://redux.js.org/basics/data-flow/)
+    *   In short, all data that lives in Redux **must** be edited by editing the Redux store, not through intermediate data changes. These edits happen through dispatched actions.
+    *   This means that automatic 2-way data binding patterns, used in frameworks like Polymer, **must not** be used.
+    *   Note: For component data stored outside of Redux, this data flow pattern **should** still be followed by treating the topmost component where data originated from as the “parent” of the data.
+*   JavaScript code **must** follow all rules listed as “Essential” in the [Redux style guide](https://redux.js.org/style-guide/style-guide/).
+    *   Additionally, “Strongly Recommended” and “Recommended” rules in Redux’s style guide **may** be followed.
+*   Objects that cannot be directly serialized into JSON **must not** be stored in the Redux store. As an example, this includes JavaScript’s native Map and Set object.
+*   Reducers, actions, and selectors **must** be organized into files according to the [“Ducks” pattern](https://github.com/erikras/ducks-modular-redux).
+*   Reducers **should** be separated into small functions that individually handle only a few action types.
+*   Redux state **should** be normalized to avoid storing duplicate copies of the same data in the state. See: [Normalizing Redux State Shape](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape/)
+*   JavaScript code **should not** directly pull data from the Redux state Object. Data inside the Redux store **should** be accessed through a layer of selector functions, using [Reselect](https://github.com/reduxjs/reselect).
+*   Reducers, selectors, and action creators **should** be unit tested like functions. For example, a reducer is a function that takes in an initial state and an action then returns a new state.
+*   Reducers **may** destructure action arguments to make it easier to read which kinds of action attributes are used. In particular, this pattern is useful when reducers are composed into many small functions that each handle a small number of actions.
+*   Components connected to Redux **may** use the [Presentational/Container component pattern](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) to separate “connected” versions of the component from “unconnected” versions. This pattern is useful for testing components outside of Redux and for separating concerns.
+
+
+## Testing
+
+
+
+*   Mock timers **must** be added for code which depends on time. For example, any code which uses debouncing, throttling, settimeout, or setInterval **must** mock time in tests. Tests dependent on time are likely to flakily fail.
+*   New JavaScript code **must** have 90% or higher test coverage. Where possible, code **should** aim for 100% test coverage.
+*   Unit tests **should** be kept small when possible. More smaller tests are preferable to fewer larger tests.
+*   The HTMLElement dispatchEvent() function **may** be used to simulate browser events such as keypresses, mouse events, and more.
+
+
+## UX
+
+
+### Follow Material Design guidelines
+
+When making design changes to our UI, we aim to follow [Google’s Material Design guidelines](https://material.io/design/). In particular, because we are designing a developer tool where our users benefit from power user features and high scannability for large amounts of data, we pay particular attention to [recommendations on applying density](https://material.io/design/layout/applying-density.html).
+
+
+
+*   Our UI designs **must not** directly use Google branding such as the Google logo or Google brand colors. Monorail is not an official Google product.
+*   Visual designs **should** follow the Material Design guidelines. In particular, try
+*   Colors used in designs **should** be taken from the [2014 Material Design color palette](https://material.io/design/color/the-color-system.html). Where this color palette falls short of our design needs, new colors **should** be created by mixing shades of the existing 2014 Material colors.
+*   Our UI designs **should** follow a “build from white” design philosophy where most of the UI is neutral in color and additional hues are added to draw emphasis to specific elements.
+
+
+### Accessibility
+
+To keep our UI accessible to a variety of users, we aim to follow the [WAI-ARIA guidelines](https://www.w3.org/WAI/standards-guidelines/aria/).
+
+
+
+*   UI designs **must** keep a 4.5:1 minimum contrast ratio for text and icons.
+*   CSS **must not** set “outline: none” without creating a new focus style for intractable elements. While removing native focus styles is a tempting way to make a design “feel more modern”, being able to see which elements are focused is an essential feature for keyboard interaction.
+*   UI changes **should** follow the [Material Design accessibility guidelines](https://material.io/design/usability/accessibility.html).
+*   HTML code **should** favor using existing semantic native elements over recreating native functionality where possible. For example, it is better to use a `<button>` element for a clickable button than to create a `<div>` with an onclick handler.
+    *   Tip: In many cases, an underlying native element **may** be used as part of an implementation that otherwise seems to need completely custom code. One common example is when styling native checkboxes: while many CSS examples create new DOM elements to replace the look of a native checkbox, it is possible to use CSS pseudoelements to tie interaction with those new elements to an underlying native checkbox.
+    *   Exception: Oftentimes, specific code requirements will make using native elements unfeasible. In these cases, the custom element implementation **must** follow any relevant WAI-ARIA for the type of element being implemented. For example, these are the [WAI-ARIA guidelines on implementing an accessible modal dialog](https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html).
+*   Any element with an onclick handler **should** be a `<button>`. The `<button>` element handles mapping keyboard shortcuts to click handlers, which adds keyboard support for these intractable elements.
+*   Manual screenreader testing **should** be done when implementing heavily customized interactive elements. Autocomplete, chip inputs, and custom modal implementations are examples of elements that should be verified against screenreader testing.
+
+
+### Writing
+
+
+
+*   Error messages **should** guide users towards solving the cause of the error when possible.
+*   Text in the UI **should not** use terminology that’s heavily tied to app internals. Concepts **should** be expressed in terms that users understand.
+*   Wording **should** be kept simple and short where possible.
diff --git a/doc/deployment.md b/doc/deployment.md
new file mode 100644
index 0000000..fd12add
--- /dev/null
+++ b/doc/deployment.md
@@ -0,0 +1,348 @@
+# Monorail Deployment
+
+*This document covers updating Chromium's instance of Monorail through
+Spinnaker. If you are looking to deploy your own instance of Monorail,
+see: [Creating a new Monorail instance](instance.md)*
+
+## Before your first deployment
+
+Spinnaker is a platform that helps teams configure and manage application
+deployment pipelines. We have a
+[ChromeOps Spinnaker](http://go/chrome-infra-spinnaker) instance that holds
+pipelines for several ChromeOps services, including Monorail.
+
+IMPORTANT: In the event of an unexpected failure in a Spinnaker pipeline, it is
+extremely important that the release engineer properly cleans up versions in the
+Appengine console (e.g. delete bad versions and manual rollback to previous
+version).
+
+### Spinnaker Traffic Splitting
+
+Spinnaker's traffic splitting, rollback, and cleanup systems rely heavily on the
+assumption that the currently deployed version always has the highest version
+number. During Rollbacks, Spinnaker will migrate traffic back to the second
+highest version number and delete the highest version number. So, if the
+previous deployed version was v013 and there was a v014 that was created but
+somehow never properly deleted, and Spinnaker just created a v015, which it is
+now trying to rollback, this means, spinnaker will migrate 100% traffic "back"
+to v014, which might be a bad version, and delete v015. The same could happen
+for traffic migrations. If a previous, good deployment is v013 with 100%
+traffic, and there is a bad deployment at v014 that was never cleaned up, during
+a new deployment, Spinnaker will have created v015 and begin traffic splitting
+between v014 and v015, which means our users are either being sent to the bad
+version or the new version.
+
+If you are ever unsure about how you should handle a manual cleanup and
+rollback, ping the [Monorail chat](http://chat/room/AAAACV9ZZ8k) and ask for
+help.
+
+Below are brief descriptions of all the pipelines that make up the Monorail
+deployment process in Spinnaker.
+
+#### Deploy Monorail
+
+This is the starting point of the Monorail deployment process and should be
+manually triggered by the Release Engineer.
+![start monorail deployment](md_images/start-deploy-monorail.png)
+This pipeline handles creating a Cloud Build of Monorail. The build can be created from
+HEAD of a given branch or it can re-build a previous Cloud Build given a "BUILD_ID".
+Once the build is complete, a `Deploy {Dev|Staging|Prod}` pipeline can be automatically
+triggered to deploy the build to an environment. On a regular weekly release, we should
+use the default "ENV" = dev, provide the release branch, and leave "BUILD_ID" empty.
+
+##### Parameter Options
+
+*   The "BRANCH" parameter takes the name of the release branch that holds
+    the commits we want to deploy.
+    The input should be in the form of `refs/releases/monorail/[*deployment number*]`.
+    e.g. "refs/releases/monorail/1" builds from HEAD of
+    [infra/infra/+/refs/releases/monorail/1](https://chromium.googlesource.com/infra/infra/+/refs/releases/monorail/1).
+*   The "ENV" parameter can be set to "dev", "staging", or "prod" to
+    automatically trigger `Deploy Dev`, `Deploy Staging`, or `Deploy
+    Production` (respectively) with a successful finish of `Deploy Monorail`.
+    The "nodeploy" option means no new monorail version will get deployed
+    to any environment.
+*   The "BUILD_ID" parameter can take the id of a previous Cloud Build found
+    [here](https://pantheon.corp.google.com/cloud-build/builds?organizationId=433637338589&src=ac&project=chrome-infra-spinnaker).
+    We can use this to rebuild older Monorail versions. When the "BUILD_ID"
+    is given "BRANCH" is ignored.
+
+#### Deploy Dev
+
+This pipeline handles deploying a new monorail-dev version and migrating traffic
+to the newest version.
+
+After a new version is created, but before traffic is migrated, there is a
+"Continue?" stage that waits on manual judgement. The release engineer is
+expected to do any testing in the newest version before confirming that the
+pipeline should continue with traffic migration. If there are any issues, the
+release engineer should select "Rollback", which triggers the `Rollback`
+pipeline. If "Continue" is selected, spinnaker will immediately migrate 100%
+traffic to to the newest version.
+![manual judgement stage](md_images/manual-judgement-stage.png)
+![continue options](md_images/continue-options.png)
+
+The successful finish of this pipeline triggers two pipelines: `Cleanup` and
+`Deploy Staging`.
+
+#### Deploy Development - EXPERIMENTAL
+
+Note that this pipeline is similar to the above `Deploy Dev` pipeline.
+This is for Prod Tech's experimental purposes. Please ignore this pipeline. This
+cannot be triggered by `Deploy Monorail`.
+
+#### Deploy Staging
+
+This pipeline handles deploying a new monorail-staging version and migrating
+traffic to the newest version.
+
+Like `Deploy Dev` after a new version is created, there is a
+"Continue?" stage that waits on manual judgement. The release engineer should
+test the new version before letting the pipeline proceed to traffic migration.
+If any issues are spotted, the release engineer should select "Rollback", to
+trigger the `Rollback` pipeline.
+
+Unlike `Deploy Dev`, after "Continue" is selected, spinnaker will
+proceed with three separate stages of traffic splitting with a waiting period
+between each traffic split.
+
+The successful finish of this pipeline triggers two pipelines: `Cleanup` and
+`Deploy Production`.
+
+#### Deploy Production
+
+This pipeline handles deploying a new monorail-prod version and migrating
+traffic to the newest version.
+
+This pipeline has the same set of stages as `Deploy Staging`. the successful
+finish of this pipeline triggers the `Cleanup` pipeline.
+
+#### Rollback
+
+This pipeline handles migrating traffic back from the newest version to the
+previous version and deleting the newest version. This pipeline is normally
+triggered by the `Rollback` stage of the `Deploy Dev|Staging|Production`
+pipelines and it only handles rolling back one of the applications, not all
+three.
+
+##### Parameter Options
+
+*   "Stack" is a required parameter for this pipeline and can be one of "dev",
+    "staging", or "prod". This determines which of monorail's three applications
+    (monorail-dev, monorail-staging, monorail-prod) it should rollback. When
+    `Rollback` is triggered by one of the above Deploy pipelines, the
+    appropriate "Stack" value is passed. When the release engineer needs to
+    manually trigger the `Rollback` pipeline they should make sure they are
+    choosing the correct "Stack" to rollback.
+    ![start rollback](md_images/start-rollback.png)
+    ![rollback options](md_images/rollback-options.png)
+
+#### Cleanup
+
+This pipeline handles deleting the oldest version.
+
+For more details read [go/monorail-deployments](go/monorail-deployments) and
+[go/chrome-infra-appengine-deployments](go/chrome-infra-appengine-deployments).
+
+TODO(jojwang): Currently, notifications still need to be set up. See
+[b/138311682](https://b.corp.google.com/issues/138311682)
+
+### Notifications
+
+Monorail's pipelines in Spinnaker have been configured to send notifications to
+monorail-eng+spinnaker@google.com when:
+
+1.  Any Monorail pipeline fails
+1.  `Deploy Staging` requires manual judgement at the "Continue?" stage.
+1.  `Deploy Production` requires manual judgement at the "Continue?" stage.
+
+## Deploying a new version to an existing instance using Spinnaker
+
+For each release cycle, a new `refs/releases/monorail/[*deployment number*]`
+branch is created at the latest [*commit SHA*] that we want to be part of the
+deployment. Spinnaker will take the [*deployment number*] and deploy from HEAD
+of the matching branch.
+
+Manual testing steps are added during Workflow's weekly meetings for each
+commit between the previous release and this release.
+
+## Spinnaker Deployment steps
+
+If any step below fails. Stop the deploy and ping
+[Monorail chat](http://chat/room/AAAACV9ZZ8k).
+
+1.  Prequalify
+    1.  Check for signs of trouble
+        1.  [go/chops-hangout](http://go/chops-hangout)
+        1.  [Viceroy](http://go/monorail-prod-viceroy)
+        1.  [go/devx-pages](http://go/devx-pages)
+        1.  [GAE dashboard](https://console.cloud.google.com/appengine?project=monorail-prod&duration=PT1H)
+        1.  [Error Reporting](http://console.cloud.google.com/errors?time=P1D&order=COUNT_DESC&resolution=OPEN&resolution=ACKNOWLEDGED&project=monorail-prod)
+    1.  If there are any significant operational problems with Monorail or ChOps
+        in general, halt deploy and notify team.
+1.  Assess
+    1.  View the existing release branches with
+        ```
+        git ls-remote origin "refs/releases/monorail/*"
+        ```
+        Each row will show the deployment's *commit SHA* followed by the branch
+        name. The value after monorail/ is the *deployment number*.
+    1.  Your *deployment number* is the last deployment number + 1.
+    1.  Your *commit SHA* is either from the commit you want to deploy from or
+        the last commit from HEAD. To get the SHA from HEAD:
+        ```
+        git rev-parse HEAD
+        ```
+1.  Create branch
+    1.  Create a new local branch at the desired [*commit SHA*]:
+        ```
+        git checkout -b <your_release_branch_name> [*commit SHA*]
+        ```
+        1.  [Optional] cherry pick another commit that is ahead of
+            [*commit SHA*]:
+            ```
+            git cherry-pick -x [*cherry-picked commit SHA*]
+            ```
+    1.  Push your local branch to remote origin and tag it as
+        <your_release_branch_name>:refs/releases/monorail/x, where x is your *deployment number*:
+        ```
+        git push origin <your_release_branch_name>:refs/releases/monorail/[*deployment number*]
+        ```
+        1.  If the branch already exists, [*commit SHA*] must be ahead of the
+            current commit that the branch points to.
+1.  Update Dev and Staging schema
+    1.  Check for changes since last deploy:
+        ```
+        tail -30 schema/alter-table-log.txt
+        ```
+        If you don't see any changes since the last deploy, skip this section.
+    1.  Copy and paste updates to the
+        [primary DB](http://console.cloud.google.com/sql/instances/primary/overview?project=monorail-dev)
+        in the `monorail-dev` project. Please be careful when pasting into SQL
+        prompt.
+    1.  Copy and paste the new changes into the
+        [primary DB](http://console.cloud.google.com/sql/instances/primary/overview?project=monorail-staging)
+        in staging.
+1.  Start deployment
+    1.  Navigate to the Monorail Delivery page at
+        [go/spinnaker-deploy-monorail](https://spinnaker-1.endpoints.chrome-infra-spinnaker.cloud.goog/#/applications/monorail/executions)
+        in Spinnaker.
+    1.  Identify the `Deploy Monorail` Pipeline.
+    1.  Click "Start Manual Execution".
+        ![start monorail deployment](md_images/start-deploy-monorail.png)
+    1.  The "BUILD_ID" field should be empty.
+    1.  The "ENV" field should be set to "dev".
+    1.  The "BRANCH" field should be set to
+        "refs/releases/monorail/[*deployment number*]".
+    1.  The notifications box can remain unchanged.
+1.  Confirm monorail-dev was successfully deployed (Pipeline: `Deploy Dev`, Stage: "Continue?")
+    1.  Find the new version using the
+        [appengine dev version console](https://pantheon.corp.google.com/appengine/versions?organizationId=433637338589&project=monorail-dev).
+    1.  Visit popular/essential pages and confirm they are all accessible.
+    1.  If everything looks good, choose "Continue" for Deploy Dev.
+    1.  If there is an issue, choose "Rollback" for this stage.
+1.  Test on Staging (Pipeline: `Deploy Staging`, Stage: "Continue?")
+    1.  Find the new version using the
+        [appengine staging version console](https://pantheon.corp.google.com/appengine/versions?organizationId=433637338589&project=monorail-staging).
+    1.  For each commit since last deploy, verify affected functionality still
+        works.
+        1.  Test using a non-admin account, unless you're verifying
+            admin-specific functionality.
+        1.  If you rolled back a previous attempt, make sure you test any
+            changes that might have landed in the mean time.
+        1.  Test that email works by updating any issue with an owner and/or cc
+            list and confirming that the email shows up in
+            [g/monorail-staging-emails](http://g/monorail-staging-emails) with
+            all the correct recipients.
+    1.  If everything looks good, choose "Continue" for Deploy Staging.
+    1.  If there is an issue, choose "Rollback" for this stage.
+1.  Update Prod Schema
+    1.  If you made changes to the Dev and Prod schema, repeat them on the prod
+        database.
+1.  Test on Prod (Pipeline: `Deploy Production`, Stage: "Continue?")
+    1.  Find the new version using the
+        [appengine prod version console](https://pantheon.corp.google.com/appengine/versions?organizationId=433637338589&project=monorail-prod).
+    1.  For each commit since last deploy, verify affected functionality still
+        works. Test using a non-admin account, unless you're verifying
+        admin-specific functionality.
+    1.  Add a comment to an issue.
+    1.  Enter a new issue and CC your personal account.
+    1.  Verify that you got an email
+    1.  Try doing a query that is not cached, then repeat it to test the cached
+        case.
+    1.  If everything looks good, choose "Continue" for Deploy Prod.
+    1.  If there is an issue, choose "Rollback" for this stage.
+1.  Monitor Viceroy and Error Reporting
+    1.  Modest latency increases are normal in the first 10-20 minutes
+    1.  Check
+        [/p/chromium updates page](https://bugs.chromium.org/p/chromium/updates/list).
+    1.  [Chromiumdash](https://chromiumdash.appspot.com/release-manager?platform=Android),
+        should work after deployment.
+1.  Announce the Deployment.
+    1.  Include the [build id](https://screenshot.googleplex.com/KvzoxHEs6Qy.png) of the
+        Cloud Build used for this deployment.
+    1.  Include a link and name of the release branch used for the deployment.
+    1.  Include list of changes that went out (obtained from section 2 above),
+        or via `git log --oneline .` (use `--before` and `--after` as needed).
+    1.  If there were schema changes, copy and paste the commands at the bottom
+        of the email
+    1.  Use the subject line:
+        "Deployed Monorail to staging and prod with release branch [*deployment number*]"
+    1.  Send the email to "monorail-eng@google.com" and
+        "chrome-infra+monorail@google.com"
+1.  Add a new row to the
+    [Monorail Deployment Stats](http://go/monorail-deployment-stats) spreadsheet
+    to help track deploys/followups/rollbacks. It is important to do this even
+    if the deploy failed for some reason.
+
+### Rolling back and other unexpected situations.
+
+If issues are discovered after the "Continue?" stage and traffic migration has
+already started: Cancel the execution and manually start the `Rollback`
+pipeline. ![cancel executions](md_images/cancel-execution.png)
+
+If issues are discovered during the monorail-staging or monorail-prod deployment
+DO NOT forget to also run the `Rollback` pipeline for monorail-dev or
+monorail-dev and monorail-staging, respectively.
+
+If you are ever unsure on how to rollback or clean up unexpected Spinnaker
+errors please ping the [Monorail chat](http://chat/room/AAAACV9ZZ8k) for help.
+
+## Manually Deploying and Rolling back if Spinnaker is down.
+
+### Creating a new app version
+1. From infra/monorail, create a new local branch at the desired [*commit SHA*]. Ensure that the branch has no unmerged changes.
+   ```
+   git checkout -b <your_release_branch_name> [*commit SHA*]
+   ```
+   1.  [Optional] cherry pick another commit that is ahead of
+            [*commit SHA*]:
+            ```
+            git cherry-pick -x [*cherry-picked commit SHA*]
+            ```
+1. run
+   ```
+   eval `../../go/env.py`
+   ```
+1. Create a new version with gae.py (replacing `deploy_dev` with `deploy_staging` or `deploy_prod`, if appropriate):
+   ```
+   make deploy_dev
+   ```
+   1.  [Optional] If you encounter `ImportError: No module named six.moves`, try again after running:
+            [*commit SHA*]:
+            ```
+            sudo `which python` `which pip` install six
+            ```
+1. The new version should show up in pantheon's App Engine's Versions [page](https://pantheon.corp.google.com/appengine/versions?src=ac&project=monorail-dev&serviceId=default). Traffic allocation should be 0%.
+
+### Migrating traffic to a previous or new version
+1. Confirm the new version you want to release or the old version you want to roll back to exists in [pantheon](https://pantheon.corp.google.com/appengine/versions?src=ac&project=monorail-dev&serviceId=api):
+   1. Confirm this for all services (default, besearch, latency-insensitive, api) via the Service dropdown.
+   ![services-dropdown](md_images/pantheon-services-dropdown.png)
+1. Select the desired version and click "Migrate Traffic". REPEAT THIS FOR EVERY SERVICE.
+   ![migrate-traffic](md_images/pantheon-migrate-traffic.png)
+
+
+## Creating and deploying a new Monorail instance
+
+See: [Creating a new Monorail instance](instance.md)
diff --git a/doc/design/data-storage.md b/doc/design/data-storage.md
new file mode 100644
index 0000000..3c2d082
--- /dev/null
+++ b/doc/design/data-storage.md
@@ -0,0 +1,292 @@
+# Monorail Data Storage
+
+## Objective
+
+Monorail needs a data storage system that is expressive, scalable, and
+performs well.
+
+Monorail needs to store complex business objects for projects, issues,
+users, and several other entities.  The tool is central to several
+software development processes, and we need to be able to continue to
+add more kinds of business objects while maintaining high confidence
+in the integrity of the data that we host.
+
+The database needs to scale well past one million issues, and support
+several thousand highly engaged users and automated clients, while
+also handling requests from many thousands more passive visitors.
+While most issues have only a few kilobytes of text, a small number of
+issues can have a huge number of comments, participants, attachments,
+or starrers.
+
+As a broad performance guideline, users expect 90% of operations to
+complete in under one second and 99% of operations to be done in under
+two seconds.  That timeframe must include work for all data
+transmission, and business logic, which leaves under one second for
+all needed database queries.  The system must perform well under load,
+including during traffic spikes due to legitimate usage or attempts to
+DoS the site.
+
+Of course, we need our data storage system to be secure and reliable.
+Even though the data storage system is not accessed directly by user
+requests, there is always the possibility of an SQL injection attack
+that somehow makes it through our request handlers.  We also need
+access controls and policies to reduce insider risks.
+
+## Background
+
+Monorail evolved from code.google.com which used Bigtable for data
+storage and a structured variant of Google's web search engine for
+queries.  Using Bigtable gave code.google.com virtually unlimited
+scalability in terms of table size, and made it easy to declare a
+schema change at any time.  However, it was poor at returning complete
+result sets and did not enforce referential integrity.  Over time, the
+application code accumulated more and more layers of backward
+compatibility with previous data schemas, or the team needed to spend
+big chunks of time doing manual schema migrations.
+
+Monorail is implemented in python and uses protocol buffers to
+represent business objects internally.  This worked well with Bigtable
+because it basically stored protocol buffers natively, but with a
+relational database it requires some ORM code.  The construction of
+large numbers of temporary objects can make python performance
+inconsistent due to the work needed to construct those objects and the
+memory management overhead.  In particular, the ProtoRPC library can
+be slow.
+
+## Approach
+
+Monorail uses Google Cloud SQL with MySQL as its main data storage
+backend.  The key advantages of this approach are that it is familiar
+to many engineers beyond Google, scales well to several million rows,
+allows for ad-hoc queries, enforces column types and referential
+integrity, and has easy-to-administer access controls.
+
+We mitigate several of the downsides of this approach as follows:
+
+*  The complexity of forming SQL statements is handled by `sql.py`
+   which exposes python functions that offer most options as keyword
+   parameters.
+
+*  The potential slowness of executing complex queries for issue search
+   is managed by sharding queries across replicas and using an index
+   that includes a `shard_id` column.
+
+*  The slowness of constructing protocol buffers is avoided by using a
+   combination of RAM caches and memcache.  However, maintaining
+   distributed caches requires distributed invalidation.
+
+*  The complexity of ORM code is managed by organizing it into classes
+   in our services layer, with much of the serialization and
+   deserialization code in associated cache management classes.
+
+*  The security risk of SQL injection is limited by having all code
+   paths go through `sql.py` which consistently makes use of the
+   underlying MySQL library to do escaping, and checks each statement
+   against an allow-list of regular expressions to ensure that no
+   user-controlled elements are injected.
+
+## Detailed design: Architecture
+
+Monorail is a GAE application with multiple services that each have
+automatic scaling of the number of instances.  The database is a MySQL
+database with one primary for most types of reads and all writes, plus
+a set of read-only replicas that are used for sharded issue queries
+and comments.  The main purpose of the sharded replicas is to
+parallelize the work needed to scan table rows during issue search
+queries, and to increase the total amount of RAM used for SQL sorting.
+A few other queries are sent to random replicas to reduce load on the
+primary.
+
+To increase the DB RAM cache hit ratio, each logical data shard is
+sent to a specific DB replica.  E.g., queries for shard 1 are sent to
+DB replica 1.  In cases where the desired DB replica is not available,
+the query is sent to a random replica.  An earlier design iteration
+also required `besearch` GAE instances to have 1-to-1 affinity with DB
+replicas, but that constraint was removed in favor of automatic
+scaling of the number of `besearch` instances.
+
+## Detailed design: Protections against SQL injection attacks
+
+With very few exceptions, SQL statements are not formed in any other
+place in our code than in `sql.py`, which has had careful review.
+Values used in queries are consistently escaped by the underlying
+MySQL library.  As a further level of protection, each SQL statement
+is matched against an allow-list of regular expressions that ensure
+that we only send SQL that fits our expectations.  For example,
+`sql.py` should raise an exception if an SQL statement somehow
+specified an `INTO outfile` clause.
+
+Google Cloud SQL and the MySQL library also have some protections
+built in.  For example, a statement sent to the SQL server must be a
+single statement: it cannot have an unescaped semicolon followed by a
+second SQL statement.
+
+Also, to the extent that user-influenced queries are sent to DB
+replicas, even a malicious SQL injection could not alter data or
+permissions because the replicas are read-only.
+
+## Detailed design: Two-level caches and distributed cache invalidation
+
+Monorail includes an `AbstractTwoLevelCache` class that combines a
+`RAMCache` object with logic for using memcache.  Each two-level cache
+or RAM cache is treated like a dictionary keyed by an object ID
+integer.  For example, the user cache is keyed by user ID number.
+Each cache also has a `kind` enum value and a maximum size.  When a
+cache is constructed, it registers with a singleton `CacheManager`
+object that is used during distributed invalidation.
+
+Each type of cache in Monorail implements a subclass of
+`AbstractTwoLevelCache` to add logic for retrieving items from the
+database on a cache miss and deserializing them.  These operations all
+work as batch operations to retrieve a collection of keys into a
+dictionary of results.
+
+When retrieving values from a two-level cache, first the RAM cache in
+that GAE instance is checked.  On a RAM miss, memcache is checked.  If
+both levels miss, the `FetchItems()` method in the cache class is run
+to query the database and deserialize DB rows into protocol buffer
+business objects.
+
+Values are never explicitly added to a two-level cache by calling
+code.  Adding an item to the cache happens only as a side effect of a
+retrieval that had a cache miss and required a fetch.
+
+When services-level code updates a business object in the database, it
+calls `InvalidateKeys()` on the relevant caches.  This removes the old
+key and value from the local RAM cache in that GAE instance and
+deletes any corresponding entry in memcache.  Of course, updating RAM
+in one GAE instance does not affect any stale values that are cached
+in the RAM of other GAE instances.  To invalidate those items, a row
+is inserted into the `Invalidate` table in the DB.  In cases where it
+is easier to invalidate all items of a given kind, the value zero is
+used as a wildcard ID.
+
+Each time that any GAE instance starts to service a new incoming
+request, it first checks for any new entries in the `Invalidate`
+table.  For up to 1000 rows, the `CacheManager` drops items from all
+registered RAM caches that match that kind and ID.  Request processing
+then proceeds, and any attempts to retrieve stale business objects
+will cause cache misses that are then loaded from the DB.
+
+Always adding rows to the `Invalidate` table would eventually make
+that table huge.  So, Monorail uses a cron task to periodically drop
+old entries in the `Invalidate` table.  Only the most recent 1000
+entries are kept.  If a GAE instance checks the `Invalidate` table and
+finds that there are 1000 or more new entries since the list time it
+checked, the instance will flush all of its RAM caches.
+
+Invalidating local caches at the start of each request does not handle
+the situation where one GAE instance is handling a long-running
+request and another GAE instance updates business objects at the same
+time.  The long-running request may have retrieved and cached some
+business objects early in the request processing, and then use a stale
+cached copy of one of those same business objects later, after the
+underlying DB data has changed.  To avoid this, services-level code
+that updates business objects specifies the keyword use_cache=False to
+retrieve a fresh copy of the object for each read-modify-write
+operation.  As a further protection, the Issue protocol buffer has an
+`assume_stale` boolean that helps check that issues from the cache are
+not written back to the database.
+
+## Detailed design: Read-only mode
+
+Monorail has a couple of different levels of read-only modes.  The
+entire site can be put into a read-only mode for maintenance by
+specifying `read_only=True` in `settings.py`.  Also, individual
+projects can be put into read-only mode by setting the
+`read_only_reason` field on the project business object.
+
+Read-only projects are a vestigial code.google.com feature that is not
+currently exposed in any administrative UI.  It is implemented by
+passing an EZT variable which causes `read-only-rejection.ezt` to be
+shown to the user instead of the normal page content.  This UI-level
+condition does not prevent API users from performing updates to the
+project.  In fact, even users who have existing pages open can submit
+forms to produce updates.
+
+The site-wide read-only mode is implemented in `registerpages.py` to
+not register POST handlers when the site is in read-only mode for
+maintenance.  Also, in both the Endpoints and pRPC APIs there are
+checks that reject requests that make updates during read-only mode.
+
+## Detailed design: Connection pooling and the avoid list
+
+It is faster to use an existing SQL connection object than to create a
+new one.  So, `sql.py` implements a `ConnectionPool` class to keep SQL
+connection objects until they are needed again.  MySQL uses implicit
+transactions, so any connection keeps reading data as it existed at
+the time of the last commit on that connection.  To get fresh data, we
+do an empty commit on each connection at the time that we take it from
+the pool.  To ensure that that commit is really empty, we roll back
+any uncommitted updates for any request that had an exception.
+
+A `MonorailConnection` is a collection of SQL connections with one for
+the primary DB and one for each replica that is used during the current
+request.  Creating a connection to a given replica can fail if that
+replica is offline.  Replicas can be restarted by the Google Cloud SQL
+service at any time, e.g., to update DB server software.  When
+Monorail fails to create a connection to a replica, it will simply use
+a different replica instead.  However, the process of trying to
+connect can be slow.  So, Monorail implements a dictionary with the
+shard IDs of any unavailable replicas and the timestamp of the most
+recent failure.  Monorail will avoid an unavailable replica for 45
+seconds, giving it time to restart.
+
+## Detailed design: Search result caches
+
+This is described in [how-search-works.md](how-search-works.md).
+
+## Detailed design: Attachments
+
+Monorail's SQL database contains rows for issue attachments that
+contain information about the attachment, but not the attachment
+content.
+
+Attachment content is stored in Google Cloud Storage.  Each attachment
+is given a path of the form `/BUCKET/PROJECT_ID/attachments/UUID`
+where UUID is a string with random hexadecimal digits generated by
+python's [uuid
+library](https://docs.python.org/2.7/library/uuid.html).  Each GCS
+object has options specified which includes a `Content-Disposition`
+header value with the desired filename.  We use the name of the
+uploaded file in cases where it is known to be safe to keep it,
+otherwise we use `attachment.dat`.
+
+The MIME type of each attachment is determined from the file name and
+file contents.  If we cannot determine a well-known file type,
+`application/octet-stream` is used instead.
+
+For image attachments, we generate a thumbnail-sized version of the
+image using GAE's
+[image](https://cloud.google.com/appengine/docs/standard/python/images/)
+library at upload time.
+
+Attachments are served to users directly from GCS via a signed link.
+The risk of malicious attachment content is reduced by using a
+different "locked domain" for each attachment link.  This prevents any
+Javascript in an attachment from accessing cookies intended for our
+application or any other website or even another attachment.
+
+## Detailed design: Secrets
+
+Monorail stores some small pieces of data in Datastore rather than
+Google Cloud Storage.  This data includes the secret keys used to
+generate XSRF tokens and authentication headers for reply emails.
+These keys will never be a valid part of any SQL data export, so they
+would need to be excluded from access granted to any account used for
+SQL data export.  Rather than take on that complexity, we used
+Datastore instead, and we do not grant access for anyone outside the
+Monorail team to access the project's Datastore entities.
+
+## Detailed design: Source code locations
+*  `framework/sql.py`: Table managers, connection pools, and other utilities.
+*  `framework/filecontent.py`: Functions to determine file types for
+    attachments.
+*  `framework/gcs_helpers.py`: Functions to write attachments into Google
+    Cloud Storage.
+*  `services/caches.py`: Base classes for caches.
+*  `services/cachemanager.py`: Code for distributed invalidation and cron job
+    for the `Invalidate` table.
+*  `services/secrets_svc.py`: Code to get secret keys from Datastore.
+*  `services/*.py`: Persistence code for business objects.
+*  `settings.py`: Configuration of DB replicas and read_only mode.
diff --git a/doc/design/emails.md b/doc/design/emails.md
new file mode 100644
index 0000000..851e072
--- /dev/null
+++ b/doc/design/emails.md
@@ -0,0 +1,333 @@
+# Monorail email design
+
+## Objective
+
+Monorail needs a strong outbound and inbound email strategy that keeps
+our users informed when issues require their attention.  We generate a
+variety of outbound messages.  We accept inbound email replies from
+human users who find it easier to post comments via email.  And, we
+integrate with alerts monitoring via inbound email.
+
+Our email features must scale up in several dimensions, including:
+
+*  The number of messages generated, which is driven by the issue
+   activity
+
+*  The number of distinct types of notifications that the tool can
+   generate
+
+*  The amount of inbound replies, alerts, as well as spam and bounce
+   notifications
+
+*  The variety of access controls needed for sensitive issues and
+   different types of users
+
+## Background
+
+Monorail is a [different-time, different-place CSCW
+application](https://en.wikipedia.org/wiki/Computer-supported_cooperative_work)
+in which users may need to work with each other on multiple occasions
+to resolve an issue.  Furthermore, the exact set of collaborators
+needed to resolve a given issue is discovered as part of the work for
+that issue rather than being known from the start.  And, each issue
+participant is likely to be highly multitasking across several issues
+and other development tasks.  As is normal for issue tracking tools,
+we send email notifications to issue participants for each issue
+change.  However, because participants can get a fair number of emails
+from us, they want to be able to filter those emails based on the
+reason why the email was sent to them.
+
+Email is not an inherently secure or private technology.  We trust
+that email is delivered to the recipient without being read by any
+servers along the way.  However, some email addresses may be
+individual users and others might be mailing lists, so we "nerf"
+messages in cases where Monorail has no indication that the recipient
+is an individual.  Also, it is possible to forge an email reply, so we
+rely on shared secrets to authenticate that an inbound message came
+from a given user.  Because the email sender line is so vulnerable to
+abuse, GAE does not allow GAE apps to set that header arbitrarily.
+Instead, we rely on a combination of supported email senders, DNS SPF
+entries, and friendly `From:` lines.
+
+Users sometimes make mistakes when entering email addresses, and email
+accounts can be shut down over time, both of these situations generate
+bounce emails.  Continued generation of outbound emails that bounce is
+a waste of resources and quota.
+
+## Approach
+
+To keep issue participants engaged, whenever an issue is created or
+has a new comment, we send email notifications to the issue owner,
+CC'd users, users who have starred the issue, and users who have
+subscriptions that match the new state of the issue.  Monorail
+generates notifications for individual issue changes, as well as bulk
+edits, blocking notifications, and approval changes.  Monorail has a
+special rule for "noisy" issues, which is to only generate emails when
+project members post comments.  Also, when a date-type field has a
+date value for a date that has arrived, we send a "follow-up" email to
+remind the user to update the issue.
+
+Monorail sends individual emails to each recipient rather than adding
+them all to one long `Cc:` line.  The reason for this is so that we
+can personalize the content of the message to each user based on their
+preferences, the reason why the message was sent to them, and our
+level of trust of that address.  Also, using individual messages
+allows us to authenticate replies with a secret that is shared
+individually with each user.  And, individual emails ensure that email
+replies come directly back to Monorail rather than going to other
+issue participants.  This reduces cases of duplicate emails and allows
+for enforcement of permissions that might have changed after an
+earlier notification was sent.
+
+To keep outbound emails organized as threads in recipients' inboxes,
+we set a `References:` header message ID that is used for all messages
+that belong in that thread.  However, as a GAE app, Monorail has no
+way to access the message ID of any actual outbound email.  Also, any
+given thread participant might join the conversation late, after
+missing the first email message that would have anchored the thread.
+So, instead of using actual message IDs, we generate a synthetic
+message ID that represents the thread as a whole and then reference
+that same ID from each email.
+
+When we send outbound emails, we include a shared secret in the
+`References:` header that an email client will echo back in the reply,
+much like a cookie.  When we receive an inbound email, we verify that
+the `References:` header includes a value that is appropriate to the
+specified user and issue.  One exception to this rule is that we allow
+inbound emails from the alert system (Monarch).
+
+Bounces are handled by flagging the user account with a bouncing
+timestamp.  Monorail will not attempt to send any more emails to a
+bouncing user account.  A user can clear the bouncing timestamp by
+clicking a button on their user profile page.
+
+An inbound email is first verified to check the identity of the sender
+and the target issue.  After permissions are checked, the message body
+is parsed.  The first few lines of the body can contain assignments to
+the issue summary, status, labels, and owner.  The rest of the body is
+posted to the issue as a comment.  Common email message footers and
+.sig elements are detected and stripped from the comment.  The
+original email message is also stored in the DB and can be viewed by
+project owners.
+
+## Detailed design: Architecture
+
+Monorail is a GAE application with several services.  The `default`
+service responds to user requests, while the `latency-insensitive`
+service executes a variety of cron jobs and follow-up tasks.
+
+Outbound email is generated in tasks that run in the
+`latency-insensitive` service so as to remove that work from the time
+needed to finish the user's request, and to avoid spikes in `default`
+instance allocations when many outbound emails are generated at one
+time.  We use automatic scaling, but turnover in `default` instances
+would lower the RAM cache hit ratio.
+
+Inbound email is currently handled in the `default` service.  However,
+those requests could be routed to the `latency-insensitive` service in
+the future.
+
+## Detailed design: Domain configuration
+
+Monorail serves HTTP requests sent to the `monorail-prod.appspot.com`
+domain as well as `bugs.chromium.org` and other "branded" domains
+configured in `settings.py` and the GAE console.  These custom domains
+are also used in the email address part of the `From:` line of outbound
+emails.  They must be listed as `monorail@DOMAIN` in the email senders
+tab of the settings page for the GAE app.
+
+The `Reply-To:` line is always set to
+`PROJECTNAME@monorail-prod.appspotmail.com` and is not branded.
+
+The DNS records for each custom domain must include a TXT record like
+`v=spf1 include:_spf.google.com ?all` to tell other servers to trust
+that the email sent from a certain list of SMTP servers is legitimate
+for our app.
+
+## Detailed design: Key algorithms and data structures
+
+`User` protocol buffers include a few booleans for the user's
+preferences for email notifications.
+
+When generating a list of notification recipients, Monorail builds a
+data structure called a `group_reason_list` which pairs
+`addr_perm_lists` with reasons why those addresses are being notified.
+Each `addr_perm_list` is a list of named tuples that have fields for a
+project membership boolean, an email address, an optional `User`
+protocol buffer, a constant indicating whether that user has permission
+to reply, and a `UserPrefs` protocol buffer.
+
+The `group_reason_list` is built up by code that is specific to each
+reason.  A given email address might be notified for more than one
+reason.  Then, that list is inverted to make a dictionary keyed by
+recipient email address that lists the reasons why that address was
+notified.  Entries for linked accounts are then merged.  And, the list
+of reasons is used to add a footer to the email body that lists the
+specific reasons for that user.
+
+When generating an outbound email, we add a `References:` header to
+make the messages for the same issue thread together and to
+authenticate any reply.  That header value is computed using a hash of
+the user's email address and the subject line of the emails (which
+includes the project name and issue ID number).  Each part is combined
+with a secret value stored in Cloud Datastore and accessed via
+`secrets_svc.py`.
+
+Most outbound emails include the summary line of the issue, the
+details of any updates, and the text of the comment.  However, there
+are some cases where Monorail sends a "nerfed" safe subset of
+information that consists only of a link to the issue and a generic
+message saying that the issue has been created or updated.  We send a
+link-only notification when the issue is restricted and the recipient
+may be a mailing list.  Monorail cannot know if an email address
+represents a mailing list or an individual user, so it assumes that
+any address corresponding to a `User` record which has never visited
+the site is a mailing list.
+
+Monorail considers an issue to be "noisy" if the issue already has
+more than 100 comments and more than 100 starrers.  Such issues can
+generate a huge amount of email that would consume our quota and have
+low value for most recipients.  A large amount of chatter by
+non-members can make it harder for project members to notice the
+comments that are important to resolving the issue.  Monorail only
+sends notifications for noisy issues if the comment author is a
+project member.
+
+## Detailed design: Code walk-throughs
+
+### Individual issue change
+
+1. User A posts a comment on an existing issue 123.
+
+1.  The user's request is handled by the `default` GAE service.
+    `work_env` coordinates the steps to update issue 123 in the
+    database and invalidate it in caches.
+
+1.  `work_env` calls `PrepareAndSendIssueChangeNotification()` to
+    create a task entry that includes a reference to the new comment
+    and a field to omit user A being notified.
+
+1.  That task is processed by the `latency-insensitive` GAE service by
+    the `NotifyIssueChangeTask` handler.  It determines if the issue is
+    noisy, gathers potential recipents by reasons, omits the comment
+    author, and checks whether each recipient would be allowed to view
+    the issue.  Three different email bodies are prepared: one for
+    non-members that has other users' email addresses obscured, one
+    for members that reveals email addresses, and one for link-only
+    notifications.
+
+1.  The group reason list is then passed to
+    `MakeBulletedEmailWorkItems()` which inverts the list, adds
+    personalized footers, and converts the plain text email body into
+    a simple HTML formatted email body.  It then returns a list with a
+    new task record for each email message to be sent.
+
+1.  If the generation task fails for any reason, it will be retried,
+    but no email messages are actually sent on the failed run.  If the
+    entire process up to this point succeeds, then
+    `AddAllEmailTasks()` is called to enqueue each of the single
+    message tasks.
+
+1.  Those tasks are handled by the `OutboundEmailTask` handler in the
+    `latency-insensitve` service.  Individual tasks are used for each
+    outbound email because sending emails can sometimes fail.  Each
+    task is automatically retried without rerunning other tasks.
+
+### Bulk issue change
+
+The process is similar to the individual issue change process, except
+that a list of allowed recipients is made for each issue.  Then, that
+list is inverted to make a list of issues that a given recipient
+should be notified about.  This is done in the `NotifyBulkChangeTask`
+handler.
+
+When a given recipient is to be notified of multiple issues that were
+changed, the email message body lists the updates made and then lists
+each of the affected issues.  In contrast, when a given recipient is
+to be notified of exactly one issue, the body is formatted to look
+like the individual issue change notification.
+
+### Blocking change
+
+When issue 123 is edited to make it blocked on issue 456, participants
+in the upstream issue (456) are notified.  Likewise, when a blocking
+relationship is removed, another notification is sent.  This is done
+by the `NotifyBlockingChangeTask` handler.
+
+### Approval issue change
+
+This is handled by `NotifyApprovalChangeTask`.
+
+TODO: needs more details.
+
+### Date-action notifications
+
+Some date-type custom fields can be configured to send follow-up
+reminders when the specified date arrives.
+
+1.  The date-action cron task runs once each day as configured in
+    `cron.yaml`.
+
+1.  The `DateActionCron` handler is run in the `latency-insensitive`
+    GAE service.  It does an SQL query to find issues that have a
+    date-type field set to today's date and that is configured to send
+    a notification.  For each such issue, it enqueues a new task to
+    handle that date action.
+
+1.  Each of those tasks is handled by `IssueDateActionTask` which
+    works like an individual email notification.  The main difference
+    is that issue subscribers are not notified and issue starrers are
+    only notified if they have opted into that type of notification.
+    The handler posts a comment to the issue, calls
+    `ComputeGroupReasonList()` to compute a group reason list, calls
+    `MakeBulletedEmailWorkItems()` to make individual message tasks,
+    and calls `AddAllEmailTasks()` to enqueue those email tasks.
+
+### Inbound email processing
+
+TODO: needs more details
+
+### Alerts
+
+TODO: needs more details
+
+## Detailed design: Source code locations
+
+*  `settings.py`: Configuration of branded domains.  Also, email
+   From-line string templates that are used to re-route email
+   generated on the dev or staging servers.
+
+*  `businesslogic/work_env.py`: Internal handlers for many changes such
+   as updating issues.  It checks permissions, coordinates updates to
+   various backends, and enqueues tasks for follow-up work such as
+   sending notifications.
+
+*  `features/notify.py`: This file has most email notification task
+   handlers.
+
+*  `features/notify_reasons.py`: Functions to compute `AddrPermLists`
+   from a list of potential recipients by checking permissions and
+   looking up user preferences.  It combines these lists into an
+   overall group reason list.  Also, computes list of issue
+   subscribers by evaluating saved queries.
+
+*  `features/notify_helpers.py`: Functions to generate personalized
+   email bodies and enqueue lists of individual email tasks based on a
+   group reason list.
+
+*  `features/dateaction.py`: Cron task for the date-action feature and
+   task handler to generate the follow-up comments and email
+   notifications.
+
+*  `features/inboundemail.py`: Handlers for inbound email messages and
+   bounce messages.
+
+*  `features/commands.py` and `features/commitlogcomands.py`: Parsing
+   and processing of issue field assignment lines that can be at the
+   top of the body of an inbound email message.
+
+*  `features/alert2issue.py`: Functions to parse emails from our alert
+   monitoring service and then create or update issues.
+
+*  `framework/emailfmt.py`: Utility functions for parsing and
+   generating email headers.
diff --git a/doc/design/how-search-works.md b/doc/design/how-search-works.md
new file mode 100644
index 0000000..c724223
--- /dev/null
+++ b/doc/design/how-search-works.md
@@ -0,0 +1,395 @@
+# How Search Works in Monorail
+
+[TOC]
+
+## Objective
+
+Our goal for Monorail's search design is to provide a fast issue
+search implementation for Monorail that can scale well past one
+million issues and give results in about two seconds.  Monorail
+supports a wide range of query terms, so we cannot simply predefine
+indexes for all possible queries.  Monorail also needs to be
+scalable in the number of requests to withstand DoS attacks,
+ill-behaved API clients, and normal traffic spikes.  A key requirement
+of Monorail's search is to give exact result counts even for fairly
+large result sets (up to 100,000 issues).
+
+## Background
+
+From 2005 to 2016, we tracked issues on code.google.com, which stored
+issues in Bigtable and indexed them with Mustang ST (a structured
+search enhancement to Google's web search).  This implementation
+suffered from highly complex queries and occasional outages.  It
+relied on caching to serve popular queries and could suffer a
+"stampede" effect when the cache needed to be refilled after
+invalidations.
+
+When the Monorail SQL database was being designed in 2011, Google
+Cloud SQL was much slower than it is today.  Some key factors made a
+non-sharded design unacceptable:
+
+*  The SQL database took too long to execute a query.  Basically, the
+   time taken is proportional to the number of `Issue` table rows
+   considered.  While indexes are used for many of Monorail's other
+   queries, the issue search query is essentially a table scan in many
+   cases.  The question is how much of the table is scanned.
+
+*  Getting SQL result rows into python was slow.  The protocol between
+   the database and app was inefficient, prompting some significant
+   work-arounds that were eventually phased out.  And, constructing a
+   large number of ProtoRPC internal business objects was slow.  Both
+   steps were CPU intensive.  Being CPU-bound produced a poor user
+   experience because the amount of CPU time given to a GAE app is
+   unpredictable, leading to frustrating latency on queries that
+   seemed fine previously.
+
+## Overview of the approach
+
+The design of our search implementation basically addresses these
+challenges point-by-point:
+
+Because there is no one index that can narrow down the number of table
+rows considered for all possible user issue queries, we sharded the
+database so that each table scan is limited to one shard.  For
+example, with 10 shards, we can use 10 database instances in parallel,
+each scanning only 1/10 of the table rows that would otherwise be
+scanned.  This saves time in retrieving rows.  Using 10 DB instances
+also increases the total amount of RAM available to those instances
+which increases their internal cache hit ratio and allows them to do
+more sorting in RAM rather than using slower disk-based methods.
+
+Because constructing ProtoRPC objects was slow, we implemented RAM
+caches and used memcache to reduce the number of issues that need to
+be loaded from the DB and constructed in python for any individual
+user request.  Using RAM caches means that we can serve traffic spikes
+for popular issues and queries well, but it also required us to
+implement a distributed cache invalidation feature.
+
+Sharding queries at the DB level naturally led to sharding requests
+across multiple besearch instances in python.  Using 10 besearch
+instances gives 10x the amount of CPU time available for constructing
+ProtoRPC objects.  Of course, sharding means that we needed to
+implement a merge sort to produce an overall result list that is
+sorted.
+
+Another aspect of our approach is that we reduce the work needed in
+the main SQL query as much as possible by mapping user-oriented terms
+to internal ID integers in python code before sending the query to
+SQL.  This mapping could be done via JOIN clauses in the query, but
+the information needed for these mappings rarely changes and can be
+cached effectively in python RAM.
+
+
+## Detailed design: Architecture
+
+The parts of Monorail's architecture relevant to search consists of:
+
+*  The `default` GAE service that handles incoming requests from users,
+   makes sharded queries to the `besearch` service, integrates the
+   results, and responds to the user.
+
+*  The `besearch` GAE service that handles sharded search requests from
+   the `default` module and communicates with a DB instance.  The
+   `besearch` service handles two kinds of requests: `search` requests
+   which generate results that are not permission-checked so that they
+   can be shared among all users, and `nonviewable` requests that do
+   permission checks in bulk for a given user.
+
+* A primary DB instance and 10 replicas.  The database has an
+   Invalidate table used for distributed invalidation.  And,
+   issue-related tables include a `shard` column that allows us to
+   define a DB index that includes the shard ID.  The worst (least
+   specific) key used by our issue query is typically `(shard,
+   status_id)` when searching for open issues and `(shard,
+   project_id)` when searching all issues in a project.
+
+*  There are RAM caches in the `default` and `besearch` service
+   instances, and we use memcache for both search result sets and for
+   business objects (e.g., projects, configs, issues, and users).
+
+*  Monorail uses the GAE full-text search index library for full-text
+   terms in the user query.  These terms are processed before the
+   query is sent to the SQL database.  The slowness of GAE full-text
+   search and the lack of integration between full-text terms and
+   structured terms is a weakness of the current design.
+
+## Detailed design: Key algorithms
+
+### Query parsing
+
+To convert the user's query string into an SQL statement,
+FrontendSearchPipeline first parses parentheses and OR statemeents, splitting
+up a query into separate subqueries that can be retrieved from the cache or
+sent to different backend shards.
+
+The generated subqueries should collectively output the same set of search
+results as the initial query, but without using ORs or parentheses in their
+syntax. An example is that the query `'A (B OR C)'` would be split into the
+subqueries `['A B', 'A C']`.
+
+Then, each besearch shard parses the subquery it was assigned using the helpers
+in search/query2ast.  We first parse the into query terms using regular
+expressions.  Then, we build an abstract syntax tree (AST).  Then, we simplify
+that AST by doing cacheable lookups in python.  Then, we convert the simplified
+AST into a set of LEFT JOIN, WHERE, and ORDER BY clauses.
+
+It is possible for a query to fail to parse and raise an exception
+before the query is executed.
+
+### Result set representations
+
+We represent issue search results as lists of global issue ID numbers
+(IIDs).  We represent nonviewable issues as sets of IIDs.
+
+To apply permission checks to search results, we simply use set
+membership: any issue IID that is in the nonviewable set for the
+current user is excluded from the allowed results.
+
+### Sharded query execution
+
+To manage sharded requests to `besearch` backends, the
+`FrontendSearchPipeline` class does the following steps:
+
+1.  The constructor checks the user query and can determine an error
+    message to display to the user.
+
+1.  `SearchForIIDs()` calls `_StartBackendSearch()` which determines
+    the set of shards that need to be queried, checks memcache for
+    known results and calls backends to provide any missing results.
+    `_StartBackendSearch()` returns a list of rpc_tuples, which
+    `SearchForIIDs()` waits on.  Each rpc_tuple has a callback that
+    contains some retry logic.  Sharded nonviewable IIDs are also
+    determined. For each shard, the allowed IIDs for the current user
+    are computed by removing nonviewable IIDs from the list of result
+    IIDs.
+
+1.  `MergeAndSortIssues()` merges the sharded results into an overall
+    result list of allowed IIDs by calling `_NarrowFilteredIIDs()` and
+    `_SortIssues()`.  An important aspect of this step is that only a
+    subset of issues are retrieved.  `_NarrowFilteredIIDs()` fetches a
+    small set of sample issues and uses the existing sorted order of
+    IIDs in each shard to narrow down the set of issues that could be
+    displayed on the current pagination page.  Once that subset is
+    determined, `_SortIssues()` calls methods in
+    `framework/sorting.py` to do the actual sorting.
+
+### Issue position in list
+
+Monorail's flipper feature also uses the `FrontendSearchPipeline`
+class, but calls `DetermineIssuePosition()` rather than
+`MergeAndSortIssues()`.  `DetermineIssuePosition()` also retrieves
+only a subset of the issues in the allowed IIDs list.  For each shard,
+it uses a sample of a few issues to determine the sub-range of issues
+that must be retrieved, and then sorts those with the current issue to
+determine the number of issues in that shard that would precede the
+currently viewed issue.  The position of the current issue in the
+overall result list is the sum of the counts of preceding issues in
+each shard.  Candidates for the next and previous issues are also
+identified on a per-shard basis, and then the overall next and
+previous issues are determined.
+
+
+### Memcache keys and invalidation
+
+We cache search results keyed by query and shard, regardless of the
+user or their permissions.  This allows the app to reuse cached
+results for different users.  When issues are edited, we only need to
+invalidate the shard that that issue belongs to.
+
+The key format for search results in memcache is `memcache_key_prefix,
+subquery, sd_str, sid`, where:
+
+ * `memcache_key_prefix` is a list of project IDs or `all`
+ * `subquery` is the user query (or one OR-clause of it)
+ * `sd_str` is the sort directive
+ * `sid` is the shard ID number
+
+If it were not for cross-project search, we would simply cache when we
+do a search and then invalidate when an issue is modified.  But, with
+cross-project search we don't know all the memcache entries that would
+need to be invalidated.  So, instead, we write the search result cache
+entries and then an initial modified_ts value for each project if it
+was not already there. And, when we update an issue we write a new
+modified_ts entry for that issue's project shard. That entry
+implicitly invalidates all search result cache entries that were
+written earlier because they are now stale.  When reading from the
+cache, we ignore any cache entry that corresponds to a project with
+modified_ts after the cached search result timestamp, because it is
+stale.
+
+We cache nonviewable IID sets keyed by user ID, project ID, and shard
+ID, regardless of query.  We only need to invalidate cached
+nonviewable IDs when a user role is granted or revoked, when an issue
+restriction label is changed, or a new restricted issue is created.
+
+
+## Detailed design: Code walk-throughs
+
+### Issue search
+
+1. The user makes a request to an issue list page.  For the EZT issue
+   list page, the usual request handling is done, including a call to
+   `IssueList#GatherPageData()`.  For, the web components list page or
+   an API client, the `ListIssues()` API handler is called.
+
+1. One of those request handlers calls `work_env.ListIssues()` which
+   constructs a `FrontendSearchPipeline` and works through the entire
+   process to generate the list of issues to be returned for the
+   current pagination page.  The pipeline object is returned.
+
+The `FrontendSearchPipeline` process steps are:
+
+1.  A `FrontendSearchPipeline` object is created to manage the search
+    process.  It immediately parses some simple information from the
+    request and initializes variables.
+
+1.  `WorkEnv` calls `SearchForIIDs(`) on the
+    `FrontendSearchPipeline`. It loops over the shards and:
+
+  * It checks memcache to see if that (query, sort, shard_id) is
+    cached and the cache is still valid.  If found, these can be used
+    as unfiltered IIDs.
+
+  * If not cached, it kicks off a request to one of the GAE `besearch`
+    backend instances to get fresh unfiltered IIDs.  Each backend
+    translates the user query into an SQL query and executes it on one
+    of the SQL replicas.  Each backend stores a list of unfiltered
+    IIDs in memcache.
+
+  * In parallel, unviewable IIDs for the current user are looked up in
+    the cache and possibly requested from the `besearch` backends.
+
+  * Within each shard, unviewable IIDs are removed from the unfiltered
+    IIDs to give sharded lists of filtered IIDs.
+
+  * Sharded lists of filtered IIDs are combined into an overall result
+    that has only the issues needed for the current pagination page.
+    This step involves retrieving sample issues and a few distinct
+    sorting steps.
+
+  * Backend calls are made with the `X-AppEngine-FailFast: Yes` header
+    set, which means that if the selected backend is already busy, the
+    request immediately fails so that it can be retried on another
+    backend that might not be busy.  If there is an error or timeout
+    during any backend call, a second attempt is made without the
+    `FailFast` header. If that fails, that failure results in an error
+    message saying that some backends did not respond.
+
+### Issue flipper
+
+For the issue detail page, we do not need to completely sort and
+paginate the issues.  Instead, we only need the count of allowed
+issues, the position of the current issue in the hypothetically sorted
+list, and the IDs of the previous and next issues, if any, which we
+call the "flipper" feature.
+
+As of December 2019, the flipper does not use the pRPC API yet.
+Instead, it uses an older JSON servlet implementation.  When it is
+implemented in the pRPC API, only the first few steps listed below
+will change.
+
+The steps for the flipper are:
+
+1.  The web components version of the issue detail page makes an XHR
+    request to the flipper servlet with the search query and the
+    current issue ref in query string parameters.
+
+1.  The `FlipperIndex` servlet decides if a flipper should be shown,
+    and whether the request is being made in the context of an issue
+    search or a hotlist.
+
+1.  It calls `work_env.FindIssuePositionInSearch()` to get the
+    position of the current issue, previous and next issues, and the
+    total count of allowed search results.
+
+1.  Instead of calling the pipeline's `MergeAndSortIssues()`, the
+    method `DetermineIssuePosition()` is called.  It retrieves only a
+    small fraction of the issues in each shard and determines the
+    overall position of the current issue and the IID of the preceding
+    and following issues in the sorted list.
+
+We also have special servlets that redirect the user to the previous
+or next issues given a current issue and a query.  These allow for
+faster navigation when the user clicks these links or uses the `j` or
+`k` keystrokes before the flipper has fully loaded.
+
+
+### Snapshots
+
+To power the burndown charts feature, every issue create and update operation
+writes a new row to the `IssueSnapshot` table. When a user visits a chart page,
+the search pipeline runs a `SELECT COUNT` query on the `IssueSnapshot` table,
+instead of what it would normally do, running a `SELECT` query on the `Issue`
+table.
+
+Any given issue will have many snapshots over time. The way we keep track of
+the most recent snapshots are with the columns `IssueSnapshot.period_start`
+and `IssueSnapshot.period_end`.
+
+If you imagine a Monorail instance with only one issue, each time we add
+a new snapshot to the table, we update the previous snapshot's `period_end`
+and the new snapshot's `period_start` to be the current unix time. This means
+that for a given range (period_start, period_end), there is only one snapshot
+that applies. The most recent snapshot always has its period_end set to
+MAX_INT.
+
+    Snapshot ID:  1         2         3                 MAX_INT
+
+    Unix time:
+    1560000004                        +-----------------+
+    1560000003              +---------+
+    1560000002    +---------+
+
+
+## Detailed design: Source code locations
+
+*  `framework/sorting.py`: Sorting of issues in RAM.  See sorting
+   design doc.
+
+*  `search/frontendsearchpipeline.py`: Where searches are processed first.
+   Sequences events for hitting sharded backends.  Does set logic to remove
+   nonviewable IIDs from the current user's search results.
+   MergeAndSortIssues() combines search results from each shard into a unified
+   result.  Also, DetermineIssuePosition() function calculates the position
+   of the current issue in a search result without merging the entire search
+   result.
+
+*  `search/backendsearchpipeline.py`: Sequence of events to search for
+   matching issues and at-risk issues, caching of unfiltered results,
+   and calling code for permissions filtering. Also, calls ast2select
+   and ast2sort to build the query, and combine SQL results with
+   full-text results.
+
+*  `search/backendsearch.py`: Small backend servlet that handles the
+   request for one shard from the frontend, uses a
+   backendsearchpipeline instance, returns the results to the frontend
+   as JSON including an unfiltered_iids list of global issue IDs.  As
+   a side-effect, each parallel execution of this servlet loads some
+   of the issues that the frontend will very likely need and
+   pre-caches them in memcache.
+
+*  `search/backendnonviewable.py`: Small backend servlet that finds
+   issues in one shard of a project that the given user cannot view.
+   This is not specific to the user's current query.  It puts that
+   result into memcache, and returns those IDs as JSON to the
+   frontend.
+
+*  `search/searchpipeline.py`: Utility functions used by both frontend
+   and backend parts of the search process.
+
+*  `tracker/tracker_helpers.py`: Has a dictionary of key functions used
+   when sorting issues in RAM.
+
+*  `services/issue_svc.py`: RunIssueQuery() runs an SQL query on a
+   specific DB shard.
+
+*  `search/query2ast.py`: parses the user’s query into an AST (abstract
+   syntax tree).
+
+*  `search/ast2ast.py`: Simplifies the AST by doing some lookups in
+   python for info that could be cached in RAM.
+
+*  `search/ast2select.py`: Converts the AST into SQL clauses.
+
+*  `search/ast2sort.py`: Converts sort directives into SQL ORDER BY
+   clauses.
diff --git a/doc/design/source-code-organization.md b/doc/design/source-code-organization.md
new file mode 100644
index 0000000..005bf57
--- /dev/null
+++ b/doc/design/source-code-organization.md
@@ -0,0 +1,208 @@
+# Monorail Source Code Organization
+
+[TOC]
+
+## Overview
+
+Monorail's source code organization evolved from the way that source
+code was organized for Google Code (code.google.com).  That site
+featured a suite of tools with code for each tool in a separate
+directory.  Roughly speaking, the majority of the code was organized
+to match the information architecture of the web site.  Monorail keeps
+that general approach, but makes a distinction between core issue
+tracking functionality (in the `tracker` directory) and additional
+features (in the `features` directory).
+
+Also dating back to Google Code's 2005 design, the concept of a
+"servlet" is key to Monorail's UI-centric source code organization.  A
+servlet is a python class with methods to handle all functionality for
+a given UI web page. Servlets handle the initial page rendering, any
+form processing, and have related classes for any XHR calls needed for
+interactive elements on a page.  Servlet's mix application business
+logic, e.g., permission checks, with purely UI logic, e.g., screen
+flow and echoing UI state in query string parameters.
+
+From 2018 to 2020, the old servlet-oriented source code organization
+is slowly being hollowed out and replaced with a more API-centric
+implementation.  Server-side python code is gradually being shifted
+from the `tracker`, `project`, `sitewide`, and `features` directories
+to the `api` and `businesslogic` directories.  While more UI logic is
+being shifted from python code into javascript code under
+`static_src`.
+
+Although Monorail's GAE app has several GAE services, we do not
+organize the source code around GAE services because they each share a
+significant amount of code.
+
+## Source code dependency layers
+
+At a high level, the code is organized into a few logical layers with
+dependencies going downward:
+
+App-integration layer
+
+*  The main program `monorailapp.py` that ties all the servlets together.
+
+*  All GAE configuration files, e.g., `app.yaml` and `cron.yaml`.
+
+Request handler layer
+
+*  This including servlets, inbound email, Endpoints, and rRPC.
+
+*  These files handle a request from a user, including syntactic
+   validation of request fields, and formatting of the response.
+
+Business logic layer
+
+*  This layer does the main work of handling a request, including
+   semantic validation of whether the request is valid, permission
+   checking, coordinating calls to the services layer, and kicking off
+   any follow-up tasks such as sending notification.
+
+*  Much of the content of `*_helper.py` and `*_bizobj.py` files also
+   belong to this layer, even though it has not been moved here as of
+   2019.
+
+Services layer
+
+*  This layer include our object-relational-mapping logic.
+
+*  It also manages connections to backends that we use other than the
+   database, for example full-text search.
+
+Framework layer
+
+*  This has code that provides widely used functionality and systemic
+   features, e.g.,`sql.py` and `xsrf.py`.
+
+Asset layer
+
+*  These are low-level files that can be included from anywhere in the
+   code, and should not depend on anything else.  For example,
+   `settings.py`, various `*_constants.py` files, and protobuf
+   definitions.
+
+
+## Source code file and directories by layer
+
+App-integration layer
+
+*  `monorailapp.py`: The main program that loads all web app request
+   handlers.
+
+*  `registerpages.py`: Code to register specific request handlers at
+   specific URLs.
+
+*  `*.yaml`: GAE configuration files
+
+Request handler layer
+
+*  `tracker/*.py`: Servlets for core issue tracking functionality.
+
+*  `features/*.py`: Servlets for issue tracking features that are not
+   core to the product.
+
+*  `project/*.py`: Servlets for creating and configuring projects and
+   memberships.
+
+*  `sitewide/*.py`: Servlets for user profile pages, the site home
+   page, and user groups.
+
+*  `templates/*/*.ezt`: Template files for old web UI page generation.
+
+*  `api/*.py`: pRPC API request handlers.
+
+*  `services/api_svc_v1.py`: Endpoints request handlers.
+
+*  `features/inboundemail.py`: Inbound email request handlers and bounces.
+
+*  `features/notify.py`: Email notification task handlers.
+
+
+Business logic layer
+
+*  `businesslogic/work_env.py`:  Internal API for monorail.
+
+*  `*/*_bizobj.py` and `*/*_helpers.py*` files: Business logic that was
+   written for servlets but that is gradually being used only through
+   work_env.
+
+Services layer
+
+*  `schema/*.sql`:  SQL database table definitions.
+
+*  `services/service_manager.py`: Simple object to hold all service objects.
+
+*  `services/caches.py` and `cachemanager.py`: RAM and memcache caches
+   and distributed invalidation.
+
+*  `services/issues_svc.py`: DB persistence for issues, comments, and
+   attachments
+
+*  `services/user_svc.py`: Persistence for user accounts.
+
+*  `services/usergroup_svc.py`: Persistence for user groups.
+
+*  `services/features_svc.py`: Persistence for hotlists, saved queries,
+   and filter rules.
+
+*  `services/chart_svc.py`: Persistence for issue snapshots and
+   charting queries.
+
+*  `services/secrets_svc.py`: Datastore code for key used to generate
+   XSRF tokens.
+
+*  `services/project_svc.py`: Persistence for projects and members.
+
+*  `services/config_svc.py`: Persistence for issue tracking
+   configuration in a project, except templates.
+
+*  `services/client_config_svc.py`: Persistence for API allowlist.
+
+*  `services/tracker_fulltext.py`: Connection to GAE fulltext search
+   index.
+
+*  `services/template_svc.py`: Persistence for issue templates.
+
+*  `services/star_svc.py`: Persistence for all types of stars.
+
+*  `services/spam_svc.py`: Persistence for abuse flags and spam verdicts.
+
+*  `services/ml_helpers.py`: Utilities for working with ML Engine backend.
+
+*  `search/*`: frontend and backend code for sharded issue search and
+   result set caching.
+
+
+Framework layer
+
+*  `framework/sql.py`: SQL DB table managers and safe SQL statement
+   generation.
+
+*  `framework/servlet.py` and `jsonfeed.py`:  Base classes for servlets.
+
+*  `framework/warmup.py`: Trivial servlet needed for GAE warmup feature.
+
+*  `framework/permissions.py`: Permissionset objects and permission
+   checking functions.
+
+*  `framework/authdata.py`, `monorailrequest.py`, and
+   `monorailcontext.py`: objects that represent information about the
+   incoming request.
+
+*  `framework/xsrf.py`, `banned.py`: Anti-abuse utilities.
+
+*  `testing/*.py`: Utilities for python unit tests.
+
+Asset layer
+
+*  `settings.py`: Server instance configuration.
+
+*  `framework/urls.py`: Constants for web UI URLs.
+
+*  `framework/exceptions.py`: python exceptions used throughout the code.
+
+*  `framework/framework_constants.py`: Implementation-oriented constants.
+
+*  `proto/*.proto`: ProtoRPC definitions for internal representation of
+   business objects.
diff --git a/doc/design/testing-strategy.md b/doc/design/testing-strategy.md
new file mode 100644
index 0000000..8865e06
--- /dev/null
+++ b/doc/design/testing-strategy.md
@@ -0,0 +1,114 @@
+# Monorail Testing Strategy
+
+## Problem
+
+Monorail (bugs.chromium.org) is a complex issue tracking tool with a
+large number of features that needs to work very reliably because it
+is used by everyone working on Chrome, which is a critical Google
+product.  At the same time, testing needs to be done at low cost
+because the Monorail team is also tasked with developing new
+functionality.
+
+## Strategy
+
+Basically, the end goal is a test automation pyramid with the base
+being unit tests with the highest test coverage, a middle layer where
+we test the API that the the UI uses, and the top layer is automated
+system tests for a few things to avoid surprises.
+
+![Testing pyramid](https://2.bp.blogspot.com/-YTzv_O4TnkA/VTgexlumP1I/AAAAAAAAAJ8/57-rnwyvP6g/s1600/image02.png)
+
+The reason for that is that unit tests give the best return on
+investment and the best support for ensuring that individual changes
+work correctly as those changes are being made.  End-to-end testing is more
+like a necessary evil: it is not very cost-effective because these
+tests are hard to write, easily broken by unrelated changes, and often
+have race conditions that make them flakey if we are not very careful.
+The API tests at the middle layer are a kind of integration testing
+that proves that larger parts of the code work together in a
+production environment, but they should still be easy to maintain.
+
+Past experience on code.google.com supports that strategy. Automated
+system tests were done through the UI and were notoriously slow and
+flakey. IIRC, we ran them 4 times and if any one run passed, it was
+considered a pass, and it was still flakey.  They frequently failed
+mysteriously.  They were so flakey that it was hard to know when a new
+real error had been introduced rather than flakiness getting worse. We
+repeatedly rewrote tests to eliminate flakiness and got them to pass,
+but that is a slow process because you need to run the tests many
+times to be sure that it is really passing, and there was always the
+doubt that a test could be falsely passing.  Many manual tests were
+never automated because of a backlog of problems with the existing
+automated tests.
+
+
+See also:
+[Google Test Blog posting about end-to-end tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html).
+And, [Google internal unit test how-to](go/unit-tests).
+
+
+## Test coverage goals
+
+| Type        | Lang/Tech                | Coverage | Flakiness |
+|-------------|--------------------------|----------|-----------|
+| Unit        | Python pRPC API code     | 100%     | None      |
+| Unit        | Other python code        | >85%     | None      |
+| Unit        | Javascript functions     | >90%     | None      |
+| Unit        | LitElement JS            | >90%     | None      |
+| Integration | Probers                  | 10%?     | None      |
+| Integration | pRPC client in python?   | 25%?     | None      |
+| UI          | Web testing tool?        | 10%?     | As little as possible |
+| UI          | English in a spreadsheet | 100%     | N/A       |
+
+
+## Plan of action
+
+Building all the needed tests are a lot of work, and we have limited
+resources and a lot of other demands.  So, we need to choose wisely.
+Also, we need to keep delivering quality releases at each step, so we
+need to work incrementally.  Since we won't "finish", we need a
+mindset of constant test improvement, as tracked in coverage and
+flakiness metrics.
+
+Steps:
+
+1.  Strictly maintain unit test coverage for pRPC, work_env, and other
+    key python files at 100%.
+
+1.  Improve python unit test code style and update library usage,
+    e.g., mox to mock.
+
+1.  Improve unit test coverage for other python code to be > 85%.
+
+1.  Maintain JS unit tests as part of UI refresh.  Run with `karma`.
+
+1.  Design and implement probers for a few key features.
+
+1.  Design and implement API unit tests and system tests.
+
+1.  Research options for web UI testing. Select one.
+
+1.  Implement automated UI tests for a few key features.
+
+1.  Maintain go/monorail-system-test spreadsheet as we add or modify
+    functionality.
+
+
+## Related topics
+
+Accessibility testing: So far we have just used the audit tools in
+Chrome. We could find additional audit tools and/or request
+consultation with some of our users who are accessibility experts.  We
+have, and will continue to, give high priority to accessibility
+problems that are reported to us.
+
+API performance testing: Some API calls are part of our monitoring.
+However, we currently do not actively manage small changes in latency.
+We should look at performance changes for each release and over longer
+time-spans.
+
+UI performance testing: Again, we have monitoring, but we have not
+been looking critically at small changes in UI performance across
+releases.
+
+Security testing:  We are enrolled in a fuzzing service.
diff --git a/doc/example.py b/doc/example.py
new file mode 100644
index 0000000..beec6c2
--- /dev/null
+++ b/doc/example.py
@@ -0,0 +1,43 @@
+# This example uses Google APIs Client for Python, you can download it here:
+# https://developers.google.com/api-client-library/python/
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import apiclient
+
+import httplib2
+
+from oauth2client.file import Storage
+
+
+DISCOVERY_URL = (
+    'https://monorail-staging.appspot.com/_ah/api/discovery/v1/apis/'
+    '{api}/{apiVersion}/rest')
+
+
+# Get credentials to authorize http object
+storage = Storage('Your-local-credential-file')
+credentials = storage.get()
+http = credentials.authorize(httplib2.Http())
+
+# Create monorail client using Google APIs Client for Python
+monorail = apiclient.discovery.build(
+    'monorail', 'v1',
+    discoveryServiceUrl=DISCOVERY_URL,
+    http=http)
+
+# Create a chromium project issue
+insert_response = monorail.issues().insert(projectId='chromium', body={
+    'summary': 'Strange grinding sound',
+    'status': 'Untriaged',
+    'cc': [{'name':'user1@example.org'}, {'name':'user2@example.org'}]
+}).execute()
+
+new_issue_id = insert_response['id']
+
+# Get all issues of chromium
+list_response = monorail.issues().list(projectId='chromium').execute()
+issues = list_response['items']
+total_issues = list_response['totalResults']
diff --git a/doc/instance.md b/doc/instance.md
new file mode 100644
index 0000000..23a7df8
--- /dev/null
+++ b/doc/instance.md
@@ -0,0 +1,37 @@
+# Creating a new Monorail instance
+
+1.  Create new GAE apps for production and staging.
+1.  Configure GCP billing.
+1.  Fork settings.py and configure every part of it, especially trusted domains
+    and "all" email settings.
+    1.  Change num_logical_shards to the number of read replicas you want.
+    1.  You might want to also update `*/*_constants.py` files.
+1.  Create new primary DB and an appropriate number of read replicas for prod
+    and staging.
+    1.  Set up IP address and configure admin password and allowed IP addr.
+        [Instructions](https://cloud.google.com/sql/docs/mysql-client#configure-instance-mysql).
+    1.  Set up backups on primary DB. The first backup must be created before
+        you can configure replicas.
+1.  Set up log saving to bigquery or something.
+1.  Set up monitoring and alerts.
+1.  Set up attachment storage in GCS.
+1.  Set up spam data and train models.
+1.  Fork and customize some of HTML in templates/framework/header.ezt,
+    footer.ezt, and some CSS to give the instance a visually different
+    appearance.
+1.  Get From-address allowlisted so that the "View issue" link in Gmail/Inbox
+    works.
+1.  Set up a custom domain with SSL and get that configured into GAE. Make sure
+    to have some kind of reminder system set up so that you know before cert
+    expire.
+1.  Configure the API. Details? Allowed clients are now configured through
+    luci-config, so that is a whole other thing to set up. (Or, maybe decide not
+    to offer any API access.)
+1.  Gain permission to sync GGG user groups. Set up borgcron job to sync user
+    groups. Configure that job to hit the API for your instance. (Or, maybe
+    decide not to sync any user groups.)
+1.  Monorail does not not access any internal APIs, so no allowlisting is
+    required.
+1.  For projects on code.google.com, coordinate with that team to set flags to
+    do per-issue redirects from old project to new site. As each project is
+    imported, set it's moved-to field.
diff --git a/doc/md_images/cancel-execution.png b/doc/md_images/cancel-execution.png
new file mode 100644
index 0000000..b9d1514
--- /dev/null
+++ b/doc/md_images/cancel-execution.png
Binary files differ
diff --git a/doc/md_images/continue-options.png b/doc/md_images/continue-options.png
new file mode 100644
index 0000000..27e8b1d
--- /dev/null
+++ b/doc/md_images/continue-options.png
Binary files differ
diff --git a/doc/md_images/manual-judgement-stage.png b/doc/md_images/manual-judgement-stage.png
new file mode 100644
index 0000000..9d8e1de
--- /dev/null
+++ b/doc/md_images/manual-judgement-stage.png
Binary files differ
diff --git a/doc/md_images/nav-to-build-log.png b/doc/md_images/nav-to-build-log.png
new file mode 100644
index 0000000..da4a0bb
--- /dev/null
+++ b/doc/md_images/nav-to-build-log.png
Binary files differ
diff --git a/doc/md_images/pantheon-migrate-traffic.png b/doc/md_images/pantheon-migrate-traffic.png
new file mode 100644
index 0000000..fae47ff
--- /dev/null
+++ b/doc/md_images/pantheon-migrate-traffic.png
Binary files differ
diff --git a/doc/md_images/pantheon-services-dropdown.png b/doc/md_images/pantheon-services-dropdown.png
new file mode 100644
index 0000000..d7fb434
--- /dev/null
+++ b/doc/md_images/pantheon-services-dropdown.png
Binary files differ
diff --git a/doc/md_images/rollback-options.png b/doc/md_images/rollback-options.png
new file mode 100644
index 0000000..d43ed6e
--- /dev/null
+++ b/doc/md_images/rollback-options.png
Binary files differ
diff --git a/doc/md_images/start-deploy-monorail.png b/doc/md_images/start-deploy-monorail.png
new file mode 100644
index 0000000..361fbfb
--- /dev/null
+++ b/doc/md_images/start-deploy-monorail.png
Binary files differ
diff --git a/doc/md_images/start-rollback.png b/doc/md_images/start-rollback.png
new file mode 100644
index 0000000..828280e
--- /dev/null
+++ b/doc/md_images/start-rollback.png
Binary files differ
diff --git a/doc/release-notes.md b/doc/release-notes.md
new file mode 100644
index 0000000..5b2c77a
--- /dev/null
+++ b/doc/release-notes.md
@@ -0,0 +1,18 @@
+# Monorail Release Notes
+
+### Mar 12th, 2018
+
+* Added HTML headers to the issue detail page to make it easier for
+  screen readers to navigate the page.
+
+### Feb 27th, 2018
+
+* Moved spam flag on issue detail page down below issue metadata to
+  prevent confusion with the star.
+
+### Jan 8th, 2018
+
+* New URL-type custom fields are available for project owners to
+  create under the 'Development Process' tab.
+* Bolded text in issue descriptions remain bolded after description
+  edits.
diff --git a/doc/terms.md b/doc/terms.md
new file mode 100644
index 0000000..a11aa38
--- /dev/null
+++ b/doc/terms.md
@@ -0,0 +1,39 @@
+# Terms of Service for Monorail (https://bugs.chromium.org)
+
+Please refer to [Google’s Terms of Service](https://policies.google.com/terms).
+
+The terms below are specific to Monorail and are in addition to or superseding
+Google’s Terms of Service.
+
+## Your Account in Monorail
+
+A Monorail User account is automatically created when you visit
+https://bugs.chromium.org while logged in for the first time. The account is
+associated with your email.
+
+## Data Retention
+
+As a bug tracker for Chromium and Chromium related projects, the issues and
+discussions that occur in Monorail are insightful and vital to understanding the
+codebase and history of the projects we track. Therefore, Monorail retains all
+data generated on the site indefinitely. User accounts and data associated with
+the account may be deleted upon request or as a result of the associated email
+being deleted. See [Deleting Your User Account](#Deleting-Your-User-Account) for
+more information.
+
+## Deleting Your User Account
+
+There are two ways a User Account can be deleted in Monorail.
+
+1.  If the Google account associated with a Monorail User Account is deleted,
+    Monorail will delete the User account within 7 days. For instructions
+    deleting your Google account visit the
+    [Google Account Help Center](https://support.google.com/accounts/answer/32046?hl=en).
+
+1.  Users can request the deletion of their owner Monorail User Account by
+    [filing a bug](https://bugs.chromium.org/p/monorail/issues/entry) with the
+    Monorail Team while logged in with the account that they want deleted. We
+    will not accept requests to delete accounts that differ from that of the
+    user making the request. Please note that if a user visits Monorail again
+    while logged in, after their account has been deleted, Monorail will
+    automatically create another account.
diff --git a/doc/triage.md b/doc/triage.md
new file mode 100644
index 0000000..81c8114
--- /dev/null
+++ b/doc/triage.md
@@ -0,0 +1,60 @@
+# Monorail Triage Guide (go/monorail-triage)
+
+Monorail is a tool that is actively used and maintained by
+the Chromium community.  It is important that we look at
+issues reported by our users and take appropriate actions.
+
+For the full list of trooper responsibilities, see
+[go/chops-workflow-trooper](http://go/chops-workflow-trooper).
+
+## Triage Process
+
+Look at each issue in the
+[Monorail untriaged
+queue](https://bugs.chromium.org/p/monorail/issues/list?q=&can=41013401) and
+[Sheriffbot untriaged queue](http://crbug.com/?q=component%3DTools%3EStability%3ESheriffbot%20status%3Auntriaged&can=2)
+and do the following:
+
+* If the issue is unintelligible or empty, flag the issue as spam.
+  * Check the user stats at bugs.chromium.org/u/{user\_email}/updates, and if none of their
+  activities on the site are serious or valid, ban them as spammer.
+* Move issues to the correct project (eg. "chromium") if misfiled.
+* Apply the correct `type:` label.
+* If the bug is caused by someone else's changes or if the bug is part of the feature
+  clearly owned by one person, assign the issue to that person and set
+  `status:Assigned`.
+* Validate the issue and make it clear and actionable
+  * If not actionable or reproducible, mark issue as `status:WontFix`
+  * If the issue requires more information from reporter, add the label
+    `needs:Feedback` and ask the reporter for more information.
+* Update issue `Pri-` label
+  * If the issue is a Monorail API Request, set `Pri-1`
+  * If the issue is a security or privacy issue, set `Pri-1`
+  * Refer to [Priorities](#Priorities) for all other cases.
+* If the issue is `Pri-0` or `Pri-1`
+  * If `Pri-0`: assign self as `owner`, mark `status:Started`, notify team leads, follow
+    the IRM process as Incident Commander.
+  * If `Pri-1`: assign self as owner, mark `status:Started` and work to resolve the
+    issue. Find another owner and make a formal handoff if you are not able to
+    address.
+* If an issue has been `needs:Feedback` for more than 7 days without response, mark
+  as `status:WontFix` with an explanatory comment.
+* Otherwise, mark issue as `status:Available`
+
+## Priorities
+
+* `Pri-0`: Critical issue causing failures in production. Major functionality broken
+  that renders a feature unusable for all customers.
+* `Pri-1`: Urgent; the issue is blocking a user from getting their job done. Defect
+  causing functional regression in production. Production issue impacting other
+  customers. Any type of security or privacy problem. Finally, any workflow
+  administrative tasks that have been officially asked to the trooper to handle,
+  that includes very explicitly: Monorail API access request, Sheriffbot testing &
+  deployment, and Hotlist removal.
+* `Pri-2`: Important; tied to OKRs or near term upcoming release. Bug that should be
+  addressed in one of the next few releases.
+* `Pri-3`: We feel your pain: the team would like to fix this, but lacks the resources
+  to do this soon. Desirable feature or enhancement not on the near-term roadmap.
+  Defects that are not regressions, have workarounds, and affect few users.
+* `Pri-4`: Ponies and icebox. Unfortunate: it's a legitimate issue, but the team never
+  plans to fix this.
diff --git a/doc/userguide/README.md b/doc/userguide/README.md
new file mode 100644
index 0000000..7651297
--- /dev/null
+++ b/doc/userguide/README.md
@@ -0,0 +1,78 @@
+# Monorail Issue Tracker User Guide
+
+
+## What is Monorail?
+
+Monorail is the Issue Tracker used by the Chromium project and other related
+projects. It is hosted at [bugs.chromium.org](https://bugs.chromium.org).
+
+
+## Why we use Monorail
+
+The projects that use Monorail have carefully considered other issue
+tracking tools and selected Monorail because of several key features:
+
+* Monorail is extremely flexible, allowing for a range of development
+  processes to be used in different projects or within the same project,
+  and for process details to be gracefully phased in and phased out
+  over time.  For example, labels and custom fields are treated very
+  much like built-in fields.
+
+* Monorail is inclusive in that it allows average users to view details
+  of how a project's development process is configured so that contributors
+  can understand how their contributions fit in.  And, Monorail's UI
+  emphasizes usability and accessibility.
+
+* Monorail has a long track record of hosting a mix of public-facing and
+  private issues.  This has required per-issue access controls and user
+  privacy features.
+
+* Monorail helps users focus on individual issues and also work with sets
+  of issues through powerful issue list, grid, and graph views.
+
+* Monorail is built and maintained by the Chrome team, allowing for
+  customization to our processes.  For example, Feature Launch Tracking.
+
+
+## User guide table of contents
+
+This user guide is organized into the following chapters:
+
+* [Quick start](quick-start.md)
+* [Concepts](concepts.md)
+* [Working with individual issues](working-with-issues.md)
+* [Issue lists, grids, and charts](list-views.md)
+* [Power user features](power-users.md)
+* [Email notifications and replies](email.md)
+<!-- Feature launch tracking and approvals -->
+* [Other project pages for users](project-pages.md)
+* [User profiles and hotlists](profiles-and-hotlists.md)
+* [Project owner's guide](project-owners.md)
+* [Site admin's guide](site-admins.md)
+
+
+## How to ask for help and report problems with Monorail itself
+
+<!-- This is purposely written in a couple different places to make it
+     easier for users to find. -->
+
+If you wish to file a bug against Monorail itself, please do so in our
+[self-hosting tracker](https://bugs.chromium.org/p/monorail/issues/entry).
+We also discuss development of Monorail at `infra-dev@chromium.org`.
+
+You can report spam issues via the "..." menu near the issue summary.
+You can report spam comments via the "..." menu on that comment.  Any
+project owner can ban a spammer from the site.
+
+
+## A history of Monorail
+
+The design of Monorail was insipred by our experience with Bugzilla and
+other issue trackers that tended toward hard-coding a development
+process into the tool.  This work was previously part of Google's
+Project Hosting service on code.google.com from 2006 until 2016.
+Several Chromium-related projects were heavy users of the issue
+tracker part of code.google.com, and they opted to continue
+development work on it.  Monorail launched as an open source project
+in 2016.  Bugs.chromium.org currently hosts over 25 related projects,
+with over one million issues in the /p/chromium project alone.
diff --git a/doc/userguide/concepts.md b/doc/userguide/concepts.md
new file mode 100644
index 0000000..d9ca829
--- /dev/null
+++ b/doc/userguide/concepts.md
@@ -0,0 +1,376 @@
+# Monorail Concepts
+
+[TOC]
+
+## User accounts
+
+*   Users may visit the Monorail server without signing in, and may view public
+    issues anonymously.
+
+*   Signing in to Monorail requires a Google account. People can create a GMail
+    account, or create a Google account using an existing email address.
+
+*   User accounts are identified by email address.
+
+*   When you post, your email address is shared with project members.
+
+*   You can access your user profile via the account menu.
+
+*   Your user pages also include a list of your recent posts, your saved
+    queries, and your hotlists.
+
+*   The settings page allows you to set user preferences, including a vacation
+    message.
+
+*   Monorail allows account linking between certain types of accounts.
+    (Currently only allowed between @google.com and @chromium.org accounts). To
+    link your accounts:
+
+    1.  Log in to the account that you want to make a child account.
+    1.  Navigate to your account 'Profile' page via the dropdown at the
+        top-right of every page.
+    1.  You should see "Link this account to:". Select the parent account and
+        click "Link"
+    1.  Switch to the account you've chosen to be the parent account.
+    1.  Navigate to the 'Profile' page.
+    1.  You should see a pending linked account invite from your first account.
+        Click 'Accept'.
+
+*   If you need to completely delete your account, there is a button for that on
+    your profile page.
+
+    *   Each issue or comment that you create becomes part of the project, so
+        deleting your account simply removes your email address from those
+        postings. If you need to delete individual posts, you should do that
+        before deleting your account.
+
+*   If Monorail sends an email to a user and that email bounces, the account is
+    marked as bouncing. A user can sign in and clear the bouncing flag via their
+    profile page.
+
+*   The bouncing state, time of last visit, and any vacation message are all
+    used to produce a user availability message that may be shown when that user
+    is an issue owner or CC’d.
+
+*   Project owners and site admins may ban user accounts that are used to post
+    spam or other content that does not align with Monorail’s mission.
+
+## Projects and roles
+
+*   Each project contains issues, grants roles to project members, and
+    configures how issues are tracked in that project.
+
+*   Projects can be public or members-only. Only project members may access the
+    contents of a members-only project, however the name of a members-only
+    project may occur in comments and other places throughout the site.
+
+*   The project owners are responsible for configuring the project to fit their
+    development process (described below).
+
+*   Project members have permissions to edit issues, and they are listed in the
+    autocomplete menus for owner and CCs.
+
+*   While most activity on a Monorail server occurs within a given project, it
+    is also possible to work across projects. For example, a hotlist can include
+    issues from multiple projects.
+
+*   Some projects that we host have a branded domain name. Visiting one of these
+    projects will redirect the user to that domain name.
+
+*   When an old project is no longer needed, it can be archived or marked as
+    moved to a different URL.
+
+## Issues, comments, and attachments
+
+Issues:
+
+*   Each issue is given a unique numeric ID number when the issue is created.
+    Issue IDs count up so that they serve as a project odometer and a quick way
+    for members to judge the age of an issue.
+
+*   Each issue has a set of fields including summary, reporter, owner, CCs,
+    components, labels, and custom fields.
+
+*   Issues may be blocked on other issues. The relationship is two-way, meaning
+    that if issue A is blocked on issue B, then issue B is blocking issue A.
+
+*   Each issue has several timestamps, including the time when it was opened,
+    when it was most recently closed, when the status was most recently set,
+    when the components were modified, and when the owner was most recently
+    changed.
+
+Comments:
+
+*   Each comment consists of the comment text, the email address of the user
+    which posted the comment, and the time at which the comment was posted.
+
+*   Each comment has a unique anchor URL that can be bookmarked and shared.
+
+*   Each comment has a sequence number within the issue. E.g., comment 1 is the
+    first comment on the issue after it was reported. Older comments may be
+    initially collapsed in the UI to help focus attention on the most recent
+    comments.
+
+*   Comment text is unicode and may include a wide range of characters including
+    emoji.
+
+*   Whitespace in comments is stored in our database but extra whitespace is
+    usually not visible in the UI unless the user has clicked the `Code` button
+    to use a fixed-width code-friendly font.
+
+*   All comments on an issue have the same access controls as the issue.
+    Monorail does not currently support private comments. If you need to make a
+    private comment to another issue participant, you should do it via email or
+    chat.
+
+*   Each comment can list some amendments to the issue. E.g., if the issue owner
+    was changed when the comment was posted, then the new owner is shown.
+
+*   Each comment is limited to 50 KB. If you wish to share log files or other
+    long documents, they should be uploaded as attachments or shared via Google
+    Drive or another tool.
+
+*   Comments can be marked as spam or marked deleted. Even these comments are
+    still part of the project and may be viewed by project members, if needed.
+
+Attachments:
+
+*   Each comment can contain some attachments, such as logs, text files, images,
+    or videos.
+
+*   Monorail supports up to 10 MB worth of attachments on each issue. Larger
+    attachments should be shared via Google Drive or some other way.
+
+*   Monorail allows direct viewing of images, videos, and certain text files.
+    Other attachment types can only be downloaded.
+
+*   Attachment URLs are not all shareable. If you need to refer to an
+    attachment, it is usually best to link to the comment that contains it.
+
+## Issue fields and labels
+
+*   Issues have several built-in fields including summary, reporter, owner, CCs,
+    status, components, and various timestamps. However, many fields that are
+    built into other issue tracking tools are configured as labels or custom
+    fields in Monorail, for example, issue priority.
+
+*   Issue labels are short strings that mean something to project members. They
+    are described more below.
+
+*   Project owners can define custom fields of several different types,
+    including enums, strings, dates, and users.
+
+*   Project owners and field admins can decide on who is allowed to edit a
+    custom field value. If an user is not allowed to edit a field, the input
+    to change its value will be greyed-out or it will not be rendered.
+
+*   There are three main types of labels:
+
+    *   OneWord labels contain no dashes and are treated similarly to hashtags
+        and tags found in other tools.
+    *   Key-Value labels have one or more dashes and are treated almost like
+        enumerated-type fields.
+    *   Restriction labels start with "Restrict-" and have the effect of
+        limiting access to the issue. These are described more below.
+
+*   A list of well-known labels can be configured by project owners. Each
+    well-known label can have a documentation string and it will sort by its
+    rank in the list.
+
+*   Well-known labels are offered in the autocomplete menus. However, users are
+    still free to type out other label strings that make sense to their team.
+
+## Issue approvals and gates
+
+Issues in Monorail can be used to track complex and multi-phased review
+processes for new features or projects using approvals and gates.
+
+Project owners can define project-wide approvals that represent the
+review process of different stakeholders and review teams. For each process,
+project owners can create issue templates that include the set of approvals
+that should be part of the process.
+E.g. A template representing a Launch process that requires approval from
+UX, Security, and A11y teams would include UX, Security, and A11y approvals.
+
+Project owners can also create gates within a template and group approvals
+under those gates to represent the review process phases.
+E.g. A process may have "Proposal", "Design", and "Launch"
+phases where some set of review teams need to review during the
+"Proposal" phase before the feature owner can move on to the "Design"
+phase and request reviews from another set of teams.
+
+When a team wants to go through a review process, they can use the template
+to create an issue that inherits the approvals and gates structure.
+
+Issue gates and approvals cannot be changed after the issue has been created.
+Approvals show up in their own section of the issue details page, separate
+from the issue's fields and comments sections.
+
+*  Approvals can be grouped under ordered gates or they can be gate-less.
+*  Approvals have their own comments separate from issue comments.
+*  Approval statuses are used to indicate the status of the review:
+
+   *  `NeedsReview`: the review has not started yet, but is required.
+   *  `NA`: a review is not required.
+   *  `ReviewRequested`: the issue owner and team have answered any survey
+      and follow-up questions and are ready for the approvers to begin
+      or continue review.
+   *  `ReviewStarted`: approvers have started review.
+   *  `NeedInfo`: approvers have more questions before they can proceed.
+   *  `Approved`: approvers have reviewed and approved the plan.
+   *  `NotApproved`: approvers have reviewed and do not approve of the plan.
+*  Only approvers of an approval are allowed to set statuses to
+   `Approved`, `Not Approved`, and `NA` and can remove and add other
+   users as approvers.
+*  Approval surveys hold context and questions that the approvers want
+   answered before a team requests review.
+*  Approvals may have custom fields associated with them. Those custom
+   fields will show up with their approvals, separate from the other
+   fields of the issue. These fields can be restricted and have field
+   editors assigned to them. This allows project owners and admins to use
+   them while having control over who is allowed to edit the value of those
+   fields, which is regularly a desired feature in this context.
+*  Issue gates also hold gate fields configured by project owners. These
+   gate fields show up right next to the gate names, separate from other
+   issue fields. If an issue does not have any gates, gate fields do not
+   show up.
+
+## Labels for flexibility and process evolution
+
+Monorail normally treats key-value labels and custom fields and labels in the
+same way that built-in fields are treated. For example:
+
+*   When displayed in the UI, they are shown individually as equals of built-in
+    fields, not nested under a subheading.
+
+*   They can be used as columns in the list view, and also in grid and chart
+    views.
+
+*   Users can search for them using the same syntax as built-in fields.
+
+Monorail tracks issues over multi-year periods, so we need to gracefully handle
+process changes that happen from time to time. In particular, Monorail allows
+for incremental formalization of enum-like values. For example:
+
+*   A team may start labeling issues simply as a way to identify a set of
+    related issues. E.g., `LCDDisplay` and `OLEDDisplay`.
+
+*   The team might decide to switch to Key-Value labels to make it easier to
+    query and read in the list views. E.g., `Display-LCD` and `Display-OLED`.
+
+*   If more people start using those labels, the project owners might make them
+    well-known labels and add documentation strings to clarify the meaning of
+    each. However, oddball labels like `Display-Paper` could still be used. This
+    configuration might last for years.
+
+*   If these labels used so much that it seems worth adding a row to the editing
+    form, then the project owners can define an enum-type custom field named
+    `Display` with the well-known label suffixes as possible values. This would
+    discourage oddball values, but they could still exist on existing issues.
+
+*   At a later date, the project owners might review the set of fields, and
+    decide to demote some of them back to well-known labels.
+
+*   If the process changes to the point that it is no longer useful to organize
+    issues by those labels, they can be removed from the well-known list, but
+    still exist on old issues.
+
+## Permissions
+
+*   In Monorail, a permission is represented by a short string, such as `View`
+    or `EditIssue`.
+
+*   Project owners grant roles to users in a project. Each role includes a list
+    of permission strings.
+
+*   The possible roles are: Anonymous visitor, signed-in non-member,
+    contributor, committer, and owner.
+
+*   Project owners may also grant additional permissions to a project member.
+    For example, a user might be a contributor, plus `EditIssue`.
+
+*   Project owners and field admins can restrict custom fields. That will allow
+    them to specify field editors: the only users allowed to edit custom field
+    values in issues and templates.
+
+*   When a user makes a request to the Monorail server, the server checks that
+    they can access the project and that they have the needed permission for
+    that action. E.g., viewing an issue requires the `View` permission.
+
+*   If an issue has a restriction label of the form
+    `Restrict-Action-OtherPermission` then the user may only perform `Action` if
+    they could normally do it, and if they also have permission
+    `OtherPermission`. For example, `Restrict-View-EditIssue` means that the
+    only users who can view the issue are the ones who could also edit it.
+
+*   Since both permissions and restriction labels are just strings, they can be
+    customized with words that make sense to the project owners. For example, if
+    only a subset of project members are supposed to deal with security issues,
+    they could be granted a `SecurityTeam` permission and those issues labeled
+    with `Restrict-View-SecurityTeam`. The most common example is
+    `Restrict-View-Google`.
+
+*   Restriction labels can be added in any of the ways that other labels can be
+    added, including adding them directly to an individual issue, bulk edits,
+    filter rules, or including them in an issue template.
+
+*   Regardless of restriction labels, the issue reporter, owner, CC’d users, and
+    users named in certain user-type custom fields always have permission to
+    view the issue. And, issue owners always have permission to edit.
+
+*   Project owners and site administrators are not subject to restriction
+    labels. They can also always edit the value of restricted fields.
+
+*   Admins of a field can always edit the field's value(s) in an issue or
+    template as long as they have permission to edit the issue or template.
+
+## Project configuration
+
+Projects are configured to define the development process used to track issues,
+including:
+
+*   The project description, optional link to a project home page, and optional
+    logo
+
+*   A list of open and closed issue statuses. Each status can have a
+    documentation string.
+
+*   A list of well-known labels and their documentation strings.
+
+*   A list of custom fields, each with a documentation string and validation
+    options.
+
+*   A list of issue templates to use when creating new issues.
+
+*   A list of components, each with a documentation string, auto-CCs, and labels
+    to add.
+
+*   A list of filter rules to automatically add some issue fields based on other
+    values.
+
+*   Default list and grid view configurations for project members.
+
+<!-- TODO: These areas of project configuration are covered more in
+     the Project Owner Guide. -->
+
+## Personal hotlists
+
+*   Each user has a list of personal hotlists that can be accessed via the
+    account menu.
+
+*   A hotlist is a ranked list of issues, which can belong to multiple projects.
+
+*   Users can rerank issues in a hotlist by dragging them up or down.
+
+*   Issues can be added to a hotlist via the hotlist page, the issue detail
+    page, or issue list.
+
+*   Each hotlist belongs to one user, but that user can add other users to be
+    editors.
+
+*   Each issue in a hotlist can also have a short note attached.
+
+*   Hotlists themselves can be public or members-only. A user who is allowed to
+    view a hotlist will only see the subset of issues that they are allowed to
+    view as determined by issue permissions. Hotlists do not affect issue
+    permissions.
diff --git a/doc/userguide/email.md b/doc/userguide/email.md
new file mode 100644
index 0000000..cecd961
--- /dev/null
+++ b/doc/userguide/email.md
@@ -0,0 +1,239 @@
+# Email Notifications and Replies
+
+[TOC]
+
+## How does Monorail send email messages?
+
+Monorail sends individual email messages to each user who should be
+notified of an issue change.  For example, if users A, B and C are all
+CC’d on an issue, a new comment on that issue will trigger three
+separate email messages: one to each of the email addresses for users
+A, B, and C.  Sending individual messages, rather than a single
+message with several addresses listed, allows Monorail to customize
+the content of each message for each user, avoid sending duplicate
+emails in many cases, and better manage email replies.
+
+
+## Who is notified when an issue changes?
+
+When a new issue is created or a comment is posted to an issue,
+Monorail computes a set of users to notify for specific reasons.
+Those reasons cause the following users to be added to the set:
+
+* The issue owner, plus the previous owner if the owner field was
+  edited
+
+* CC’d users, including any CC’d user added by filter rules or
+  component definitions
+
+* Users who have starred the issue and are currently allowed to view
+  the issue
+
+* Users named in user-type custom fields that are configured to
+  trigger notifications
+
+* Users who have subscriptions with queries that match the new state
+  of the issue
+
+If any of those items are user groups that exist in Monorail, they are
+replaced by the list of members of the user group.  If a given user is
+a member of two user groups in the set, the user themselves will only
+be in the set once.
+
+The issue reporter is not automatically included in the set of users
+to notify.  However, by default, a user stars an issue when they
+report it.  So, the issue reporter is usually also a starrer unless
+they click to unstar the issue.
+
+Monorail removes some users from the set:
+
+* The user who made the change, because they already know about the
+  change.
+
+* Users who opted out of issue change notifications by unchecking some
+  options on their user settings page (see below).
+
+* For any users with linked accounts, if both accounts would be
+  notified, Monorail omits the child account.
+
+* Any banned or bouncing users.
+
+Finally, Monorail adds in these email addresses:
+
+* Any also-notify addresses defined in matching filter rules
+
+* Any notify-all email address configured by the project owners
+
+Monorail then sends email messages to each of those email addresses.
+Usually, those addresses correspond to the inboxes of individual users.
+However, some accounts have email addresses that post to mailing lists
+(not user groups that are sync’d to Monorail).  For those messages,
+the mailing list software takes over to distribute the message to
+members of that mailing list and add it to the mailing list archive.
+
+## Who is notified when an issue's approval changes?
+
+Some issues own approvals which have their own thread of comments and
+fields. Notifications for approval changes and comments behave
+differently from issue notifications.
+
+Below, "feature team" will be used to mean users named in user-type
+custom fields that are configured to trigger notifications AND
+the issue owner.
+CC'd users are not notified for any approval changes.
+
+The following are all possible changes someone could make to an approval
+and the users that would be added to the set of notification recipients
+as a result.
+
+A new approval comment or approval field change  will trigger
+notifications to:
+  *  The feature team
+  *  Approvers of the approval
+
+An approval status change to:
+  *  `ReviewRequested` notifies approvers.
+  *  `NeedInfo`, `NeedsReview`, `ReviewStarted`, `Approved`,
+     `NotApproved`, and `NA` notifies the feature team.
+
+A change of approvers notifies all approvers including the ones being
+added and removed.
+
+Like issues, the user who made the approval changes will not
+receive the resulting email.
+
+## Why did I get an email notification?
+
+If you receive an email notification from Monorail, it is because of
+one of the reasons listed in the section above.  Monorail personalizes
+each message by adding a footer that lists the reason or reasons why
+that message was sent to you.  In Gmail, you may need to click a "..."
+icon near the bottom of the message to see this footer.
+
+
+## Why did I NOT get an email notification?
+
+Please see the reasons listed above for why Monorail removes potential
+email recipients.
+
+If Monorail previously sent an email to your address, and that
+email bounced, Monorail will flag your account as bouncing.  This can
+happen if you are new to a team and a teammate CC’d your email address
+before your email account had been created.  You can clear the
+bouncing flag by clicking a link on your user profile page.
+
+If you use multiple accounts with Monorail, someone may have CC’d
+one of your other accounts.
+
+
+## How do I configure email notifications?
+
+Users currently have two options to configure email notifications on
+their settings page:
+
+* `If I am in the issue's owner or CC fields.`
+
+* `If I starred the issue.`
+
+You can access the settings page via the account menu.
+
+
+## How can I filter these emails?
+
+The best way to filter emails is to look at the reasons listed in the
+footer.  For example, if the email message contains the string "You
+are the owner of the issue", then you might want to mark the message
+as important.
+
+
+## How to star an issue
+
+Anyone can star an issue by clicking the star icon near the issue ID
+on the issue detail page, issue list page, or hotlist page.  Starring
+an issue will subscribe you to email notifications for changes to that
+issue.  The issue reporter stars the issue be default, but they can
+unstar it if they are no longer interested.
+
+
+## How to subscribe to an issue query
+
+1. Sign in and click your email address in the far upper right of the
+   page to access your account menu.
+1. Choose `Saved queries`.
+1. Choose a name that describes what you are interested in.
+1. List the names of the projects where you want the subscription to
+   apply.
+1. Type an issue query for issues in those projects that you are
+   interested in.
+
+If you choose the `Notify immediately` option, then when any issue
+that matches that query is updated, you will get an email
+notification.  If you choose either to be notified or not notified,
+you can use any of your subscriptions in the issue query scope menu.
+
+
+## Why did I get a follow-up email notification?
+
+When an issue has a date-type custom field set, and that field is
+configured to trigger notifications, and the specified date arrives
+while the issue is still open, then Monorail will automatically post a
+comment to the issue as a reminder.  That comment triggers
+notification emails to the issue owner, CC’d users, and users who
+starred the issue.
+
+On the settings page, there is an option to not send follow-up emails
+for issues that you have starred.  However, follow-up emails are
+always sent to the issue owner and CC’d users.
+
+
+## How to reply to a notification email
+
+Project owners may enable processing of replies in their project, but
+it is disabled by default.  If it is enabled in your project, you will
+see that the footer of each email notification invites you to "Reply
+to this email to add a comment" or "Reply to this email to add a
+comment or make updates".  These lines will only appear on messages
+that are sent directly to you, not on messages sent to an also-notify
+or notify-all mailing list address.
+
+When you see one of those footer lines, you can reply to the email
+from your email client.  You should reply using the same email address
+that the notification was sent to, which normally happens
+automatically unless you forward your email from one account to
+another.  And, you must keep the same email subject line.  The text of
+your reply will become the content of a new comment on the issue.  If
+your reply cannot be processed, you will get an email notification
+with an error message explaining what went wrong.
+
+If the notification sent to you invited you to "make updates", then
+you can include some lines at the top of your email message to update
+the state of the issue.  Any text after the first line that does not
+parse as a command will be treated as the comment content.  Quoted
+lines and signature lines are automatically stripped off.  To help
+make sure nothing important is lost, project members may use the
+comment `...` menu to view the original email message with nothing
+stripped off.
+
+For example:
+
+```
+Owner: user@example.com
+Status: Started
+Pri: 2
+
+I looked through the logs and found a possible root cause.  Lowering
+priority because there is a clear work-around.
+
+Cheers
+```
+
+That reply will update the issue owner, status, and the issue
+priority.  And, it will use the rest of the text as the comment body,
+except for "Cheers" which will be stripped.
+
+
+
+## Can I create a new issue via email?
+
+Monorail does not currently allow for new issues to be created via
+email.  Please use the issue entry page to create a new issue.
diff --git a/doc/userguide/list-views.md b/doc/userguide/list-views.md
new file mode 100644
index 0000000..a553847
--- /dev/null
+++ b/doc/userguide/list-views.md
@@ -0,0 +1,202 @@
+# Issue lists, grids, and charts
+
+[TOC]
+
+## How do teams use Monorail list, grid, and chart views?
+
+There are many uses for issue aggregate views, including:
+
+*   Finding a particular issue or a set of related issues
+*   Seeing issues assigned to you, or checking the status of issues you reported
+*   Seeing incoming issues that need to be triaged
+*   Understanding the set of open issues that your team is responsible for
+    resolving
+*   Understanding trends, hot spots, or rates of progress
+
+Monorail has flexible list views so that users can accomplish a wide range of
+common tasks without needing external reporting tools. But, for more challenging
+understanding tasks, we also offer limited integration with some reporting tools
+to Googlers.
+
+## How to search for issues
+
+1.  Sign in, if you want to work with restricted issues or members-only
+    projects.
+1.  Navigate into the appropriate project by using the project list on the site
+    home page or the project menu in the upper-left of each page.
+1.  Type search terms into the search box at the top of each project page. An
+    autocomplete menu will help you enter search terms. The triangle menu at the
+    end of the search box has an option for a search tips page that gives
+    example queries.
+1.  Normally, Monorail searches open issues only. You can change the search
+    scope by using the menu just to the left of the search box.
+1.  Press enter or click the search icon to do the search
+
+You can also jump directly to any issue by searching for the issue’s ID number.
+
+## How to find issues that you need to work on
+
+1.  Sign in and navigate to the appropriate project.
+1.  Search for `owner:me` to see issues assigned to you. You might also try
+    `cc:me` or `reporter:me`. If you are working on a specific component, try
+    `component:` followed by the component’s path.
+
+If you are a project member, the project owner may have already configured a
+default query that will show your issues or a list of high-priority issues that
+all team members should focus on.
+
+Issue search results pages can be bookmarked, so it is common for teams to
+define a query that team members should use to triage incoming issues, and then
+share that as a link or make a short-link for it.
+
+## How to refine a search query
+
+You can refine a query by editing the query terms in the search box at the top
+of the page. You can add more search terms to the end of the query or adjust
+existing terms. The autocomplete menu will appear if you position the text
+cursor in the existing query term. One common way to adjust search terms is to
+add more values to an existing term by using commas, e.g., `Pri:0,1`.
+
+A quick way to narrow down a displayed list of issues is to filter on one of the
+shown columns. Click on the column heading, open the `Show only` submenu, and
+select a value that you would like to see. A search term that matches only that
+value will be added to the query and the list will be updated.
+
+## How to sort an issue list
+
+1.  Do a query to get the right set of issues, and show the column that you want
+    to sort on.
+1.  Click the column heading to open the column menu.
+1.  Choose `Sort up` or `Sort down`.
+
+Labels defined by the project owners will sort according to the order in which
+they are listed on the label admin page, e.g., Priority-High, Priority-Medium,
+Priority-Low. Teams are also free to use labels that make sense to them without
+waiting for the project owner to define them, and those labels will sort
+alphabetically.
+
+To sort on multiple columns, A, B, and C: First sort on column, C, then B, and
+then A. Whenever two issues have the same value for A, the tie will be broken
+using column B, and C if needed.
+
+For multi-valued fields, issues sort based on the best value for that
+field.  E.g., if an issue has CC’s for `a-user@example.com` and
+`h-user@example.com`, it will sort with the A’s when sorting up and
+with the H’s when sorting down.
+
+Project owners can specify a default sort order for issues. This default order
+is used when the user does not specify a sort order and when there are ties.
+
+## How to change columns on an issue list
+
+1.  Click on the `...` menu located after the last column heading in the issue
+    list.
+1.  Select one of the column names to toggle it.
+
+Alternatively, you can hide an existing column by clicking on the column heading
+and choosing `Hide column`.
+
+## How to put an issue list into a spreadsheet
+
+You can copy and paste from an HTML table to a Google Spreadsheet:
+
+1.  Do a search and configure the issue list to show the desired columns.
+1.  Drag over the desired issues to select all cells.
+1.  Copy with control-c or by using the browse `Edit` menu.
+1.  Visit the Google Spreadsheet that you wish to put the information into.
+1.  Paste with control-v or by using the browse `Edit` menu.
+
+For longer sets of results, project members can export CSV files:
+
+1.  Sign in as a project member.
+1.  Do a search and configure the issue list to show the desired columns.
+1.  Click the `CSV` link at the bottom of the issue list.  A file will download.
+1.  Visit Google Drive.
+1.  Choose the menu item to upload a file.
+1.  Upload the CSV file that was download.
+
+The exported CSV will only contain data on that page. If the list is longer
+than one page either shorten the search result or go to the next page and
+click the `CSV` link again.
+You can also increase the pagination size to 1000 to include more results per
+page by adding &num=1000 to the query parameter
+
+## How to group rows
+
+The issue list can group rows and show a group heading. For example, issues
+could be grouped by priority. To group rows:
+
+1.  Do the issue search and, if needed, add a column for the field that you want
+    to use for grouping.
+1.  Click on the column header.
+1.  Choose `Group rows`.
+
+Each section header shows the count of issues in that section, and sections
+expanded or collapsed as you work through the list.
+
+## How to do a bulk edit
+
+1.  Search for issues that are relevant to your task.
+1.  Click the checkboxes for the issues that you want to edit, or use the
+    controls above the issue table to select all issues on the current page.
+1.  Select `Bulk edit` from the controls above the issue list.
+1.  On the bulk edit page, describe the reason for your change, and enter values
+    to add, remove, or set on each issue.
+1.  Submit the form.
+
+## How to view an issue grid
+
+Monorail’s issue grid is similar to a scatter chart in that it can give a
+high-level view of the distribution of issues across rows and columns that you
+select.
+
+1.  On the issue list page, click `Grid` in the upper-left above the issue list
+    table.
+1.  The issue grid shows the same issues that were shown in the issue list, but
+    in a grid format.
+1.  Select the fields to be used for the grid rows and columns.
+
+You can also set the level of detail to show in grid cells: issue tiles, issue
+IDs, or counts. Tiles give the most details about issues, but only a limited
+number of tiles can fit most screens. If issue IDs are shown, hovering the mouse
+over an ID shows the issue summary. Counts can be the best option for a large
+set of issues, and clicking on a count link navigates you to a list of specific
+issues.
+
+Note: Because some issue fields can be multi-valued, it is possible for a given
+issue to appear in multiple places on the issue grid at the same time. The total
+count shown above the grid is the number of issues in the search results, even
+if some of them are displayed multiple times.
+
+## How to view an issue chart
+
+Monorail’s chart view is a simple way to see the number of issues that satisfy a
+query over a period of time, which gives you insights into issue trends.
+Monorail uses historical data to chart any query made up of query terms that we
+support in charts. Unlike many other reporting tools, you do not need to define
+a query ahead of time. To see a chart:
+
+1.  Do a query for the issues that you are interested in. Charts currently
+    support only query terms for cc, component, hotlist, label, owner, reporter,
+    and status.
+1.  Click `Chart` in the upper-left above the issue table.
+1.  Hover your mouse over data points to see exact dates and counts.
+1.  Use the controls below the chart to adjust date range, grouping, and
+    predictions.
+
+## How to see a burndown chart
+
+One of the most important issue trends is the trend toward zero as a team works
+to resolve a set of issues related to some upcoming milestone or launch.
+Monorail can add a prediction line to the chart, which is commonly called a
+burndown chart. Here’s how:
+
+1.  Do a query for the issues that you are interested in. Charts currently
+    support only query terms for cc, component, hotlist, label, owner, reporter,
+    and status.
+1.  Click `Chart` in the upper-left above the issue table.
+1.  Click one of the prediction options in the controls below the chart. A
+    dotted line will be added to the chart showing the current trend and a
+    future prediction.
+1.  Optionally, click the chart legend items at the top of the chart to add
+    prediction error ranges.
diff --git a/doc/userguide/power-users.md b/doc/userguide/power-users.md
new file mode 100644
index 0000000..1241133
--- /dev/null
+++ b/doc/userguide/power-users.md
@@ -0,0 +1,159 @@
+# Power User Features
+
+[TOC]
+
+## Keyboard shortcuts
+
+Monorail offers many keyboard shortcuts. Press `?` to see a list of shortcuts.
+If your text cursor is in a text field, click outside of it or press Escape
+before pressing `?`.
+
+## Bookmarks
+
+Every page in Monorail can be bookmarked. The URL of each page can be shared
+with other users, who will see the contents of that page based on their own
+permissions. Individual issue comments are also anchors that can be bookmarked.
+
+For example, if your team uses a specific query to see the list of incoming
+issues that you are responsible for triaging, copy the URL of that issue list
+and share it. You may want to create a short link (a go/ link) and add a link to
+it in your team documentation.
+
+There are several query string parameters that can be included in the issue list
+page links, including:
+
+*   q= is the issue search query.
+
+*   can= is the issue search scope number. This defaults to 2 for open issues,
+    but it can be set to 1 to search all issues, or other values for other items
+    in the scope menu.
+
+*   sort= is a space-separated list of column names with the most significant
+    column first. Each column name can have a leading minus-sign for descending
+    sorting.
+
+*   colspec= is a space-separated list of columns names to display.
+
+*   groupby= is a space-separated list of columns names to group results by.
+
+*   num= is the number of issue results to be shown on each pagination page,
+    which defaults to 100.
+
+*   mode= defaults to `list` but can be set to `chart` or `grid`.
+
+## Deep links
+
+It is possible to bookmark Monorail pages that display a form, such as the issue
+entry page. Furthermore, you may add URL query string parameters to prefill some
+form fields for the user.
+
+On the issue entry form, these values can be filled in even if the user would
+normally not see those fields offered. You can use such links in user
+documentation that guides the user to report an issue that is labeled in a way
+to make it show up in your team’s triage query.
+
+Those fields are:
+
+* `template=` the name of the issue template to use
+
+* `summary=` initial issue summary
+
+* `description=` initial issue description
+
+* `labels=` a comma-separated list of labels (e.g., Type-Bug, Pri-2)
+
+* `owner=` email address of the initial issue owner
+
+* `status=` the status value for the new issue
+
+* `cc=` comma-separated list of email addresses to CC
+
+* `components=` a comma-separated list of component paths
+
+* `blocking=` a comma-separated list of bugs the new issue will block
+
+* `blockedon=` a comma-separated list of bugs the new issue is blocked by
+
+## Chrome custom search engines
+
+An easy way to skip a step when searching for issues is to define a keyword for
+searching monorail in the Chrome browser. Here’s how:
+
+1.  Pop up a context menu in the Chrome Omnibox URL field.
+1.  Select "Edit Search Engines...".
+1.  Click "Add".
+1.  Define a short keyword and use a Monorail URL with a `%s` placeholder.
+
+For example, you might define "mc" to search /p/chromium issues using URL
+`https://bugs.chromium.org/p/chromium/issues/list?q=%s` Or, you might use a deep
+link rather than a search. For example, you could start reporting a defect
+summary line directly from the Omnibox by defining "md" as
+`https://bugs.chromium.org/p/chromium/issues/entry?template=Defect+report+from+developer&summary=%s`
+
+## Federated issue tracking
+
+Monorail has some support for referencing issues in other issue tracking tools.
+You can always reference any issue in any web-based issue tracking tool by
+pasting the URL of that page into a Monorail issue comment. Googlers may also
+want to use the "b/" shorthand for internal issues. The "b/" syntax can be used
+in issue comments, blocking, blocked-on, and merged-info fields. Googlers may
+see an "i" icon with an option to sign in to preview the summary lines of
+referenced internal issues.
+
+## Crbug.com shortlinks
+
+Separate from Monorail itself, the Chromium developers maintain a service on
+crbug.com that redirects requests to Monorail pages. This allows for shorter
+URLs that are easier to type and fit more easily into source code comments.
+
+The supported URLs include:
+
+*   http://crbug.com/: Goes to the issue list in the /p/chromium project.
+
+*   http://crbug.com/123: Goes to issue 123 in the /p/chromium project.
+
+*   http://crbug.com/123#c4: Goes to issue chromium:123 comment 4.
+
+*   http://crbug.com/new or http://crbug.new: Goes to issue entry form in the
+    /p/chromium project.
+
+*   http://crbug.com/PROJECT/: Goes to the issue list in the specified project.
+
+*   http://crbug.com/PROJECT/123: Goes to issue 123 in the specified project.
+
+*   http://crbug.com/PROJECT/new: Goes to the issue entry form in the specified
+    project.
+
+## Autolinking
+
+When you type an issue description or comment, Monorail will automatically
+convert some parts of the text into HTML links. The following are supported:
+
+*   `issue 123` or `bug 123` link to issue 123 in the current project.
+
+*   `issue project:123` links to issue 123 in the specified project.
+
+*   `b/123` links to issue 123 in Google’s internal issue tracker.
+
+*   `cl/123` links to changelist 123 in Google’s internal version control
+    system.
+
+*   A git hash links to http://crrev.com for the specified commit, unless
+    otherwise specified in the project.
+
+*   `comment 6` or `#c6` link to the 6th comment in the current issue thread.
+
+*   `go/link-name` links to your page through Google’s shortlink service
+
+## Commit-log message integration
+
+Most projects are configured to allow the Git Watcher tool to post comments to
+issues. Including `Bug: <bug_id...>` in a commit message triggers Git Watcher to
+post a comment mentioning the commit on the issue when the commit's CL is
+submitted. Including `Fixed: <bug_id...>` in a commit message triggers Git
+Watcher to set the referenced bugs' status to "Fixed".
+
+Both the `Bug:` and the `Fixed:` automations accept bug IDs in multiple formats:
+*   `Fixed: n`
+*   `Fixed: project:n`
+*   `Fixed: <Issue Tracker URL>`
diff --git a/doc/userguide/profiles-and-hotlists.md b/doc/userguide/profiles-and-hotlists.md
new file mode 100644
index 0000000..4c85888
--- /dev/null
+++ b/doc/userguide/profiles-and-hotlists.md
@@ -0,0 +1,202 @@
+# User Profiles and Hotlists
+
+[TOC]
+
+## The account menu
+
+The user account menu is located at the far upper-right of each Monorail page.
+When you are signed out, it offers a link to sign in. When you are signed in, it
+offers a menu with choices to switch users, access your user pages, or sign out.
+User pages include the user profile, updates, settings, saved queries, and
+hotlists.
+
+## The user profile page
+
+Each Monorail user has a profile page that can be accessed at URL `/u/EMAIL` or
+via their user ID number. You can access your own profile page through the
+account menu. You can click to access the profile page of any user who you see
+mentioned on an issue detail page as the issue reporter, owner, CC'd user, or a
+comment author.
+
+The user profile page lists projects where that user has a membership. The
+profile page shows how long it has been since the user used that account to
+visit the site. Please note that some users have multiple accounts, so they may
+have visited more recently using a different account. Also, if the user has set
+a vacation message, that message is shown here.
+
+Any project owner may ban a user from the site by clicking a button on the user
+profile page. This is one way that we fight spam and abuse.
+
+## Linked accounts
+
+Googlers who have an @chromium.org account may wish to link it to their
+@google.com account. These two types of accounts can be linked with one becoming
+the parent account and the other becoming the child account.
+
+When accounts are linked:
+
+*   Using the child account will display a reminder notice to switch to the
+    parent account.
+
+*   When signed in to the parent account, the user also has all permissions of
+    the child account.
+
+*   If both accounts would be listed in an autocomplete menu, only the parent
+    account is listed.
+
+*   If both accounts would be notified of an issue change, only the parent
+    account is notified.
+
+*   Searching using the `me` keyword will match issues that reference either
+    account.
+
+To link accounts:
+1.  Sign in to the account that you want to become the child
+    account (the one that you don't intend to use any longer).
+1.  Visit the profile page for the child account.
+1.  Invite the other account to be the parent account.
+1.  Use the account menu to switch users to the parent account (the one
+    that you intend to use from now on).
+1.  Use the account menu to go to the profile page for the parent
+    account. This is not the profile that you are already on.
+1.  Accept the invitation to link the child account.
+
+To unlink accounts: sign in as either account, use the account menu to navigate
+to your profile page, and click the `Unlink` button.
+
+## The user settings page
+
+The settings page allows users to set user preferences for their account. You
+can navigate to the settings page by signing in and selecting `Settings` from
+the account menu.
+
+On that page you can set preferences that affect:
+
+*   Privacy: How your email address is displayed to non-members.
+
+*   Notifications: What triggers notifications to you and how they are
+    formatted.
+
+*   Community interactions: Opt into settings that help avoid accidental
+    oversharing.
+
+*   Availability: You can let other users know that you are away.
+
+Site administrators can also view and change the settings for any other user on
+that user's profile page.
+
+## The user activity page
+
+The user updates page lists recent activity by that user. This page can be
+reached by clicking `Updates` in the account menu for your own updates, or via
+the `Updates` tab on any user's profile page.
+
+The list of updates includes new issue reports and comments posted on existing
+issues. Each row show how long ago the activity happened, which issue was
+affected, and the content of the change. You can click to expand each row to
+show more details.
+
+The list of issue changes only includes rows for issues that the signed in user
+is currently allowed to view.
+
+## The user hotlists page
+
+The user hotlists page lists hotlists that a user owns or can edit. This page
+can be reached by clicking `Hotlists` in the account menu for your own hotlists,
+or via the `Hotlists` tab at the top of any users profile page.
+
+Clicking on a row in the hotlists table navigates to the list of issues in that
+hotlist. When viewing a hotlist, the hotlist owner and editors may rerank issues
+in the hotlist, and they may add or remove issues. Reranking issues in a hotlist
+is only possible when the issues are sorted by rank. Reranking is done by
+dragging a gripper icon up or down the list.
+
+In the list of hotlists, only hotlists that the signed in user is allowed to
+view are shown. And, within a specific hotlist, only issues that the signed in
+user is allowed to view are shown.
+
+## How to create a hotlist
+
+1.  Sign in and select `Hotlists` from the account menu.
+1.  Click the `Create hotlist` button.
+1.  Choose a name and provide a description for the hotlist.
+1.  You may list other users who should be able to edit the hotlist.
+1.  Hotlists are only visible to hotlist members by default, but you can make
+    your hotlist public.
+1.  Submit the form.
+
+It is also possible to create a hotlist directly from an issue list, an issue
+detail page, or an existing hotlist page. See below for details.
+
+## Who can view a hotlist?
+
+A public hotlist can be viewed by anyone on the Internet, even anonymous users.
+
+A members-only hotlist can only be viewed by the hotlist owner and members
+listed on the hotlist people page. You can add or remove people from your
+hotlist by clicking the `People` tab at the very top of any hotlist page.
+
+Hotlists do not affect issue permissions. The individual issues within a hotlist
+are subject to the normal permission checking for issues. If a user cannot view
+an issue, they will not see it listed in the hotlist, even if they can view or
+edit the hotlist itself.
+
+## How to add or remove issues to a hotlist
+
+There are several ways to do it.
+
+Starting from an issue detail page, you can add the issue to one or more
+hotlists, or remove it:
+
+1.  Sign in and view an issue detail page.
+1.  Click `Update your hotlists` in the issue data column.
+1.  In the dialog box, check or uncheck the names of hotlists to add or remove.
+1.  Alternatively, you can create a new hotlist directly from that dialog box.
+
+Project members can add multiple issues to hotlists by starting from the issue
+list page or an existing source hotlist:
+
+<!-- TODO: The WC version of the issue list does not require the user
+to be a member. -->
+
+1.  Sign in as a project member and view an issue list page or existing hotlist.
+1.  Select one or more issues by clicking checkboxes or using the `x` keystroke.
+1.  Click `Add to hotlist...` in the action options above the issue list.
+1.  In the dialog box, check the names of hotlists to add the issues to.
+1.  Alternatively, you can create a new hotlist directly from that dialog box.
+
+Starting from the list of issues in the target hotlist:
+
+1.  Sign in as the hotlist owner or a member who can edit the hotlist.
+1.  Click `Add issues...` from the actions above the issue list.
+1.  Type in a comma-separated list of project names and issue IDs. For example
+    `chromium:1234`.
+
+## How to rerank issues in a hotlist
+
+1.  Sign in as the hotlist owner or a member who can edit the hotlist.
+1.  Visit the hotlist issue list page.
+1.  Don't sort the issues by any column heading other than Rank.
+1.  As you hover the mouse over each issue row, a gripper icon will appear in
+    the far left column.
+1.  Drag the gripper icon up or down the list to place the selected issue in the
+    new position.
+
+## How to delete a hotlist
+
+1.  Sign in as the hotlist owner.
+1.  Visit the hotlist issue list page.
+1.  Click the `Settings` tab at the very top of the page.
+1.  Click `Delete hotlist` and confirm the deletion.
+
+## How can I completely delete my account?
+
+You can delete your Google account at http://myaccount.google.com. If you do
+that, a few days later, Monorail will be notified of the account deletion. At
+that time, issues and comments posted by that account will be changed to
+indicate that the author is `a deleted user`. The issues and comments themselves
+are your contribution to the project and they remain a part of the project.
+
+If you wish to completely delete your Monorail account without deleting your
+Google account, please
+[file an issue](http://bugs.chromium.org/p/monorail/issues/entry?labels=Restrict-View-Google).
diff --git a/doc/userguide/project-owners.md b/doc/userguide/project-owners.md
new file mode 100644
index 0000000..0ce4f5b
--- /dev/null
+++ b/doc/userguide/project-owners.md
@@ -0,0 +1,398 @@
+# Project Owner's Guide
+
+[TOC]
+
+## Why does Monorail have projects?
+
+Each project contains issues, grants roles to project members, and
+configures how issues are tracked in that project.
+
+Projects are coarse-grained containers that provide the most basic
+issue organization and access control capabilities.  For example,
+issues related to the Chromium browser are in `/p/chromium`, while
+issues for the Monorail issue tracker are in `/p/monorail`.  Monorail
+has many ways to organize issues, such as labels and components, but
+at the highest level, issues are organized by project.  Likewise,
+Monorail has many ways to control access to issues through restriction
+labels, but at the highest level a user either has permission to visit
+an entire project or they do not.  Projects also provide a
+coarse-grained life-cycle for issues: when the entire project is
+archived, all issues belonging to that project become inaccessible.
+
+The rest of this chapter deals with how project owners can configure
+the issue tracking process within a project.  Each project is intended
+to have a single, unified, and coordinated process for tracking
+issues.  If two issues are in the same project, they should be
+expected to have roughly the same life-cycle and meaningful fields,
+whereas two issues in two separate projects might be tracked in fairly
+different ways.  Also, the set of possible issue owners is determined
+by the members of the project, so two issues in two distinct projects
+could have two distinct sets of possible issue owners.
+
+Unlike some other issue tracking tools, components in Monorail are not
+a unit of process definition: an issue can be in zero, any one, or any
+number of components within a project. The components should just
+provide context as to which part of the project's source code has the
+defect and which teams should be CC'd on the issue.  Every issue in a
+project should have the same life-cycle regardless of component.  Any
+member of a project could be the owner of any issue in that project,
+regardless of components.
+
+Issues can be moved between projects, but that is uncommon, and they
+are contained within exactly one project at any time.  When an issue
+is moved between projects, it is likely that several fields of the
+issue will need to be updated, such as the status and owner.  In
+contrast, components within a project can usually be added to or
+removed from an issue while keeping other fields unchanged.
+
+## How to quickly remove spam and spammers
+
+The purpose of Monorail is to help developers resolve software defects
+and other issues.  Any comments that seem to be spam, abuse, or wildly
+off topic should be removed from the site.  That can be done by using
+the `...` menu on comments or issues.
+
+Any project owner can ban a user from the site by clicking on the user
+email link to get to that user’s profile page, and then clicking `Ban
+Spammer`.  All comments and issues entered by that user are
+automatically marked as spam.
+
+## How to grant roles to project members
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `People`.
+1.  Click the `Add members` button.
+1.  Enter the email addresses of the users that you want to add to the project.
+1.  Choose the role that they should have: Owner, Committer, or Contributor.
+1.  Click `Save changes`.
+
+Once a user has been granted a role in the project, the people list
+page will have a row for that user.  Anyone who can visit the project
+can click a project member row to see details of that user’s
+permissions in the project on a people detail page.  Project owners
+can use the people details page to change the role of a user or grant
+them individual permissions.
+
+User roles in a project can be removed by clicking buttons on either
+the people list page or people detail page.
+
+## How to configure statuses
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Statuses` tab at the top of the page.
+1.  Type open and closed status definition lines in two text
+    input areas on that page.
+1.  Click `Save changes`.
+
+The syntax of a status definition line is `[#]StatusName[=
+docstring]`.  Where `#` indicates that the status is deprecated.
+`StatusName` is the name of the status, which may contain dots,
+dashes, and underscores, but no spaces.  And, the optional `docstring`
+is the documentation string that will be displayed to users to explain
+the meaning of that status.
+
+Deprecated status values are not offered in autocomplete menus or the
+status field menu.  However, they are kept in the system so that
+existing issues that have that status can be sorted according to the
+logical rank.  In contrast, a status value that is no longer desired
+could be simply deleted, which would remove it from menu choices and
+also lose the logical ranking of that status value.
+
+The status definition page also has a field to list statuses that
+indicate that an issue is being merged into another issue.  Usually
+that is set to simply `Duplicate`.  However, it is possible to use a
+different name for that status that fits your process better, or to
+list multiple such statuses.
+
+## How to configure labels
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Labels and fields` tab at the top of the page.
+1.  Type label definition lines in the text input area.
+1.  Click `Save changes`.
+
+The syntax of a label definition line is `[#]LabelName[= docstring]`.
+Where `#` indicates that the label is deprecated.  `LabelName` is the
+name of the status, which may contain dots, dashes, and underscores,
+but no spaces.  And, the optional `docstring` is the documentation
+string that will be displayed to users to explain the meaning of that
+label.
+
+It is common to define a set of related Key-Value labels that all have
+the same Key.  The Monorail user interface treats them somewhat like
+enum fields.  The Key part of the label can be used in queries, as
+search result column headings, or as grid axes.  Some Key strings can
+be listed as exclusive prefixes, which means that the Monorail UI will
+not offer autocomplete options for another value once an issue has one
+of those Key-Value labels.
+
+Deprecated labels values are not offered in autocomplete menus, just
+as with deprecated status values.  See the section above for details.
+
+## How to configure custom fields
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Labels and fields` tab at the top of the page.
+1.  To edit an existing custom field, click on the row for that
+    custom field in the field definition table.
+1.  Or, to create a new custom field, click `Add field`.
+
+The form used to create or edit a field definition consists of the
+field name, field type, and various validation options that are
+appropriate to that field type.  For example, an integer custom field
+could specify a minimum or maximum value.  Most details of a field
+definition can be changed later, but the name cannot.  Also, a deleted
+field name cannot be reused.
+
+Enum-type custom fields are stored as labels in Monorail's database.
+If you start to create an enum-type custom field with name "Key", you
+will immediately see enum values offered for each existing Key-Value
+label that has the same Key part.  The syntax for defining new enum
+options is `EnumValue[= docstring]`.
+
+Custom fields may be configured to be applicable to any issue or only
+to issues that have a specific `Type-*` label.  And, the field can be
+optional or required on issues where it is applicable.  For example, a
+DesignDoc custom field with a link to a design document might be a
+required field for any issue that has the Type-Design-Review label.
+
+A custom field can have a `Parent Approval`, which means they will
+appear under the parent approval's section in an issue, separate from
+where other fields are shown.
+A custom field can also be an `Issue Gate field`, which means they will
+appear under each issue's gate, separate from where other fields are shown.
+For example, if an Issue has gates `Design`, `Test`, and `Launch`, a gate
+field called `Milestone` would appear under each gate section. If
+`Milestone` is a multi-valued field, each gate's "Milestone" can have
+multiple values.
+A custom field cannot be a gate field and have a parent approval at the
+same time.
+
+Some fields are more commonly used than others.  In large projects,
+there may be variations of the software development process that are
+only used with a few issues.  Over time, more and more such process
+variations will be defined, and the total set of custom fields to
+support all those different variations could make issue editing forms
+long and complex.  Monorail helps manage that situation by allowing
+fields to be defined as important enough to always be offered as a
+visible field when the field is applicable to the issue, or only
+important enough to be kept behind a `Show all fields` link.
+
+Project owners may edit any field.  Each field may also specify a list
+of field administrators who are also allowed to edit that field.  This
+helps project owners delegate responsibility for configuring fields
+used in certain development processes to the developers who perform
+those processes.  Each field can also specify a list of field editors
+when the field is restricted.  This helps project owners delegate responsibility
+for changing the value of a custom field (in issues or templates) to specific
+users or monorail groups.
+
+## How to configure approvals
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Labels and fields` tab at the top of the page.
+1.  To edit an existing approval definition, click on the row for that
+    approval field in the field definition table.
+1.  Or, to create a new approval definition, click `Add field`.
+
+Approvals are used to track the review and approval of some feature,
+project, or proposal.
+An approval definition has a default set of `Approvers`,
+a `Survey` field, plus a `Description` and `Admins` like custom fields.
+
+Approval definitions are used to create Approval Values in issues.
+`Approvers` are allowed to set an Approval Value status to
+`Approved` or `Not Approved`. and `NA`. When an issue is first created
+with approvals, the approval definition's `Approvers` are used as the
+initial default set of the Approval Value's `Approvers`. From there,
+the approvers can add or remove other users as approverss for that
+particular issue.
+
+An approval definition's `Survey` will also be copied over to an issue's
+approval value during issue creation and can be modified within the issue
+later on.
+
+Changing an Approval definition's `Approvers` or `Survey` does not
+retroactively change the `Approvers` or `Survey` of approval values
+in existing issues.
+
+Approval values in issues can only be created using issue templates.
+
+See the section on [configuring templates](#How-to-configure-issue-templates)
+for more information.
+
+## How to configure filter rules
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Rules` tab at the top of the page.
+1.  Fill in some rule predicates and consequences.
+1.  Submit the form.
+
+Filter rules are if-then rules that automatically add information to
+issues when the issues are created, when the issues are updated, and
+when the rules change.  Rules add derived values to issues, which are
+stored separately from the explicitly set values on the issue itself.
+When a value is explicitly set, that value overrides the derived
+value, however the derived value itself cannot be edited except by
+changing the rule.
+
+The purpose of filter rules is to allow the explicit values on an
+issue to focus on capturing the details of the problem situation,
+while pragmatic concerns such as access controls and prioritization
+can have meaningful defaults set automatically.
+
+For example, there could be a rule that says `if [type=Defect
+component=Authentication] then add label Restrict-View-SecurityTeam`.
+That would mean that any defect in the system’s authentication
+component should be restricted to only be viewable by the security
+team (and the reporter, owner, and any CC’d users).
+
+## How to configure issue templates
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Templates` tab at the top of the page.
+
+Issue templates are used to create new issues with some of the details
+filled in.  Each template has a name and the initial values for the
+issue summary, description, and other fields.  Most templates will be
+available to all users who can report issues in the project, but it is
+also possible to restrict a template to only project members.
+
+On that page you can set the default templates that are used for new
+issue reports by project members or non-members.  And, there is a list
+of existing templates and a button to create a new template.
+
+Each template must have a unique name.  Templates cannot be renamed
+after creation, but you can create a new template with the desired
+name then delete the original template.
+
+Template summary lines that begin with text inside brackets, such as
+`[Deployment] Name of system that needs to be deployed` will cause the
+issue entry page to keep the bracketed text when the user initially
+types the summary line.  Also, any labels that end with a question
+mark, like `Key-?`, will cause the issue entry page to require the
+user to edit that label before submitting the form.
+
+A template can have some set of approvals that may belong to some
+set of gates defined in the template. A template may have approvals
+that do not belong to any gate, but a gate must have at least one
+approval associated with it. Approvals and gates are for project owners
+to communicate some process of reviewing and approving. A project may
+have a process where new features must be reviewed by some group of
+UX, Privacy, Security, A11y teams. These are represented by approvals.
+The process may also require some phases like `Proposal`, `Design`,
+and `Launch` where the approvals of one phase must be completed before
+a team can move on to the next phase. This process can be represented
+by approvals within gates.
+When an issue is created using the template, it inherits the set of
+approvals and gates. The approvals and gates that exist within an
+issue cannot be changed after issue creation. Issues cannot be
+created with approvals and gates that are not copied over from a
+template.
+Project owners can create issue templates for a process. Whenever
+a team wants to begin such a process, they would create a new issue
+using the appropriate template.
+Changing the approvals and gates structure of a template will not
+retroactively change the approvals and gates of issues that have
+already been created.
+
+Each template can have a comma-separated list of template
+administrators who are allowed to edit that template.  This allows
+project owners to delegate authority to maintain certain templates to
+the teams that work on issues that use that template.  However, the
+overall set of templates is controlled by the project owners.
+
+## How to configure components
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Components` tab at the top of the page.
+
+Components form a high-level outline of the software being developed
+in the project so that an issue can be related to the part of that
+software that needs to change.  For example, if a software system has
+architectural tiers for database access, business logic, and user
+interface, that would suggest using three components in Monorail.  If
+a piece of software is developed by a different team or using
+different processes, then using a separate monorail project may be
+more appropriate.
+
+The components list page shows a list of all currently active
+components in the project.  It can be filtered to show a smaller set
+of components, for example just the components that the signed-in user
+is involved in.  Showing all components includes both active
+components and components that have been marked as deprecated.
+
+Each component is identified by a path string that consists of a list
+of names separated by greater-than signs, e.g., `Database>Metrics`.
+When searching for issues by component, subcomponents are normally
+included in the results.
+
+The main purpose of components is to indicate the part of the software
+that needs to change to resolve an issue.  That could be determined as
+part of the investigative work needed to fully document the issue.
+Monorail components help with some of the pragmatic aspects of issue
+tracking by automatically adding labels or CCing people who might help
+resolve the issue.
+
+Each component can have a comma-separated list of component
+administrators who are allowed to edit that component.  This allows
+project owners to delegate authority to maintain certain components to
+the teams that work on issues that use that component.  However, the
+overall set of components and their organization is controlled by the
+project owners.
+
+## How to configure default views
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Development process`.
+1.  Click the `Views` tab at the top of the page.
+1.  Fill in a default query for project members to help them stay
+    focused on the issues that are most important for the team as a
+    whole, or set it to `owner:me` to focus each team member on
+    resolving the issues assigned to them.
+1.  Fill in default list columns and grid options.
+1.  Submit the form.
+
+## How to administer project settings
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Administer`.
+
+This page allows project owners to edit the project summary line,
+description, access level and some other settings.  The description
+can be written in Markdown.
+
+## How to view the project storage quota
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Administer`.
+1.  Click the `Advanced` tab at the top of the page.
+
+The second section of the page shows how much storage space has been
+used for attachments in this project and the current limit.  If the
+usage reaches the limit, users will no longer be offered the option to
+add attachments to issues.  Site administrators can increase the
+storage limit for each project.
+
+## How to move, archive, or delete a project
+
+1.  Sign in as a project owner and visit any page in your project.
+1.  Open the gear menu and select `Administer`.
+1.  Click the `Advanced` tab at the top of the page.
+1.  Click a button to `Archive` the project.
+1.  Or, fill in a new project URL and click the `Move` button to
+    indicate that the project has moved.
+
+When a project is archived, only project owners may access the content
+of the project.  Also, ‘Unarchive’ and `Delete` options will be
+offered on that page.  If the project owner clicks the `Delete`
+button, the contents of the project will immediately become
+inaccessible to any users, and all data for that project will be
+deleted from Monorail's database within a few days.
diff --git a/doc/userguide/project-pages.md b/doc/userguide/project-pages.md
new file mode 100644
index 0000000..3b30716
--- /dev/null
+++ b/doc/userguide/project-pages.md
@@ -0,0 +1,76 @@
+# Other Project Pages for Users
+
+[TOC]
+
+## The project list page
+
+The site home page at https://bugs.chromium.org/ shows a list of
+projects that we host on the site.  Each project row shows the project
+name, summary, your role in the project, and how long it has been
+since the last activity in that project.
+
+## The project menu
+
+When you are in a project, the name of the project is shown in the
+upper-left of each page.  Clicking on that name opens a menu that
+lists all the projects where you have a membership or that you have
+starred.  You can easily navigate between projects by selecting a
+different project from that menu.
+
+## The project people page
+
+When you are in a project, there is a gear menu located just to the
+right of the issue search box.  Selecting `People` from that menu
+takes you to the project people list.
+
+The project people list page lists all the members of the current
+project, along with their roles, extra permissions, and any notes.  As
+a user, you can refer to this list to learn who is involved in the
+project so that you can involve them in issues or contact them via
+email.  As with all pages on the site, project members see the email
+addresses of other users, and non-members will only see the email
+addresses of other users who have opted into sharing that information.
+
+Project owners use this page to add and remove project members.
+
+## The development process pages
+
+When you are in a project, there is a gear menu located just to the
+right of the issue search box.  Selecting `Development process` from
+that menu takes you to the project introduction page.
+
+The project introduction page has a short textual description of the
+goal of the project.  And, most projects have a link to the source
+code location for the software being developed.
+
+The tabs across the top of the page allow you to navigate to pages
+that show many more details of the software development process.
+These pages are accessible to all users who can view the project.  The
+purpose of these pages is to help all users participate in the project
+productively, which requires that they know the meaning of the fields
+and values being used.
+
+The development process tabs are:
+
+* Statuses: Lists open and closed issue statuses
+
+* Labels and fields: Lists issue labels and custom field definitions
+
+* Templates: Lists templates used to create new issues
+
+* Components: Lists software components being developed
+
+* Views: Shows default options for the issue list and grid pages, and
+  any saved queries
+
+
+## The project history page
+
+When you are in a project, there is a gear menu located just to the
+right of the issue search box.  Selecting `History` from that menu
+takes you to the project history page.
+
+The project history page shows a list of recent changes to issues in a
+project.  You can click to expand each row to see the details of an
+individual change.  The list only includes items for issues that you
+are currently allowed to view.
diff --git a/doc/userguide/quick-start.md b/doc/userguide/quick-start.md
new file mode 100644
index 0000000..fd9b790
--- /dev/null
+++ b/doc/userguide/quick-start.md
@@ -0,0 +1,186 @@
+# Monorail Quick Start
+
+[TOC]
+
+## Mission: Track issues to help improve software products
+
+Monorail’s mission is to track issues to help improve software
+products.  Every piece of information stored in Monorail’s database is
+contributed from a user to one of the projects that we host for the
+purpose of helping developers work on that project.  Issue tracking
+covers many different development processes, including fixing defects,
+organizing enhancement requests, breaking down new development work
+into tasks, tracking operational and support tasks, coordinating and
+prioritizing work, estimating schedules, and overseeing new feature
+launches.
+
+
+## Guiding principles
+
+* Flexibility: Monorail is unusually flexible, especially in its use
+  of labels and custom fields.  This allows large projects to include
+  several small teams, some of which care about labels and fields that
+  are specific to their own processes.  Flexibility also enables
+  process changes to be gracefully phased in or phased out over the
+  long term.
+
+* Security: Even open source projects need access controls so that
+  developers can work to resolve security flaws before disclosing
+  them.  Per-issue access controls allow developers to work closely
+  with users and partners on a mix of public and restricted issues.
+
+* Inclusiveness: Computing is an empowering and equalizing force in
+  society.  Monorail’s inclusive functionality and user interface can
+  help many different stakeholders influence the future of a project,
+  causing ripple effects of inclusion through our software ecosystem.
+
+
+## High-level organization
+
+A Monorail server is divided into projects, such as `/p/chromium` and
+`/p/monorail`, that each have a list of project members, and that
+contain a set of issues.  Each project also has a page that lists the
+history of issue changes, and a set of pages that describe the software
+development process configured for that project.
+
+Each issue has metadata such as the issue summary, reporter, owner,
+CC'd users, labels, and custom fields.  Each issue also has a list of
+comments, which may each have some attachments and amendments to the
+metadata.
+
+Each user also has a profile page and related pages that show that user's
+activity on the site, their saved queries, and their hotlists.
+
+
+## How to enter an issue
+
+Please search for relevant existing issues before entering a new issue.
+
+1. Sign in to Monorail.  If this is your first time, you will see a
+   privacy notice dialog box.
+1. Click on the `New issue` button at the top of the page.
+   Note: In the `/p/chromium` project, non-members will be redirected
+   to a new issue wizard.
+1. Choose an issue template based on the kind of issue that you want
+   to create.
+1. Fill in the issue summary and details.
+1. Project members can also set initial values for labels and fields.
+1. Attach files that will help project members understand and
+   resolve the issue.
+
+Most issue types are public or could become public, so don't include
+personal or confidential information.  Be mindful of the contents of
+attachments, and crop and redact screenshots to avoid sharing
+unintended details.  Never include passwords.
+
+When you report an issue, you star the issue by default.  Starring
+causes you to get email notifications of comments on that issue.
+
+It is also possible to enter issues by clicking on a "deep link" to
+our issue entry page.  Such links are sometimes used in documentation
+pages that tell users when to file an issue.
+
+
+## How to search for issues
+
+1. Click on the search box at the top of the page.
+1. Type in some search terms.  These can be structured or full text terms.
+   The autocomplete menu lists possible structured terms.
+1. The search will cover open issues by default.  You can search all
+   issues or another scope by selecting a value from the search scope
+   menu.
+1. Press Enter, or click the search icon.
+
+A menu at the end of the search box input field offers links to the
+advanced search page and the search tips page.
+
+You can jump directly to any issue by searching for the issue’s ID
+number.
+
+
+## How to comment on an issue
+
+1. Sign in to Monorail.  If this is your first time, you will see a
+   privacy notice dialog box.
+1. The comment box is located at the bottom of each issue detail page,
+   below existing comments.
+1. Please keep comments respectful and constructive with the goal of
+   resolving the issue in mind.  A message with a code of conduct link
+   is shown to new users.
+1. If you want to be notified of future updates on this issue, click
+   the star icon.
+
+If you need to delete a comment that you posted, use the "..." menu
+for that comment.  Attachments can also be marked as deleted.
+
+
+## How to edit an issue
+
+1. Sign in to Monorail as a project member.  Only project members may
+   edit issues.
+1. The editing fields are located with the comment form at the bottom
+   of the page.
+1. Please consider posting a comment that briefly explains the reason
+   for the edit.  For example, "Lowering the priority of this defect
+   because there is a clear work-around."
+
+It is also possible for project members to bulk edit multiple issues
+at one time, and to update an issue by replying to some issue update
+notifications.
+
+
+## How to link to an issue
+
+If you need a URL that you can bookmark or paste into another document:
+
+* You can copy and share the URL of the issue as it is shown in the
+  browser location bar.
+
+* For a cleaner link, open the browser context menu on the link icon
+  located next to the issue summary, then choose "Copy Link Address".
+
+If you are writing text in an issue comment, you can make a textual
+reference to another issue by typing the word "issue" or "issues".  For
+example:
+
+* issue 1234
+* issues 1234, 2345, and 3456
+* issue monorail:1234
+
+
+## How to be notified of changes to an issue
+
+There are several ways to get notifications:
+
+* Click the star icon at the top of the issue to express your interest
+  in seeing the issue resolved and to be notified of future updates to
+  the issue.  Or, click the star icon for that issue in the issue list.
+
+* The issue owner and any CC’d addresses are notified of changes.
+
+* You can subscribe to a saved query.  Start by clicking the "Saved
+  queries" item in the account menu.
+
+
+## How to associate a CL with an issue
+
+1. When you create a code change list, include a "BUG:" or "Fixed:"
+   line at the bottom of the change description.
+1. When you upload the CL for review, consider posting a comment to
+   the issue with the URL of the pending CL.
+1. Most projects have set up the Git Watcher, formerly Bugdroid, to post a
+   comment to the issue when the CL lands.
+
+
+## How to ask for help and report problems with Monorail itself
+
+<!-- This is purposely written in a couple different places to make it
+     easier for users to find. -->
+
+If you wish to file a bug against Monorail itself, please do so in our
+[self-hosting tracker](https://bugs.chromium.org/p/monorail/issues/entry).
+We also discuss development of Monorail at `infra-dev@chromium.org`.
+
+You can report spam issues via the "..." menu near the issue summary.
+You can report spam comments via the "..." menu on that comment.  Any
+project owner can ban a spammer from the site.
diff --git a/doc/userguide/site-admins.md b/doc/userguide/site-admins.md
new file mode 100644
index 0000000..619db5e
--- /dev/null
+++ b/doc/userguide/site-admins.md
@@ -0,0 +1,95 @@
+# Site Admin's Guide
+
+[TOC]
+
+## What are Monorail site administrator accounts and what are they used for?
+
+Site administrators are like super-users in Monorail.  A site admin
+account can perform any action that any other account can perform, and
+some that are only available for site administrators.  While most
+permissions can be granted to project members by project owners, some
+of the administrative permissions are reserved for site admins only.
+
+Site admins have the ability to create projects and user groups.  They
+can also make changes to existing projects, user groups, users, or
+issues on behalf of project owners that are having trouble making the
+desired changes for some reason.  For example, a site admin might help
+a project owner by setting up an initial project configuration.  Both
+project owners and site admins can ban users from the site to help
+fight spam and abuse.
+
+
+## How to create a project
+
+1.  Sign into your site admin account.
+1.  Visit the site home page.
+1.  Click the `Create a new project` link.
+1.  Fill in the project name, summary, and description.
+1.  Submit the form.
+
+1.  In the new project, visit the People page to grant a role to a
+    project owner and remove yourself.
+
+## How to delete a project
+
+1.  Sign into your site admin account.
+1.  Open the gear menu and select `Administer`.
+1.  Click the `Advanced` tab at the top of the page.
+1.  Click a button to `Archive` the project.
+
+Site admins also have a `Doom` option that schedules the project for
+deletion in 90 days.  The `Archive` options will be a better choice
+for most projects because storage space is typically not a problem for
+our site.
+
+## How to increase the project storage quota
+
+1.  Sign into your site admin account and visit any page in the project.
+1.  Open the gear menu and select `Administer`.
+1.  Click the `Advanced` tab at the top of the page.
+1.  Type in a new storage limit.  The limit is measured in megabytes.
+1.  Click `Update Quota`.
+
+## How to view the list of user groups
+
+1.  Sign into your site admin account.
+1.  Visit the `/g` URL to see the list of user groups.
+
+There is currently no link to navigate to that page.  It is only
+accessible to site admins.
+
+## How to create a new user group
+
+1.  Sign into your site admin account.
+1.  Visit the `/g` URL to see the list of user groups
+1.  Click `Create Group`.
+1.  Fill in the form and submit it.
+
+Monorail has three types of user groups: native groups that are
+managed entirely within Monorail, synchronized user groups that are
+periodically copied from Google Groups or other sources, and computed
+user groups that are based entirely on email address domain name.  To
+set up a synchronized user group, see the Monorail playbook.
+
+## How to ban a user account
+
+1.  Sign into your site admin account.
+1.  Visit the user’s profile page.
+1.  Fill in a reason to ban the user.  Or, click `Ban this user as a spammer`.
+
+The reason field serves as a note to other site admins, it is not
+shown to the user or other users.
+
+If you use the `Ban this user as a spammer` button, all of the issues
+and comments posted by that user will be marked as spam.
+
+## How to completely delete a user account
+
+1.  Sign into your site admin account.
+1.  Visit the user's profile page.
+1.  Click `Delete user account`.
+
+The user record will be deleted from our database.  Any references to
+that user in issue fields or comment author lines will be removed or
+changed to `a deleted user`, but the content itself will be retained
+as part of the project that it was contributed to.
diff --git a/doc/userguide/working-with-issues.md b/doc/userguide/working-with-issues.md
new file mode 100644
index 0000000..a43777b
--- /dev/null
+++ b/doc/userguide/working-with-issues.md
@@ -0,0 +1,350 @@
+# Working with Issues
+
+[TOC]
+
+## Why do we  track issues?
+
+The goal of tracking an issue is to resolve it.  Issues are a way for
+the development team to organize information about work that could be
+done to improve their software product.  There are many types of
+issues including defects, enhancement requests, operational alert
+tickets, and several others.  There are typically far more things that
+the team could be working on than they actually have time to work on,
+so issues are triaged, prioritized, labeled, assigned, and discussed.
+They can be open for a long time before being resolved.  After they
+are resolved, many issues contain important rationale that can be used
+when investigating regressions.
+
+
+## What’s in an issue?  And, why is that information needed?
+
+Issues in Monorail contain a summary line, description, comments, a
+reporter, an owner, CC’d users, labels, components, custom fields, and
+references to related issues.  Users can express interest in an issue
+by starring it, and each issue shows a star count.  Each piece of that
+information serves one or more of the following purposes:
+
+
+* Description of the situation.  Issues should capture details about
+  error messages, steps to reproduce, and how the software product
+  falls short of user expectations.  Attachments may help capture this
+  information in the form of screenshots or logs.
+
+* Routing and prioritization.  Before an issue can be resolved, it
+  needs to be seen by the right team and assigned to an owner who can
+  make the needed changes.  CC’s, components, and labels are often
+  used to make issues show up in the triage queries used by different
+  teams.
+
+* Investigation.  Not every issue can be solved immediately.  Some
+  issues need further investigation to find the root cause of the
+  problem or to weigh different solution approaches.  This information
+  is usually captured in comments, attachments, and links.
+
+* Traceability.  Comments with references to CLs show how much work
+  has been done so far.  After code changes are committed, they must
+  often be verified by a QA team or the issue reporter.  Later, the
+  ability to trace between code commits and issues provides important
+  rationale for the state of the code, which can help prevent future
+  regressions or fix them.
+
+* Advocacy and community.  Users should have a voice in the project.
+  Issue stars help prioritize issues and keep users in the loop as the
+  discussion continues.  Users can post comments to explain the impact
+  that an issue is having on them, to weigh in on solution
+  alternatives, and to thank developers.
+
+
+## Who can view an issue?
+
+Basically, public issues may be viewed by anyone, whereas restricted
+issues can only be viewed by people involved in the issue and project
+members who were granted access that type of issue.
+
+<!-- TODO(jrobbins): Maybe move this to a separate permissons.md
+     reference page. -->
+
+Here are the details:
+
+1. Before a user may access an issue, they must first be able to
+   access the project.  Most projects that we host are public, but
+   some are members-only.
+
+1. The issue participants (including reporter, owner, and any CC’d
+   users) can always view that issue.  Users named in certain custom
+   fields also gain access.  Project owners can view all issues in
+   that project, and site administrators can view any issue in any
+   project.
+
+1. For other project members, an issue that has a label
+   `Restrict-View-X` may only be viewed if the user has been granted
+   permission `X` in that project.  If there are multiple
+   `Restrict-View-*` labels, the user needs all permissions specified
+   in those labels.
+
+1. An issue that is in a public project and that has no restriction
+   labels may be viewed by anyone.
+
+For example, in a public project there could be an issue labeled
+`Restrict-View-SecurityTeam`.  The only users who may view that issue
+would be the reporter, owner, CC’d users, users named in custom
+fields, project owners, site admins, and project members who were
+granted the `SecurityTeam` permission.  It would not be viewable by
+anonymous visitors, non-members who are not participants, or even
+project members who were not granted the `SecurityTeam` permission.
+
+
+## Who can edit an issue?
+
+Basically, only project committers and project owners may edit issues.
+That set of users is narrowed down if the issue has restriction
+labels.
+
+<!-- TODO(jrobbins): Maybe move this to a separate permissons.md
+     reference page. -->
+
+Here are the details:
+
+1. A user cannot edit an issue if they are not permitted to view it.
+
+1. The issue owner can always edit that issue.  Also, users named in
+   certain custom fields may be able to edit the issue.  Project
+   owners can edit any issue in that project, and site administrators
+   can edit any issue in any project.
+
+1. For other project members, editing an issue requires the
+   `EditIssue` permission.  That permission is part of the project
+   committer role, and it can also be granted to a project
+   contributor.
+
+1. If the issue has a restriction label `Restrict-EditIssue-X`, then
+   only project members who were granted permission `X` in that
+   project may edit that issue.  If there are multiple
+   `Restrict-EditIssue-*` labels, the user needs all permissions
+   specified in those labels.
+
+
+## Who owns issue content?
+
+Because the purpose of tracking issues is to help project members
+improve the software that they are developing, all issue content
+belongs to the project.  When a user reports an issue or posts a
+comment, they are contributing that information to the project for the
+benefit of the project.
+
+
+## How to enter an issue
+
+<!-- Note that this is also in quick-start.md. -->
+
+1. Sign in to Monorail.  If this is your first time, you will see a
+   privacy notice dialog box.
+
+1. Click on the `New issue` button at the top of the page.
+
+1. In the /p/chromium project, non-members will be directed to the
+   "new issue wizard".
+
+1. Choose an issue template based on the kind of issue that you want to create.
+
+1. Fill in the issue summary and details.
+
+1. Project members can also set initial values for labels and fields.
+
+1. Optionally, attach files that will help project members understand
+   and resolve the issue.
+
+1. When you report an issue, you star the issue by default.  Starring
+   causes you to get email notifications of comments on that issue.
+
+
+## How to link to an issue or a specific comment in an issue
+
+<!-- Note that this is also in quick-start.md. -->
+
+If you need a URL that you can bookmark or paste into another document:
+
+* You can copy and share the URL of the issue as it is shown in the
+  browser location bar.
+
+* For a cleaner link, click on the link icon located to the right of
+  the issue summary.
+
+If you are writing text in an issue comment, you can make a textual
+reference to another issue by typing word "issue" or "issues".  For
+example:
+
+* issue 1234
+* issues 1234, 2345, and 3456
+* issue monorail:1234
+* Googlers can reference an internal issue by using: b/1234
+
+
+## How to view and download issue attachments
+
+* Attached images and videos show a thumbnail or preview image.  You
+  can click on that to see the full-sized image or play the video.
+
+* Click the `View` link to view the attachment in a new browser tab.
+  Not all types of attachments can be viewed this way.
+
+* For many types of text files, viewing the attachment opens a new
+  page that shows the file with syntax highlighting.
+
+* Click the `Download` link to download the attachment to your
+  computer.  Usually the file name is the same as the one used when
+  the file was uploaded, however in some cases we use a filename that
+  we know is safe.
+
+
+## How to link to a specific line of attached text file
+
+1. Open the attachment by clicking the `View` link.
+
+1. In the text file attachment viewer page, the line numbers are
+   hyperlinks.  Click one to add an anchor to your current browser
+   location, or use a pop-up menu to copy the link address.
+
+
+## How to comment on an issue
+
+<!-- Note that this is also in quick-start.md. -->
+
+1. Sign in to Monorail.  If this is your first time, you will see a
+   privacy notice dialog box.
+
+1. The comment box is located at the bottom of each issue detail page,
+   below existing comments.
+
+1. Please keep comments respectful and constructive with the goal of
+   resolving the issue in mind.  A message with a code of conduct link
+   is shown to new users.
+
+1. If you want to be notified of future updates on this issue, click
+   the star icon.
+
+
+## How to delete a comment or attachment
+
+Users may delete comments that they posted.  Also, project owners and
+site administrators may delete any comment.
+
+1. Open the `...` menu that is near the comment.
+1. Select `Delete comment` to mark the comment as deleted.
+
+
+## How to report spam
+
+Users may report spam issues and comments.  These spam reports are
+used as inputs to our spam detection model.  When a project member
+reports spam, the issue or comment is immediately classified as spam.
+
+1. Sign in to Monorail.
+1. Open the `...` menu near the issue summary or comment.
+1. Select `Flag comment` item or the `Flag issue as spam` item.
+
+
+## How to edit an issue
+
+<!-- Note that this is also in quick-start.md. -->
+
+1. Sign in to Monorail as a project member.  Only project members may
+   edit issues.
+
+1. The editing fields are located with the comment form at the bottom
+   of the page.
+
+1. Please consider posting a comment that briefly explains the reason
+   for the edit.  For example, "Lowering the priority of this defect
+   because there is a clear work-around."
+
+It is also possible for project members to bulk edit multiple issues
+at one time, and to update an issue by replying to some issue update
+notifications.
+
+
+## How to close an issue as a duplicate of another issue
+
+1. Sign in as project committer and view the issue.
+
+1. Set the issue status to `Duplicate`.
+
+1. A `Merged into:` input field will appear, type the other issue ID
+   into that field.
+  * If the other issue is in a different project, use `projectname:issue_id`.
+  * For Googlers, if the issue is in our internal issue tracking tool,
+    use `b/issue_id`.
+
+1. Press `Save changes`.
+
+
+## How to be notified of changes to an issue
+
+Anyone can star an issue by clicking the star icon near the issue ID
+on the issue detail page, issue list page, or hotlist page.  Starring
+an issue will subscribe you to email notifications for changes to that
+issue.  The issue reporter stars the issue by default, but they can
+unstar it if they are no longer interested.
+
+Another way to get notifications is to create a saved query.  Sign in
+and click your email address in the far upper right of the page to
+access your account menu, then choose `Saved queries`.  You can name
+and define an issue query for issues that you are interested in.  When
+any issue that matches that query is updated, you will get an email
+notification.
+
+Email notifications of changes are also sent to the issue owner and
+any CC’d users.  Users named in certain custom fields will also be
+notified.  When the issue owner is changed, the old issue owner gets
+one final notification of that change, even though they are no longer
+an issue participant.
+
+Project owners and component admins can also set up filter rules and
+auto-cc rules to automatically add users to the CC field of an issue.
+Filter rules can also cause notifications to be sent to email
+addresses that do not represent users, for example, mailing lists.
+
+
+## How to associate a CL with an issue
+
+When writing your CL description, add a `BUG=` line or a `Fixes:` line
+to the end of the commit log message.  These lines can reference a
+`/p/chromium` issue by issue ID number, or an issue in any project by
+using the "project_name:issue_id" syntax.
+
+After you upload a CL for review, you can copy the URL of the code
+review and then paste it into an issue comment.
+
+When the CL passes review and is committed, the bugdroid utility will
+automatically post a comment to the issue referenced in the `BUG=` or
+`Fixes` line.
+
+
+## How to move or copy an issue between projects
+
+<!-- I am not mentioning DeleteIssue because I think it should not be
+required.  See monorail:6634 -->
+
+You must have permission to edit issues in the destination project.
+Only non-restricted issues can be moved: if an issue is restricted,
+consider creating a new issue in the target project that blocks the
+original issue.
+
+1. Open the `...` menu near the issue summary and choose `Move issue`
+   or “Copy issue”.
+
+1. Select the name of the target project in the dialog box that opens.
+
+1. Press the `Move issue` or `Copy issue` button.
+
+
+## How to delete an issue
+
+In most cases, you should close the issue with status `Invalid` or
+`WontFix` rather than deleting it.  Spam issues should be marked as
+spam so that they help build our spam model.  Only project owners and
+site administrators can mark issues as deleted.
+
+1. Sign in as a project owner and view the issue.
+1. Open the `...` menu near the issue summary and choose `Delete issue`.
+1. Confirm that you really want to mark the issue as deleted.