Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/templates/framework/admin-email-sender-part.ezt b/templates/framework/admin-email-sender-part.ezt
new file mode 100644
index 0000000..783a87f
--- /dev/null
+++ b/templates/framework/admin-email-sender-part.ezt
@@ -0,0 +1,15 @@
+[if-any project_is_restricted]
+<p style="width:35em; border: 1px solid #933; padding: 3px">
+  <b style="color:#933">Important</b>: Access to this project is restricted, so
+  please do not specify a public mailing list address for all notifications.
+  Use only private mailing lists to avoid unwanted disclosures.  If you make
+  your project public later, choose a new mailing list at that time.
+</p>
+[end]
+
+<p>
+    Notifications will be sent from:
+    <tt>[email_from_addr]</tt><br>
+    You may need to add this address as an allowed poster to your mailing list.<br>
+    If using Google Groups, add the address directly with no email delivery.
+</p>
diff --git a/templates/framework/alert.ezt b/templates/framework/alert.ezt
new file mode 100644
index 0000000..45a61aa
--- /dev/null
+++ b/templates/framework/alert.ezt
@@ -0,0 +1,35 @@
+  <table id="alert-table" align="center" border="0" cellspacing="0" cellpadding="0" style="margin-bottom: 6px[if-any alerts.show][else];display: none[end]">
+   <tr><td class="notice" id="notice">
+     [if-any alerts.updated]
+      <a href="[project_home_url]/issues/detail?id=[alerts.updated]">Issue [alerts.updated]</a>
+      has been updated.
+     [end]
+
+     [if-any alerts.moved]
+       Issue has been moved to
+       <a href="/p/[alerts.moved_to_project]/issues/detail?id=[alerts.moved_to_id]">
+         [alerts.moved_to_project]:[alerts.moved_to_id]
+      </a>
+     [end]
+
+     [if-any alerts.copied]
+       <a href="[project_home_url]/issues/detail?id=[alerts.copied_from_id]">Issue [alerts.copied_from_id]</a>
+       has been copied to
+       <a href="/p/[alerts.copied_to_project]/issues/detail?id=[alerts.copied_to_id]">
+         [alerts.copied_to_project]:[alerts.copied_to_id]
+      </a>
+     [end]
+
+     [if-any alerts.saved]
+      Changes have been saved
+     [end]
+
+     [if-any alerts.deleted]
+      [is alerts.deleted "1"]
+       Item deleted
+      [else]
+       [alerts.deleted] items deleted
+      [end]
+     [end]
+   </td></tr>
+  </table>
diff --git a/templates/framework/artifact-collision-page.ezt b/templates/framework/artifact-collision-page.ezt
new file mode 100644
index 0000000..0c34479
--- /dev/null
+++ b/templates/framework/artifact-collision-page.ezt
@@ -0,0 +1,30 @@
+[include "../framework/header.ezt" "showtabs"]
+
+[# Note: No need for UI element permission checking here. ]
+
+<h3>Update Collision</h3>
+
+<h4>What happened?</h4>
+
+<p>While you were viewing or updating [artifact_name], another user
+submitted an update to it.  That user's update has already
+taken effect.  Your update cannot be saved because your changes could
+overwrite the other user's changes.</p>
+
+<p>Note: if you have been viewing and updating [artifact_name] in multiple
+browser windows or tabs, it is possible that the "other user" is
+actually yourself.</p>
+
+
+<div style="margin:2em" class="help">
+  <b style="margin:0.5em">Your options:</b>
+
+  <ul>
+   <li>Start over: view the up-to-date
+   <a href="[artifact_detail_url]">[artifact_name]</a>
+   and consider making your changes again.</li>
+  </ul>
+
+</div>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/framework/artifact-list-admin-part.ezt b/templates/framework/artifact-list-admin-part.ezt
new file mode 100644
index 0000000..e24a236
--- /dev/null
+++ b/templates/framework/artifact-list-admin-part.ezt
@@ -0,0 +1,129 @@
+[# If any value is supplied for arg0, the user will also be able
+   to edit grid preferences.]
+<h4>[if-any arg0]List and grid preferences[else]List preferences[end]</h4>
+<div class="section">
+
+ <div class="closed">
+  <div>Default query for project members:
+   <a class="ifClosed toggleHidden" href="#"
+      style="font-size:90%; margin-left:.5em">Learn more</a>
+  </div>
+
+  <div id="colhelp" class="ifOpened help">
+      <div>
+       You may enter a default query for project members.  They will run
+       this query when they click on the "Issues" tab.
+      </div>
+  </div>
+  <br>
+ </div>
+
+ <input type="text" size="75" name="member_default_query"
+        value="[config.member_default_query]" id="searchq"
+        [if-any perms.EditProject][else]readonly="readonly"[end]
+        class="acob" style="margin-left:.7em">
+ <br>
+ <br>
+ <br>
+
+ <div class="closed">
+  <div>Default columns shown in list view:
+   <a class="ifClosed toggleHidden" href="#"
+      style="font-size:90%; margin-left:.5em">Learn more</a>
+  </div>
+
+  <div id="colhelp" class="ifOpened help">
+      <div>
+       You may enter a series of column names separated by spaces.  The
+       columns will be displayed in order on the list view page.
+      </div>
+      <br>
+      <div>
+       Columns may be the names of built-in attributes, e.g., "Summary"
+       or "Stars". Columns may also be prefixes of the labels on items.
+       To experiment with label prefixes, label some items with
+       Key-Value labels, then click the "..." menu in the far upper right
+       heading of the list view.
+      </div>
+  </div>
+  <br>
+ </div>
+
+ <input type="text" size="75" name="default_col_spec" value="[config.default_col_spec]"
+        [if-any perms.EditProject][else]readonly="readonly"[end]
+        class="acob" style="margin-left:.7em">
+ <br>
+ <br>
+ <br>
+
+ <div class="closed">
+  <div>Default sorting order:
+   <a class="ifClosed toggleHidden" href="#"
+      style="font-size:90%; margin-left:.7em">Learn more</a>
+  </div>
+
+  <div class="ifOpened help">
+      <div>
+       You may enter a series of column names separated by spaces.  Items
+       will be sorted by the first column specified.  If two items have
+       the same value in the first column, the items' values in the second
+       column will be used to break the tie, and so on. Use a leading
+       minus-sign to reverse the sort order within a column.
+      </div>
+      <br>
+      <div>
+       To experiment with column sorting, click the list view header cells and
+       choose "Sort up" or "Sort down". The sorting specification used becomes
+       part of the page URL.
+      </div>
+  </div>
+  <br>
+ </div>
+
+ <input type="text" size="75" name="default_sort_spec" value="[config.default_sort_spec]"
+        [if-any perms.EditProject][else]readonly="readonly"[end]
+        class="acob" style="margin-left:.7em">
+
+
+ [if-any arg0]
+ <br>
+ <br>
+ <br>
+
+ <div class="closed">
+  <div>Default grid axes:
+   <a class="ifClosed toggleHidden" href="#"
+      style="font-size:90%; margin-left:.7em">Learn more</a>
+  </div>
+
+  <div class="ifOpened help">
+      <div>
+       You may enter one attribute name for the default grid rows and one for
+       the default grid columns.  For example, "milestone" and "priority".  Or,
+       you may leave each field blank.
+      </div>
+      <br>
+      <div>
+       To experiment with grid axes, click the "grid" link in the list view and
+       use the drop-down menus to select row and column attributes.
+      </div>
+  </div>
+  <br>
+ </div>
+
+ <span style="margin-left:.7em">
+   Rows: <input type="text" size="10" name="default_y_attr" value="[config.default_y_attr]"
+                [if-any perms.EditProject][else]readonly="readonly"[end]
+                class="acob">
+ </span>
+
+ <span style="margin-left:.7em">
+   Columns: <input type="text" size="10" name="default_x_attr" value="[config.default_x_attr]"
+                   [if-any perms.EditProject][else]readonly="readonly"[end]
+                   class="acob">
+ </span>
+
+ [end]
+
+</div>
+
diff --git a/templates/framework/artifact-list-pagination-part.ezt b/templates/framework/artifact-list-pagination-part.ezt
new file mode 100644
index 0000000..8896c14
--- /dev/null
+++ b/templates/framework/artifact-list-pagination-part.ezt
@@ -0,0 +1,18 @@
+[if-any pagination]
+ [if-any pagination.visible]
+  <div class="pagination">
+   [if-any pagination.prev_url]
+     <a href="[pagination.prev_url]"><b>&lsaquo;</b> Prev</a>
+   [end]
+   [if-any pagination.start]
+     [pagination.start] - [pagination.last]
+   [end]
+   [if-any pagination.total_count]
+     of [pagination.total_count][if-any pagination.limit_reached]+[end]
+   [end]
+   [if-any pagination.next_url]
+     <a href="[pagination.next_url]">Next <b>&rsaquo;</b></a>
+   [end]
+  </div>
+ [end]
+[end]
diff --git a/templates/framework/banned-page.ezt b/templates/framework/banned-page.ezt
new file mode 100644
index 0000000..4b3effb
--- /dev/null
+++ b/templates/framework/banned-page.ezt
@@ -0,0 +1,31 @@
+[include "../framework/header.ezt" "hidetabs"]
+
+<h3>Access Not Allowed</h3>
+
+<h4>What happened?</h4>
+
+<p>
+[if-any is_plus_address]
+  We do not accept accounts with "+" in the email address.
+[else]
+  You are not allowed to access this service.
+[end]
+</p>
+
+<p>Please <a href="mailto:[feedback_email]">contact us</a> if you believe that you should be able to access this service. (This is a Google Group; what you write will be visible on the Internet.)</p>
+
+[# Note: we do not show the reason for being banned. ]
+
+
+<div style="margin:2em" class="help">
+  <b style="margin:0.5em">Your options:</b>
+
+  <ul>
+   <li>Participate in the open source community through other websites.</li>
+   <li><a href="[logout_url_goto_home]">Sign out</a> and access this site as
+     an anonymous user.</li>
+   <li><a href="mailto:[feedback_email]">Contact us</a> for further assistance.</li>
+  </ul>
+</div>
+
+[include "../framework/footer.ezt"]
diff --git a/templates/framework/banner_message.ezt b/templates/framework/banner_message.ezt
new file mode 100644
index 0000000..fc31310
--- /dev/null
+++ b/templates/framework/banner_message.ezt
@@ -0,0 +1,8 @@
+[if-any site_banner_message]
+ <div style="font-weight:bold; color:var(--chops-field-error-color); padding:5px; margin-top:10px; text-align:center; background:var(--chops-orange-50);">
+  [site_banner_message]
+  [if-any banner_time]
+    <chops-timestamp timestamp="[banner_time]"></chops-timestamp>
+  [end]
+ </div>
+[end]
diff --git a/templates/framework/comment-pagination-part.ezt b/templates/framework/comment-pagination-part.ezt
new file mode 100644
index 0000000..6867c57
--- /dev/null
+++ b/templates/framework/comment-pagination-part.ezt
@@ -0,0 +1,8 @@
+[if-any cmnt_pagination.prev_url]
+  <a href="[cmnt_pagination.prev_url]" style="margin-right:.7em"><b>&lsaquo;</b> Newer</a>
+[end]
+Showing comments [cmnt_pagination.last] - [cmnt_pagination.start]
+[if-any cmnt_pagination.total_count]of [cmnt_pagination.total_count][end]
+[if-any cmnt_pagination.next_url]
+  <a href="[cmnt_pagination.next_url]" style="margin-left:.7em">Older <b>&rsaquo;</b></a>
+[end]
diff --git a/templates/framework/component-validation-row.ezt b/templates/framework/component-validation-row.ezt
new file mode 100644
index 0000000..5d37b84
--- /dev/null
+++ b/templates/framework/component-validation-row.ezt
@@ -0,0 +1,5 @@
+<tr>
+  <td colspan="3">
+    <div id="component_blocksubmitarea" class="blockingsubmit"><span id="component_blocksubmitmsg"></span></div>
+  </td>
+</tr>
diff --git a/templates/framework/database-maintenance.ezt b/templates/framework/database-maintenance.ezt
new file mode 100644
index 0000000..fe8d7b4
--- /dev/null
+++ b/templates/framework/database-maintenance.ezt
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <link rel="icon" type="image/vnd.microsoft.icon" href="/static/images/monorail.ico">
+  <title>This bug tracker is unavailable due to database issues.</title>
+  <meta name="ROBOTS" content="NOARCHIVE">
+  <link type="text/css" rel="stylesheet" href="/static/css/ph_core.css">
+</head>
+<body>
+  <h2>This bug tracker is currently unavailable due to database issues.</h2>
+  Please <a href="[requested_url]">try again</a> later.
+</body>
+</html>
diff --git a/templates/framework/debug.ezt b/templates/framework/debug.ezt
new file mode 100644
index 0000000..7903de5
--- /dev/null
+++ b/templates/framework/debug.ezt
@@ -0,0 +1,50 @@
+[is dbg "off"]
+ [if-any perms._ViewDebug]
+   <div class="debug">
+    - <a href="[debug_uri]">Reload w/ debug info</a>
+   </div>
+ [end]
+[else]
+   [# Note that this only handles the top two levels of (sub)phases.
+    # If you nest phases further than that (which we haven't wanted/needed to
+    # do so far), you'll have to modify this code in order to render it.]
+   <style type="text/css">
+    .debug, .debug a { color: #444; font-size: x-small}
+    .debug td, .debug th { background: #ddf}
+    .debug th { text-align: left; font-family: courier; font-size: small}
+   </style>
+
+   <div class="debug">Profile Data
+     <table class="ifOpened" cellpadding="2" cellspacing="2" border="0"  style="padding-left: 1em">
+       [for profiler.top_phase.subphases]
+        <tr>
+         <th style="white-space:nowrap">[profiler.top_phase.subphases.name]:</th>
+         <td align="right">[profiler.top_phase.subphases.ms][is profiler.top_phase.subphases.ms "in_progress"][else] ms[end]</td>
+         <td><table cellspacing="1" cellpadding="0"><tr>
+         [for profiler.top_phase.subphases.subphases]
+          <td title="[profiler.top_phase.subphases.subphases.name]: [profiler.top_phase.subphases.subphases.ms]ms"
+            width="[is profiler.top_phase.subphases.subphases.ms "in_progress"]100%[else][profiler.top_phase.subphases.subphases.ms][end]"
+            style="padding:2px;color:#fff;background:#[profiler.top_phase.subphases.subphases.color]">[profiler.top_phase.subphases.subphases.ms]</td>
+         [end]
+
+         [if-any profiler.top_phase.subphases.uncategorized_ms]
+           <td title="uncategorized: [profiler.top_phase.subphases.uncategorized_ms]ms"
+              width="[profiler.top_phase.subphases.uncategorized_ms]"
+              style="padding:1px">[profiler.top_phase.subphases.uncategorized_ms]</td>
+         [end]
+        </tr></table>
+         </td>
+        </tr>
+       [end]
+     </table>
+   </div><br>
+ [for debug]
+   <div class="debug">[debug.title]
+     <table cellpadding="2" cellspacing="2" border="0" style="padding-left: 1em">
+      [for debug.collection]
+       <tr><th>[debug.collection.key]</th><td>[debug.collection.val]</td></tr>
+      [end]
+     </table>
+   </div><br>
+ [end]
+[end]
diff --git a/templates/framework/display-project-logo.ezt b/templates/framework/display-project-logo.ezt
new file mode 100644
index 0000000..fd787c6
--- /dev/null
+++ b/templates/framework/display-project-logo.ezt
@@ -0,0 +1,29 @@
+[# This template displays the project logo with the file name and a View link.
+
+   arg0: Whether to display a checkbox to delete the logo.
+]
+
+<table cellspacing="5" cellpadding="2" border="0">
+  <tr>
+    <td>
+      <b>[logo_view.filename]</b>
+    </td>
+    <td>
+      <a href="[logo_view.viewurl]" target="_blank" style="margin-left:.2em">View</a>
+    </td>
+  </tr>
+  <tr>
+    <td colspan=2>
+      <a href="[logo_view.viewurl]" target="_blank">
+        <img src="[logo_view.thumbnail_url]" class="preview">
+      </a>
+    </td>
+  </tr>
+  [if-any arg0]
+    <tr>
+      <td colspan=2>
+        <input type="checkbox" name="delete_logo" id="delete_logo"> Delete this logo
+      </td>
+    </tr>
+  [end]
+</table>
diff --git a/templates/framework/excessive-activity-page.ezt b/templates/framework/excessive-activity-page.ezt
new file mode 100644
index 0000000..e7b10ad
--- /dev/null
+++ b/templates/framework/excessive-activity-page.ezt
@@ -0,0 +1,31 @@
+[include "../framework/header.ezt" "hidetabs"]
+
+<h3>Action Limit Exceeded</h3>
+
+<h4>What happened?</h4>
+
+<div style="width:60em">
+
+<p>You have performed the requested action too many times in a 24-hour
+time period.  Or, you have performed the requested action too many
+times since the creation of your account.</p>
+
+<p>We place limits on the number of actions that can be performed by
+each user in order to reduce the potential for abuse.  We feel that we have set
+these limits high enough that legitimate use will very rarely
+reach them.  Without these limits, a few abusive users could degrade
+the quality of this site for everyone.</p>
+
+
+<div style="margin:2em" class="help">
+  <b style="margin:0.5em">Your options:</b>
+
+  <ul>
+   <li>Wait 24 hours and then try this action again.</li>
+   <li>Ask another member of your project to perform the action for you.</li>
+   <li><a href="mailto:[feedback_email]">Contact us</a> for further assistance.</li>
+  </ul>
+</div>
+
+</div>
+[include "../framework/footer.ezt"]
diff --git a/templates/framework/file-content-js.ezt b/templates/framework/file-content-js.ezt
new file mode 100644
index 0000000..72e882e
--- /dev/null
+++ b/templates/framework/file-content-js.ezt
@@ -0,0 +1,89 @@
+[# TODO(jrobbins): move this into compiled javascript. ]
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+  var numsGenState = {table_base_id: 'nums_table_'};
+  var srcGenState = {table_base_id: 'src_table_'};
+  var alignerRunning = false;
+  var startOver = false;
+
+  function setLineNumberHeights() {
+    if (alignerRunning) {
+      startOver = true;
+      return;
+    }
+    numsGenState.chunk_id = 0;
+    numsGenState.table = document.getElementById('nums_table_0');
+    numsGenState.row_num = 0;
+
+    if (!numsGenState.table) {
+      return;  // Silently exit if no file is present.
+    }
+
+    srcGenState.chunk_id = 0;
+    srcGenState.table = document.getElementById('src_table_0');
+    srcGenState.row_num = 0;
+
+    alignerRunning = true;
+    continueToSetLineNumberHeights();
+  }
+
+  function rowGenerator(genState) {
+    if (genState.row_num < genState.table.rows.length) {
+      var currentRow = genState.table.rows[[]genState.row_num];
+      genState.row_num++;
+      return currentRow;
+    }
+
+    var newTable = document.getElementById(
+        genState.table_base_id + (genState.chunk_id + 1));
+    if (newTable) {
+      genState.chunk_id++;
+      genState.row_num = 0;
+      genState.table = newTable;
+      return genState.table.rows[[]0];
+    }
+
+    return null;
+  }
+
+  var MAX_ROWS_PER_PASS = 1000;
+
+  function continueToSetLineNumberHeights() {
+    var rowsInThisPass = 0;
+    var numRow = 1;
+    var srcRow = 1;
+
+    while (numRow && srcRow && rowsInThisPass < MAX_ROWS_PER_PASS) {
+      numRow = rowGenerator(numsGenState);
+      srcRow = rowGenerator(srcGenState);
+      rowsInThisPass++;
+
+      if (numRow && srcRow) {
+        if (numRow.offsetHeight != srcRow.offsetHeight) {
+          numRow.firstChild.style.height = srcRow.offsetHeight + 'px';
+        }
+      }
+    }
+
+    if (rowsInThisPass >= MAX_ROWS_PER_PASS) {
+      setTimeout(continueToSetLineNumberHeights, 10);
+    } else {
+      alignerRunning = false;
+      if (startOver) {
+        startOver = false;
+        setTimeout(setLineNumberHeights, 500);
+      }
+    }
+
+  }
+
+  function initLineNumberHeights() {
+    // Do 2 complete passes, because there can be races
+    // between this code and prettify.
+    startOver = true;
+    setTimeout(setLineNumberHeights, 250);
+    window.addEventListener('resize', setLineNumberHeights);
+  }
+  initLineNumberHeights();
+});
+</script>
diff --git a/templates/framework/file-content-part.ezt b/templates/framework/file-content-part.ezt
new file mode 100644
index 0000000..5a89915
--- /dev/null
+++ b/templates/framework/file-content-part.ezt
@@ -0,0 +1,46 @@
+[# Safely display user-content text, such a program source code, with
+   line numbers.
+
+Other EZT variables used:
+  file_lines: List of lines in the file, each with a line number and content.
+  should_prettify: whether the text should be syntax highlighted.
+  prettify_class: additional CSS class used to tell prettify.js how to
+      best syntax highlight this source file.
+]
+
+[# Display the line numbers and source lines in separate columns.
+   See corresponding comments L1, L2, L3 and S1, S2, S3 below.
+   This is messy because the pre tags have significant whitespace, so we
+   break lines inside the tags themslves to make our templates readable.]
+<table class="opened"><tr>
+<td id="nums">
+[# L1. Start with a nocursor row at the top to space the line numbers down the
+       same amount as the source code lines w/ their initial cursor_hidden row.]
+<pre><table width="100%"><tr class="nocursor"><td></td></tr></table></pre>
+
+[# L2. Display each line number in a row that we can refer
+       to by ID, and make each line number a self-link w/ anchor.]
+<pre><table width="100%" id="nums_table_0">[for file_lines]<tr id="gr_[file_lines.num]"
+><td id="[file_lines.num]"><a href="#[file_lines.num]">[file_lines.num]</a></td></tr
+>[end]</table></pre>
+
+[# L3. Finish the line numbers column with another nocursor row to match
+       the spacing of the source code column's final cursor_hidden row.]
+<pre><table width="100%"><tr class="nocursor"><td></td></tr></table></pre>
+</td>
+<td id="lines">
+
+[# S1. Start the source code column with a cursor row. ]
+<pre><table width="100%"><tr class="cursor_stop cursor_hidden"><td></td></tr></table></pre>
+
+[# S2. Display each source code line in a table row and cell
+       that we can identify by id.]
+<pre [if-any should_prettify]class="prettyprint [prettify_class]"[end]><table id="src_table_0">[for file_lines]<tr
+id=sl_[file_lines.num]
+><td class="source">[file_lines.line]<br></td></tr
+>[end]</table></pre>
+
+[# S3. Finish the line numbers column with another cursor stop.]
+<pre><table width="100%"><tr class="cursor_stop cursor_hidden"><td></td></tr></table></pre>
+</td>
+</tr></table>
diff --git a/templates/framework/filter-rule-admin-part.ezt b/templates/framework/filter-rule-admin-part.ezt
new file mode 100644
index 0000000..4822265
--- /dev/null
+++ b/templates/framework/filter-rule-admin-part.ezt
@@ -0,0 +1,155 @@
+<style>
+  #rules th, #rules td {  padding-bottom: 1em }
+</style>
+
+[# If any value is supplied for arg0, the user will be able to set actions
+   that set default owner, set default status, and add CC users.]
+<h4 id="filters">Filter rules</h4>
+<div class="section">
+
+ <div class="closed">
+  <div>Filter rules can help you fill in defaults and stay organized.
+   <a class="ifClosed toggleHidden" href="#"
+      style="font-size:90%; margin-left:.5em">Learn more</a>
+  </div>
+
+  <div id="filterhelp" class="ifOpened help">
+       Filter rules can help your team triage issues by automatically
+       filling in default values based on other values.  They can be used
+       in the same way that you might use message filters in an email client.
+       Filter rules are evaluated after each edit, not just on new items. And,
+       filter rules only add values or set default values, they never override
+       values that were explicitly set by a user.<br>
+       <br>
+       Note that exclusive prefixes still apply.  So, if a user has set a label
+       with one of the exclusive prefixes, a rule that adds another label with
+       the same prefix will have no effect.
+  </div>
+  <br>
+
+  <table border="0" id="rules">
+   <tr>
+    <th></th>
+    <th style="text-align:left">If the issue matches this query:</th>
+    <th colspan="2" style="text-align:left">Then, [if-any arg0]do the following[else]add these labels[end]:</th>
+    <th></th>
+   </tr>
+
+   [for rules]
+   <tr>
+    <td style="text-align:right" width="20">[rules.idx].</td>
+    <td><input type="text" name="predicate[rules.idx]" size="60" value="[rules.predicate]"
+               autocomplete="off" id="predicate_existing_[rules.idx]" class="acob"></td>
+    <td>
+      [if-any arg0]
+       <select name="action_type[rules.idx]">
+         <option value="" disabled="disabled" [is rules.action_type ""]selected="selected"[end]>Choose...</option>
+         <option value="default_status" [is rules.action_type "default_status"]selected="selected"[end]>Set default status:</option>
+         <option value="default_owner" [is rules.action_type "default_owner"]selected="selected"[end]>Set default owner:</option>
+         <option value="add_ccs" [is rules.action_type "add_ccs"]selected="selected"[end]>Add Cc:</option>
+         <option value="add_labels" [is rules.action_type "add_labels"]selected="selected"[end]>Add labels:</option>
+         <option value="also_notify" [is rules.action_type "also_notify"]selected="selected"[end]>Also notify email:</option>
+         <option value="warning" [is rules.action_type "warning"]selected="selected"[end]>Show warning:</option>
+       </select>
+      [end]
+    </td>
+    <td>
+      <input type="text" name="action_value[rules.idx]" size="70" value="[rules.action_value]" class="acob">
+    </td>
+    <td></td>
+   </tr>
+   [end]
+
+   [for new_rule_indexes]
+   <tr id="newrow[new_rule_indexes]" [if-index new_rule_indexes first][else]style="display:none"[end]>
+    <td style="text-align:right" width="20">[new_rule_indexes].</td>
+    <td><input type="text" name="new_predicate[new_rule_indexes]" size="60" value=""
+               class="showNextRuleRow acob" data-index="[new_rule_indexes]"
+               autocomplete="off" id="predicate_new_[new_rule_indexes]"></td>
+    <td>
+      [if-any arg0]
+       <select name="new_action_type[new_rule_indexes]">
+         <option value="" disabled="disabled" selected="selected">Choose...</option>
+         <option value="default_status">Set default status:</option>
+         <option value="default_owner">Set default owner:</option>
+         <option value="add_ccs">Add Cc:</option>
+         <option value="add_labels">Add labels:</option>
+         <option value="also_notify">Also notify email:</option>
+         <option value="warning">Show warning:</option>
+       </select>
+      [end]
+    </td>
+    <td>
+      <input type="text" name="new_action_value[new_rule_indexes]" size="70" value="" class="acob">
+      [# TODO(jrobbins): figure out a way to display error messages on each rule. ]
+    </td>
+    <td width="40px">
+     [if-index new_rule_indexes last][else]
+      <span id="addrow[new_rule_indexes]" class="fakelink" class="fakelink" data-index="[new_rule_indexes]">Add a row</span
+     [end]
+    </td>
+   </tr>
+   [end]
+
+  </table>
+ </div>
+
+ [if-any errors.rules]
+  [for errors.rules]
+    <div class="fielderror">[errors.rules]</div>
+  [end]
+  <script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+   document.location.hash = 'filters';
+});
+  </script>
+ [end]
+
+</div>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+  function showNextRuleRow(i) {
+   if (i < [max_rules]) {
+     _showID('newrow' + (i + 1));
+     _hideID('addrow' + i);
+   }
+  }
+
+  var addARowLinks = document.getElementsByClassName("fakelink");
+  for (var i = 0; i < addARowLinks.length; ++i) {
+    var link = addARowLinks[[]i];
+    link.addEventListener("click", function(event) {
+        var index = Number(event.target.getAttribute("data-index"));
+        showNextRuleRow(index);
+    });
+  }
+
+  var typeToAddARow = document.getElementsByClassName("showNextRuleRow");
+  for (var i = 0; i < typeToAddARow.length; ++i) {
+    var el = typeToAddARow[[]i];
+    el.addEventListener("keydown", function(event) {
+        var index = Number(event.target.getAttribute("data-index"));
+        showNextRuleRow(index);
+    });
+  }
+
+  var acobElements = document.getElementsByClassName("acob");
+  for (var i = 0; i < acobElements.length; ++i) {
+     var el = acobElements[[]i];
+     el.addEventListener("focus", function(event) {
+         _acrob(null);
+         _acof(event);
+     });
+  }
+
+  var acobElements = document.getElementsByClassName("acob");
+  for (var i = 0; i < acobElements.length; ++i) {
+     var el = acobElements[[]i];
+     el.addEventListener("focus", function(event) {
+         _acrob(null);
+         _acof(event);
+     });
+  }
+});
+</script>
diff --git a/templates/framework/footer-script.ezt b/templates/framework/footer-script.ezt
new file mode 100644
index 0000000..5e338ea
--- /dev/null
+++ b/templates/framework/footer-script.ezt
@@ -0,0 +1,34 @@
+[# The order of imports matters in this file. Scripts are imported after other scripts they depend on.]
+
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/common.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/listen.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/xmlhttp.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/shapes.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/geom.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/graveyard/popup_controller.js" nonce="[nonce]"></script>
+
+[if-any is_ezt]
+  [# Note that this file will be requested twice on some pages, but chrome is smart enough
+    to not even request it the second time.]
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-ajax.js" nonce="[nonce]"></script>
+
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/externs.js" nonce="[nonce]"></script>
+[end]
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/ac.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-ac.js" nonce="[nonce]"></script>
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-install-ac.js" nonce="[nonce]"></script>
+[if-any is_ezt]
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-components.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-dd.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-display.js" nonce="[nonce]"></script>
+[end]
+<script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-editing.js" nonce="[nonce]"></script>
+[if-any is_ezt]
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-fields.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-keystrokes.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-nav.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-update-issues-hotlists.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-util.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/tracker/tracker-onload.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/clientmon.js" nonce="[nonce]"></script>
+[end]
diff --git a/templates/framework/footer-shared.ezt b/templates/framework/footer-shared.ezt
new file mode 100644
index 0000000..9630e4c
--- /dev/null
+++ b/templates/framework/footer-shared.ezt
@@ -0,0 +1,107 @@
+[# This template displays the part of the footer used by both web components and EZT pages. ]
+
+<div id="footer">
+  [if-any old_ui_url]
+    <a href="[old_ui_url]">
+      View in the old UI
+    </a>
+  [else][if-any new_ui_url]
+    <a href="[new_ui_url]">
+      View in the new UI
+    </a>
+  [end][end]
+  [is projectname "fuchsia"]
+    <a href="https://bugs.fuchsia.dev/p/fuchsia/issues/entry?template=Report+Community+Abuse" title="Monorail [app_version]">Report Abuse</a>
+  [end]
+  <a href="https://bugs.chromium.org/p/monorail/adminIntro" title="Monorail [app_version]">About Monorail</a>
+  <a href="https://chromium.googlesource.com/infra/infra/+/main/appengine/monorail/doc/userguide/README.md">User Guide</a>
+  <a href="https://chromium.googlesource.com/infra/infra/+/main/appengine/monorail/doc/release-notes.md">Release Notes</a>
+  <a href="https://bugs.chromium.org/p/monorail/issues/entry?template=Online%20Feedback" target="_blank">Feedback on Monorail</a>
+  <a href="https://chromium.googlesource.com/infra/infra/+/main/appengine/monorail/doc/terms.md">Terms</a>
+  <a href="https://www.google.com/policies/privacy/">Privacy</a>
+</div>
+
+[include "debug.ezt"]
+
+[include "../webpack-out/ezt-footer-scripts-package.ezt"]
+
+<script type="module" nonce="[nonce]">
+// Load and instantiate pRPC client before any other script.
+window.prpcClient = new AutoRefreshPrpcClient(
+  CS_env.token, CS_env.tokenExpiresSec);
+</script>
+
+[if-any is_ezt]
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/externs.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/env.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-ajax.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-cues.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-display.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-menu.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-myhotlists.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/framework-stars.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/framework/project-name-check.js" nonce="[nonce]"></script>
+  <script type="text/javascript" defer src="[version_base]/static/js/graveyard/xmlhttp.js" nonce="[nonce]"></script>
+[end]
+[include "footer-script.ezt"]
+
+
+[if-any is_ezt]
+  <script type="text/javascript" nonce="[nonce]">
+  runOnLoad(function() {
+    var toggles = document.getElementsByClassName("toggleHidden");
+    for (var i = 0; i < toggles.length; ++i) {
+      var toggle = toggles[[]i];
+      toggle.addEventListener("click", function (event) {
+          _toggleHidden(event.target);
+          event.preventDefault();
+      });
+    }
+
+    toggles = document.getElementsByClassName("toggleCollapse");
+    for (var i = 0; i < toggles.length; ++i) {
+      var toggle = toggles[[]i];
+      toggle.addEventListener("click", function (event) {
+          _toggleCollapse(event.target);
+          event.preventDefault();
+      });
+    }
+
+    [if-any form_token]
+      var tokenFields = document.querySelectorAll("input[[]name=token]");
+      for (var i = 0; i < tokenFields.length; ++i) {
+        var field = tokenFields[[]i];
+        field.form.addEventListener("submit", function(event) {
+            refreshTokens(
+                event, "[form_token]", "[form_token_path]", [token_expires_sec]);
+        });
+      }
+    [end]
+
+    [if-any project]
+      _fetchUserProjects(false);
+    [end]
+    _onload();
+
+  });
+  </script>
+[else]
+  <script type="text/javascript" nonce="[nonce]">
+  runOnLoad(function() {
+    TKR_install_ac();
+  });
+  </script>
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+  // CrDX Feedback Button
+  (function(i,s,o,g,r,a,m){i[[]'CrDXObject']=r;i[[]r]=i[[]r]||function(){
+  (i[[]r].q=i[[]r].q||[]).push(arguments)},a=s.createElement(o),
+  m=s.getElementsByTagName(o)[0];a.async=1;a.setAttribute('nonce','[nonce]');
+  a.src=g;m.parentNode.insertBefore(a,m)
+  })(window,document,'script','https://storage.googleapis.com/chops-feedback/feedback.js','crdx');
+
+  crdx('setFeedbackButtonLink', 'https://bugs.chromium.org/p/monorail/issues/entry?template=Online%20Feedback');
+});
+</script>
diff --git a/templates/framework/footer.ezt b/templates/framework/footer.ezt
new file mode 100644
index 0000000..963b61d
--- /dev/null
+++ b/templates/framework/footer.ezt
@@ -0,0 +1,42 @@
+</div> [# End <div id="maincol"> from header.ezt]
+
+[include "footer-shared.ezt"]
+
+<script type="text/javascript" nonce="[nonce]">
+// Google Analytics
+(function(i,s,o,g,r,a,m){i[[]'GoogleAnalyticsObject']=r;i[[]r]=i[[]r]||function(){
+(i[[]r].q=i[[]r].q||[[]]).push(arguments)},i[[]r].l=1*new Date();a=s.createElement(o),
+m=s.getElementsByTagName(o)[[]0];a.async=1;a.setAttribute('nonce','[nonce]');
+a.src=g;m.parentNode.insertBefore(a,m)
+})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+(function setupGoogleAnalytics() {
+  const _EMAIL_REGEX =
+      ["/([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})/"];
+
+  ga('create', '[analytics_id]', {'siteSpeedSampleRate': 100});
+
+  [if-any logged_in_user]
+    ga('set', 'dimension1', 'Logged in');
+  [else]
+    ga('set', 'dimension1', 'Not logged in');
+  [end]
+
+  const path = window.location.href.slice(window.location.origin.length);
+  if (path.startsWith('/u')) {
+    [# Keep anything that looks like an email address out of GA.]
+    ga('set', 'title', 'A user page');
+    ga('set', 'location', path.replace(_EMAIL_REGEX, 'user@example.com'));
+  }
+
+  ga('send', 'pageview');
+})();
+</script>
+
+<ezt-app-base [if-any logged_in_user]
+  userDisplayName="[logged_in_user.email]"[end]
+  projectName="[projectname]"
+></ezt-app-base>
+
+</body>
+</html>
diff --git a/templates/framework/group-setting-fields.ezt b/templates/framework/group-setting-fields.ezt
new file mode 100644
index 0000000..4d62ab2
--- /dev/null
+++ b/templates/framework/group-setting-fields.ezt
@@ -0,0 +1,95 @@
+[# Diplay a widget to choose group visibility level, or read-only text showing
+   the visibility level.  Read-only text is used when the user does not have
+   permission to edit, or if there is only one available choice.
+]
+
+[define vis_menu_was_shown]False[end]
+
+[if-any read_only][else]
+  <select name="visibility" id="visibility" [if-any import_group]disabled="disabled"[end]>
+    <option value="" disabled="disabled" [if-any initial_visibility][else]selected="selected"[end]>
+      Select a visibility level...
+    </option>
+    [for visibility_levels]
+      <option value="[visibility_levels.key]"
+        [if-any initial_visibility]
+          [is initial_visibility.key visibility_levels.key]selected="selected"[end]
+        [end]>
+        [visibility_levels.name]
+      </option>
+    [end]
+  </select>
+  [define vis_menu_was_shown]True[end]
+
+  <br><br>
+  Friend projects: <br>
+  <input size="60" type="text" id="friendprojects" name="friendprojects" value="[initial_friendprojects]">
+  <div class="fielderror">
+    <span id="friendprojectsfeedback"></span>
+    [if-any errors.friendprojects][errors.friendprojects][end]
+  </div>
+
+  <br><br>
+  <input type="checkbox" name="import_group" id="import_group"
+         [if-any import_group]checked="checked"[end]
+         [if-any groupadmin]disabled="disabled"[end] >
+  <label for="import_group">Import from external group</label>
+
+  <div class="fielderror">
+    <span id="groupimportfeedback"></span>
+    [if-any errors.groupimport][errors.groupimport][end]
+  </div>
+
+  <br>
+  &nbsp;&nbsp;External group type:
+  <select name="group_type" id="group_type"
+          [if-any import_group][else]disabled="disabled"[end]
+          [if-any groupadmin]disabled="disabled"[end] >
+    <option value="" disabled="disabled" [if-any initial_group_type][else]selected="selected"[end]>
+      Select a group type...
+    </option>
+    [for group_types]
+      <option value="[group_types.key]"
+        [if-any initial_group_type]
+          [is initial_group_type.key group_types.key]selected="selected"[end]
+        [end]>
+        [group_types.name]
+      </option>
+    [end]
+  </select>
+  <br><br>
+
+  <script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+    cur_vis_value = $("visibility").value;
+
+    function _updateSettings() {
+      if ($("import_group").checked) {
+        $("group_type").disabled = false;
+        cur_vis_value = $("visibility").value;
+        $("visibility").value = 0;
+        $("visibility").disabled = true;
+        $("friendprojects").disabled = true;
+      } else {
+        $("group_type").disabled = true;
+        $("visibility").value = cur_vis_value;
+        $("visibility").disabled = false;
+        $("friendprojects").disabled = false;
+      }
+    }
+
+    $("import_group").addEventListener("click", _updateSettings);
+});
+  </script>
+[end]
+
+[is vis_menu_was_shown "False"]
+  [initial_visibility.name]
+  <input type="hidden" name="visibility" value="[initial_visibility.key]">
+[end]
+
+<div class="formerror">
+  [if-any errors.access]
+    <div class="emphasis">[errors.access]</div>
+  [end]
+</div>
\ No newline at end of file
diff --git a/templates/framework/header-shared.ezt b/templates/framework/header-shared.ezt
new file mode 100644
index 0000000..614fe5c
--- /dev/null
+++ b/templates/framework/header-shared.ezt
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+[# This is the part of header.ezt used by both the legacy
+   Monorail UI, and the new Polymer pages.
+]
+<html lang="en">
+<head>
+  <link rel="icon" type="image/vnd.microsoft.icon" href="/static/images/monorail.ico">
+  [if-any link_rel_canonical]
+    <link rel="canonical" href="[link_rel_canonical]">
+  [end]
+
+  <script type="text/javascript" nonce="[nonce]">
+   [# Javascript object containing basic page data. ]
+   window.CS_env = {
+     'absoluteBaseUrl': '[format "js"][absolute_base_url][end]',
+     'app_version': '[format "js"][app_version][end]',
+     'token': '[format "js"][xhr_token][end]',
+     'tokenExpiresSec': [format "js"][token_expires_sec][end],
+     'loggedInUserEmail':
+       [if-any logged_in_user]
+         '[format "js"][logged_in_user.email][end]'
+       [else]
+         null
+       [end],
+     'login_url': '[format "js"][login_url][end]',
+     'logout_url': '[format "js"][logout_url][end]',
+     'profileUrl':
+       [if-any logged_in_user]
+         '[format "js"][logged_in_user.profile_url][end]'
+       [else]
+         null
+       [end],
+     'projectName': '[format "js"][projectname][end]',
+     'projectIsRestricted': [if-any project_is_restricted]true[else]false[end],
+     'is_member': '[format "js"][is_member][end]',
+     'gapi_client_id': '[format "js"][gapi_client_id][end]',
+   };
+  </script>
+
+  [# Improve the snippet that appears in search]
+  [if-any show_search_metadata]
+    <meta name="Description" content="Monorail is simple, reliable, and flexible issue tracking tool.">
+    <meta name="robots" content="NOODP">
+  [end]
+
+    <title>
+      [if-any title][title] - [end]
+      [if-any title_summary][title_summary] - [end]
+      [if-any projectname]
+        [projectname] -
+      [else]
+        [if-any viewing_user_page][viewed_user.display_name] - [end]
+      [end]
+      [if-any title_summary][else]
+        [if-any project_summary][project_summary] - [end]
+      [end]
+      [site_name]
+    </title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="referrer" content="no-referrer">
+    [if-any robots_no_index]
+     <meta name="ROBOTS" content="NOINDEX,NOARCHIVE">
+    [else]
+     <meta name="ROBOTS" content="NOARCHIVE">
+    [end]
+    <meta name="viewport" content="width=device-width, minimum-scale=1.0">
+    <link type="text/css" rel="stylesheet" href="[version_base]/static/css/chopsui-normal.css">
+
+    [if-any is_ezt]
+      <link type="text/css" rel="stylesheet" href="[version_base]/static/css/ph_core.css">
+      <link type="text/css" rel="stylesheet" media="(max-width:425px)"
+            href="[version_base]/static/css/ph_mobile.css">
+
+      [if-any category_css]
+        <link type="text/css" rel="stylesheet" href="[version_base]/static/[category_css]">
+      [end]
+      [if-any category2_css]
+        <link type="text/css" rel="stylesheet" href="[version_base]/static/[category2_css]">
+      [end]
+      [if-any page_css]
+        <link type="text/css" rel="stylesheet" href="[version_base]/static/[page_css]">
+      [end]
+    [end]
+
+    <!-- Lazy load icons. -->
+    <link rel="stylesheet"
+      href="https://fonts.googleapis.com/icon?family=Material+Icons"
+      media="none"
+      id="icons-stylesheet">
+    <script type="module" async defer nonce="[nonce]">
+      document.getElementById('icons-stylesheet').media = 'all';
+    </script>
+    [# NO MORE SCRIPTS IN HEAD, it makes page loading too slow.]
+</head>
+
+[# Tiny script used sitewide. ]
+<script type="text/javascript" nonce="[nonce]">
+   function _go(url, newWindow) {
+     if (newWindow)
+       window.open(url, '_blank');
+     else
+       document.location = url;
+   }
+   function $(id) { return document.getElementById(id); }
+
+   var loadQueue = [];
+   function runOnLoad(fn) { loadQueue.push(fn); }
+
+   window.onload = function() {
+     for (var i = 0; i < loadQueue.length; i++)
+       loadQueue[[]i]();
+     delete loadQueue;
+   };
+</script>
diff --git a/templates/framework/header.ezt b/templates/framework/header.ezt
new file mode 100644
index 0000000..fe798af
--- /dev/null
+++ b/templates/framework/header.ezt
@@ -0,0 +1,39 @@
+[# This is the main header file that is included in all Monorail servlets that render a page.
+
+   Args:
+     arg0: Can be "showtabs", "showusertabs" or "showusergrouptabs" to select which top-plevel tabs are shown.
+     arg1: String like "t1", "t2", "t3" to identify the currently active tab.
+]
+[define is_ezt]Yes[end]
+[include "header-shared.ezt"]
+
+[include "../webpack-out/ezt-element-package.ezt"]
+
+<body class="[main_tab_mode] [if-any perms.EditIssue]perms_EditIssue[end]">
+
+[# Tiny script used sitewide. ]
+<script type="text/javascript" nonce="[nonce]">
+   function _go(url, newWindow) {
+     if (newWindow)
+       window.open(url, '_blank');
+     else
+       document.location = url;
+   }
+   function $(id) { return document.getElementById(id); }
+
+   var loadQueue = [];
+   function runOnLoad(fn) { loadQueue.push(fn); }
+
+   window.onload = function() {
+     for (var i = 0; i < loadQueue.length; i++)
+       loadQueue[[]i]();
+     delete loadQueue;
+   };
+</script>
+
+[include "maintabs.ezt" arg0 arg1]
+
+[include "banner_message.ezt"]
+
+<div id="maincol">
+[include "alert.ezt"]
diff --git a/templates/framework/js-placeholders.ezt b/templates/framework/js-placeholders.ezt
new file mode 100644
index 0000000..ae3548a
--- /dev/null
+++ b/templates/framework/js-placeholders.ezt
@@ -0,0 +1,12 @@
+[# Empty function definitions because we load the JS at bottom of page.
+   Without this, some rollovers or on-click handlers might give errors for
+   the first 200ms or so after the page loads. With them, they simply are
+   no-ops. ]
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+function _showBelow(){}
+function _toggleStar(){}
+function _goIssue(){}
+function _goFile(){}
+});
+</script>
diff --git a/templates/framework/label-validation-row.ezt b/templates/framework/label-validation-row.ezt
new file mode 100644
index 0000000..4c2d1a3
--- /dev/null
+++ b/templates/framework/label-validation-row.ezt
@@ -0,0 +1,6 @@
+<tr>
+  <td colspan="3">
+    <div id="confirmarea" class="novel"><span id="confirmmsg"></span></div>
+    <div id="blocksubmitarea" class="blockingsubmit"><span id="blocksubmitmsg"></span></div>
+  </td>
+</tr>
diff --git a/templates/framework/maintabs.ezt b/templates/framework/maintabs.ezt
new file mode 100644
index 0000000..416b992
--- /dev/null
+++ b/templates/framework/maintabs.ezt
@@ -0,0 +1,99 @@
+[# Show top-level tabs.
+
+   Args:
+     arg0: Can be "showtabs", or "showusertabs" to select which
+         top-level tabs are shown.
+     arg1: String like "t1", "t2", "t3" to identify the currently active tab.
+]
+[if-any projectname]
+
+[# Non-fixed container around mr-header to allow the fixed header to "take up space". ]
+<div style="width: 100%; height: var(--monorail-header-height); margin-bottom: -1px;">
+  <mr-header
+    [if-any logged_in_user]
+    userDisplayName="[logged_in_user.email]"
+    [end]
+    projectThumbnailUrl="[project_thumbnail_url]"
+    projectName="[projectname]"
+    loginUrl="[login_url]"
+    logoutUrl="[logout_url]"
+  ></mr-header>
+</div>
+[else]
+<table id="monobar" width="100%" cellpadding="0" cellspacing="0" role="presentation">
+  <tr>
+    <th class="padded">
+      <a href="/" id="wordmark">[site_name]</a>
+    </th>
+    [if-any viewed_user]
+      <th class="padded">
+        User: <a href="[viewed_user.profile_url]">[viewed_user.display_name]</a>
+        [if-any viewed_user_pb.is_site_admin_bool]<i>(Administrator)</i>[end]
+      </th>
+    [end]
+    [if-any hotlist_id]
+      <th class="toptabs padded">
+      <a href="[hotlist.url]" title="[hotlist_id]"
+        id = "hotlists-dropdown">Hotlist: [hotlist.name] <small>&#9660;</small></a>
+      <a href="[hotlist.url]" class="[is main_tab_mode "ht2"]active[end]">Issues</a>
+      <a href="[hotlist.url]/people" class="[is main_tab_mode "ht3"]active[end]">People</a>
+      <a href="[hotlist.url]/details" class="[is main_tab_mode "ht4"]active[end]">Settings</a>
+      </th>
+    [end]
+
+    <td width="100%" id="userbar">
+      [include "user-bar.ezt"]
+    </td>
+  </tr>
+</table>
+[end]
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+  if ($("hotlists-dropdown"))
+    $("hotlists-dropdown").addEventListener("click", function(event) {
+        event.preventDefault();
+    });
+});
+</script>
+
+[is arg0 "showtabs"]
+  <div class="subt">
+    [include "projecttabs.ezt"]
+  </div>
+[else][is arg0 "showusertabs"]
+  <div class="subt">
+    [include "../sitewide/usertabs.ezt" arg1]
+  </div>
+[else][is arg0 "showusergrouptabs"]
+  <div class="subt">
+    [include "../sitewide/usergrouptabs.ezt" arg1]
+  </div>
+[end][end][end]
+
+[if-any warnings]
+  <table align="center" border="0" cellspacing="0" cellpadding="0" style="margin-bottom: 6px">
+   [for warnings]
+     <tr><td class="notice">
+         [warnings]
+     </td></tr>
+   [end]
+  </table>
+[end]
+[if-any errors.query]
+  <table align="center" border="0" cellspacing="0" cellpadding="0" style="margin-bottom: 6px">
+   <tr><td class="notice">
+       [errors.query]
+   </td></tr>
+  </table>
+[end]
+
+[if-any site_read_only][else]
+  [if-any project_alert]
+    <div style="font-weight: bold; color: #c00; margin-top: 5px; display: block;">
+      [project_alert]
+    </div>
+  [end]
+[end]
+
+[include "../features/cues.ezt"]
diff --git a/templates/framework/polymer-footer.ezt b/templates/framework/polymer-footer.ezt
new file mode 100644
index 0000000..a83d40f
--- /dev/null
+++ b/templates/framework/polymer-footer.ezt
@@ -0,0 +1,14 @@
+[include "footer-shared.ezt"]
+
+<script type="text/javascript" nonce="[nonce]">
+// Google Analytics
+(function(i,s,o,g,r,a,m){i[[]'GoogleAnalyticsObject']=r;i[[]r]=i[[]r]||function(){
+(i[[]r].q=i[[]r].q||[[]]).push(arguments)},i[[]r].l=1*new Date();a=s.createElement(o),
+m=s.getElementsByTagName(o)[[]0];a.async=1;a.setAttribute('nonce','[nonce]');
+a.src=g;m.parentNode.insertBefore(a,m)
+})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+(function setupGoogleAnalytics() {
+  ga('create', '[analytics_id]', {'siteSpeedSampleRate': 100});
+})();
+</script>
diff --git a/templates/framework/project-access-part.ezt b/templates/framework/project-access-part.ezt
new file mode 100644
index 0000000..2400dfe
--- /dev/null
+++ b/templates/framework/project-access-part.ezt
@@ -0,0 +1,36 @@
+[# Diplay a widget to choose project access level, or read-only text showing
+   the access level.  Read-only text is used when the user does not have
+   permission to edit, or if there is only one available choice.
+]
+
+[define access_menu_was_shown]False[end]
+
+[if-any read_only][else]
+  [if-any offer_access_level]
+    <select name="access" id="access">
+      <option value="" disabled="disabled" [if-any initial_access][else]selected="selected"[end]>
+        Select an access level...
+      </option>
+      [for available_access_levels]
+        <option value="[available_access_levels.key]"
+          [if-any initial_access]
+            [is initial_access.key available_access_levels.key]selected="selected"[end]
+          [end]>
+          [available_access_levels.name]
+        </option>
+      [end]
+    </select>
+    [define access_menu_was_shown]True[end]
+  [end]
+[end]
+
+[is access_menu_was_shown "False"]
+  [initial_access.name]
+  <input type="hidden" name="access" value="[initial_access.key]">
+[end]
+
+<div class="formerror">
+  [if-any errors.access]
+    <div class="emphasis">[errors.access]</div>
+  [end]
+</div>
diff --git a/templates/framework/project-descriptive-fields.ezt b/templates/framework/project-descriptive-fields.ezt
new file mode 100644
index 0000000..b237f20
--- /dev/null
+++ b/templates/framework/project-descriptive-fields.ezt
@@ -0,0 +1,41 @@
+Summary:<br>
+<input type="text" id="summary" name="summary" size="75" value="[initial_summary]"><br>
+<div class="fielderror">&nbsp;
+   <span id="summaryfeedback">[if-any errors.summary][errors.summary][end]</span>
+</div>
+
+Description:<br>
+<textarea id="description" name="description" rows="20" cols="90" wrap="soft"
+          >[initial_description]</textarea><br>
+<div class="fielderror">&nbsp;
+  <span id="descriptionfeedback">[if-any errors.description][errors.description][end]</span>
+</div>
+
+Project home page (optional):<br/>
+<input type="text" id="project_home" name="project_home" size="75" value="[initial_project_home]"><br>
+<div class="fielderror">&nbsp;
+  <span id="project_homefeedback">[if-any errors.project_home][errors.project_home][end]</span>
+</div>
+
+Project documentation page (optional):<br/>
+<input type="text" id="docs_url" name="docs_url" size="75" value="[initial_docs_url]"><br>
+<div class="fielderror">&nbsp;
+  <span id="docs_urlfeedback">[if-any errors.docs_url][errors.docs_url][end]</span>
+</div>
+
+Project source browser (optional):<br/>
+<input type="text" id="source_url" name="source_url" size="75" value="[initial_source_url]"><br>
+<div class="fielderror">&nbsp;
+  <span id="source_urlfeedback">[if-any errors.source_url][errors.source_url][end]</span>
+</div>
+
+[if-any logo_view.viewurl]
+  Project logo:<br>
+  [include "display-project-logo.ezt" True]
+[else]
+  Upload project logo (optional, will be resized to 110x30):<br/>
+  <input type="file" name="logo" id="logo">
+  <div class="fielderror">&nbsp;
+    <span id="logofeedback">[if-any errors.logo][errors.logo][end]</span>
+  </div>
+[end]
diff --git a/templates/framework/projecttabs.ezt b/templates/framework/projecttabs.ezt
new file mode 100644
index 0000000..6a05c74
--- /dev/null
+++ b/templates/framework/projecttabs.ezt
@@ -0,0 +1,25 @@
+[is main_tab_mode "t4"]
+  <div class="[admin_tab_mode]">
+      <div class="at isf">
+       <span class="inst1"><a href="/p/[projectname]/adminIntro">Introduction</a></span>
+       <span class="inst3"><a href="/p/[projectname]/adminStatuses">Statuses</a></span>
+       <span class="inst4"><a href="/p/[projectname]/adminLabels">Labels and fields</a></span>
+       [if-any perms.EditProject][# Rule might be too sensitive for non-members to view.]
+         <span class="inst5"><a href="/p/[projectname]/adminRules">Rules</a></span>
+       [end]
+       <span class="inst6"><a href="/p/[projectname]/adminTemplates">Templates</a></span>
+       <span class="inst7"><a href="/p/[projectname]/adminComponents">Components</a></span>
+       <span class="inst8"><a href="/p/[projectname]/adminViews">Views</a></span>
+     </div>
+  </div>
+[end]
+
+
+[is main_tab_mode "t6"]
+  <div class="[admin_tab_mode]">
+      <div class="at isf">
+       <span class="inst1"><a href="/p/[projectname]/admin">General</a></span>
+       <span class="inst9"><a href="/p/[projectname]/adminAdvanced">Advanced</a></span>
+      </div>
+  </div>
+[end]
diff --git a/templates/framework/read-only-rejection.ezt b/templates/framework/read-only-rejection.ezt
new file mode 100644
index 0000000..3147d9a
--- /dev/null
+++ b/templates/framework/read-only-rejection.ezt
@@ -0,0 +1,10 @@
+<span style="color:#a30">
+  [if-any site_read_only]
+    This operation cannot be done when the site is read-only.
+    Please come back later.
+  [else]
+    [if-any project_read_only]
+      READ-ONLY
+    [end]
+  [end]
+</span>
diff --git a/templates/framework/saved-queries-admin-part.ezt b/templates/framework/saved-queries-admin-part.ezt
new file mode 100644
index 0000000..6514e30
--- /dev/null
+++ b/templates/framework/saved-queries-admin-part.ezt
@@ -0,0 +1,135 @@
+[# arg0 is either "user" for user saved queries or "project" for canned queries]
+<style>
+  #queries th, #queries td {  padding-bottom: 1em }
+</style>
+
+<table border="0" id="queries">
+   <tr>
+    <th></th>
+    <th style="text-align:left">Saved query name:</th>
+    [is arg0 "user"]
+      <th style="text-align:left">Project(s):</th>
+    [end]
+    <th colspan="2" style="text-align:left">Query:</th>
+    [is arg0 "user"]
+      <th style="text-align:left">Subscription options:</th>
+    [end]
+    <th></th>
+   </tr>
+
+   [for canned_queries]
+   <tr>
+    <td style="text-align:right" width="20">[canned_queries.idx].
+      <input type="hidden" name="savedquery_id_[canned_queries.idx]" value="[canned_queries.query_id]">
+    </td>
+    <td><input type="text" name="savedquery_name_[canned_queries.idx]" size="35" value="[canned_queries.name]" class="acob"></td>
+    [is arg0 "user"]
+      <td><input type="text" name="savedquery_projects_[canned_queries.idx]" size="35" value="[canned_queries.projects]"
+           class="acob" autocomplete="off" id="savedquery_projects_[canned_queries.idx]"></td>
+    [end]
+
+    <td>
+       <select name="savedquery_base_[canned_queries.idx]">
+         [define can][canned_queries.base_query_id][end]
+         [include "../tracker/issue-can-widget.ezt" "admin"]
+       </select>
+    </td>
+    <td>
+      <input type="text" name="savedquery_query_[canned_queries.idx]" size="50" value="[canned_queries.query]" autocomplete="off" id="query_existing_[canned_queries.idx]" class="acob">
+    </td>
+    [is arg0 "user"]
+      <td>
+        <select id="savedquery_sub_mode_[canned_queries.idx]" name="savedquery_sub_mode_[canned_queries.idx]">
+          <option [is canned_queries.subscription_mode "noemail"]selected="select"[end] value="noemail"
+                  >No emails</option>
+          <option [is canned_queries.subscription_mode "immediate"]selected="select"[end] value="immediate">Notify Immediately</option>
+          [# TODO(jrobbins): <option disabled="disabled">Notify Daily</option>]
+          [# TODO(jrobbins): <option disabled="disabled">Notify Weekly on Monday</option>]
+        </select>
+      </td>
+    [end]
+    <td></td>
+   </tr>
+   [end]
+
+   [is arg0 "user"]
+     [define can]1[end][# All blank lines for user queries default to "All Issues" scope.]
+   [else]
+     [define can]2[end][# All blank lines for project queries default to "Open issues" scope.]
+   [end]
+   [for new_query_indexes]
+   <tr id="newquery[new_query_indexes]" [if-index new_query_indexes first][else]style="display:none"[end]>
+    <td style="text-align:right" width="20">[new_query_indexes].</td>
+    <td><input type="text" name="new_savedquery_name_[new_query_indexes]"
+               class="showNextQueryRow acob" data-index="[new_query_indexes]"
+               size="35" value="" placeholder="Required"></td>
+    [is arg0 "user"]
+      <td><input type="text" name="new_savedquery_projects_[new_query_indexes]" size="35" value="" class="acob"
+           autocomplete="off" id="new_savedquery_projects_[new_query_indexes]" placeholder="Optional"></td>
+    [end]
+    <td>
+       <select name="new_savedquery_base_[new_query_indexes]">
+         [include "../tracker/issue-can-widget.ezt" "admin"]
+       </select>
+    </td>
+    <td>
+      <input type="text" name="new_savedquery_query_[new_query_indexes]" size="50" value="" autocomplete="off" id="query_new_[new_query_indexes]" class="acob" placeholder="Optional. Example- &quot;label:Security owner:me&quot;">
+    </td>
+    [is arg0 "user"]
+      <td>
+        <select id="new_savedquery_sub_mode_[new_query_indexes]" name="new_savedquery_sub_mode_[new_query_indexes]">
+          <option selected="selected" value="noemail">No emails</option>
+          <option value="immediate">Notify Immediately</option>
+          [# TODO(jrobbins): <option disabled="disabled">Notify Daily</option>]
+          [# TODO(jrobbins): <option disabled="disabled">Notify Weekly</option>]
+        </select>
+      </td>
+    [end]
+    <td width="40px">
+     [if-index new_query_indexes last][else]
+      <span id="addquery[new_query_indexes]" class="fakelink" data-index="[new_query_indexes]">Add a row</span
+     [end]
+    </td>
+   </tr>
+   [end]
+
+</table>
+
+<script type="text/javascript" nonce="[nonce]">
+runOnLoad(function() {
+  function showNextQueryRow(i) {
+   if (i < [max_queries]) {
+     _showID('newquery' + (i + 1));
+     _hideID('addquery' + i);
+   }
+  }
+  _fetchUserProjects(true);
+
+  var addARowLinks = document.getElementsByClassName("fakelink");
+  for (var i = 0; i < addARowLinks.length; ++i) {
+    var link = addARowLinks[[]i];
+    link.addEventListener("click", function(event) {
+        var index = Number(event.target.getAttribute("data-index"));
+        showNextQueryRow(index);
+    });
+  }
+
+  var typeToAddARow = document.getElementsByClassName("showNextQueryRow");
+  for (var i = 0; i < typeToAddARow.length; ++i) {
+    var el = typeToAddARow[[]i];
+    el.addEventListener("keydown", function(event) {
+        var index = Number(event.target.getAttribute("data-index"));
+        showNextQueryRow(index);
+    });
+  }
+
+  var acobElements = document.getElementsByClassName("acob");
+  for (var i = 0; i < acobElements.length; ++i) {
+     var el = acobElements[[]i];
+     el.addEventListener("focus", function(event) {
+         _acrob(null);
+         _acof(event);
+     });
+  }
+});
+</script>
diff --git a/templates/framework/user-bar.ezt b/templates/framework/user-bar.ezt
new file mode 100644
index 0000000..a80fe90
--- /dev/null
+++ b/templates/framework/user-bar.ezt
@@ -0,0 +1,11 @@
+<span style="padding: 0 1em">
+  [if-any logged_in_user]
+    <mr-account-dropdown
+      userDisplayName="[logged_in_user.email]"
+      loginUrl="[login_url]"
+      logoutUrl="[logout_url]"
+    ></mr-account-dropdown>
+  [else]
+    <a href="[login_url]"><u>Sign in</u></a>
+  [end]
+</span>
diff --git a/templates/framework/user-link-availability.ezt b/templates/framework/user-link-availability.ezt
new file mode 100644
index 0000000..cc02db7
--- /dev/null
+++ b/templates/framework/user-link-availability.ezt
@@ -0,0 +1,18 @@
+[# arg0: The UserView to display.
+   arg1: "Yes" to show a shortened reason as visible text on the page.
+]
+<div class="userlink_avail" title="[arg0.display_name][if-any arg0.avail_message]:
+[arg0.avail_message][end]">
+  [if-any arg0.avail_message]
+    <span class="availability_[arg0.avail_state]">&#9608;</span>
+  [end]
+  [if-any arg0.profile_url]
+    <a href="[arg0.profile_url]">[arg0.display_name]</a>[else]<a>[arg0.display_name]</a>[end]
+</div>
+[is arg1 "Yes"]
+ [if-any arg0.avail_message]
+  <div class="availability_[arg0.avail_state]" title="[arg0.display_name]:
+[arg0.avail_message]"
+  >[arg0.avail_message_short]</div>
+ [end]
+[end]
diff --git a/templates/framework/user-link.ezt b/templates/framework/user-link.ezt
new file mode 100644
index 0000000..18d7e6c
--- /dev/null
+++ b/templates/framework/user-link.ezt
@@ -0,0 +1 @@
+[if-any arg0.profile_url]<a style="white-space: nowrap" href="[arg0.profile_url]" title="[arg0.display_name]">[arg0.display_name]</a>[else][arg0.display_name][end]
\ No newline at end of file