diff --git a/package-lock.json b/package-lock.json
index 17b97dd..14992a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2,10 +2,199 @@
   "requires": true,
   "lockfileVersion": 1,
   "dependencies": {
-    "bower": {
-      "version": "1.8.4",
-      "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.4.tgz",
-      "integrity": "sha1-54dqB23rgTf30GUl3F6MZtuC8oo="
+    "acorn": {
+      "version": "4.0.13",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+      "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "enhanced-resolve": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-2.3.0.tgz",
+      "integrity": "sha1-oRXDJQS2MC6Fp2Jp16V8zdli41k=",
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "memory-fs": "^0.3.0",
+        "object-assign": "^4.0.1",
+        "tapable": "^0.2.3"
+      }
+    },
+    "errno": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+      "requires": {
+        "prr": "~1.0.1"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "glob": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.1.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+      "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "memory-fs": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz",
+      "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=",
+      "requires": {
+        "errno": "^0.1.3",
+        "readable-stream": "^2.0.1"
+      }
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "process-nextick-args": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+      "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
+    },
+    "prr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
+    },
+    "readable-stream": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "tapable": {
+      "version": "0.2.8",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz",
+      "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI="
+    },
+    "tern": {
+      "version": "0.21.0",
+      "resolved": "https://registry.npmjs.org/tern/-/tern-0.21.0.tgz",
+      "integrity": "sha1-gJyHqCbhEklDmM+IlPfC0bNGTrc=",
+      "requires": {
+        "acorn": "^4.0.9",
+        "enhanced-resolve": "^2.2.2",
+        "glob": "^7.1.1",
+        "minimatch": "^3.0.3",
+        "resolve-from": "2.0.0"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
     }
   }
 }
diff --git a/resources/templates/issue-expert/list.html b/resources/templates/issue-expert/list.html
index e69de29..629fc72 100644
--- a/resources/templates/issue-expert/list.html
+++ b/resources/templates/issue-expert/list.html
@@ -0,0 +1,146 @@
+{% extends "base.html" %}
+<!-- File list-followuprequests-Followuprequests.html generated 2018-07-17T07:58:11.329Z by adl.to-selmer-templates.
+See [Application Description Language](https://github.com/simon-brooke/adl).-->
+
+
+{% block back-links %}
+<div>
+<div class='back-link-container'>
+<a id='prev-selector' class='back-link'>
+Previous
+</a>
+</div>
+</div>
+
+{% endblock %}
+{% block big-links %}
+<div>
+<div class='big-link-container'>
+<a id='next-selector' role='button' class='big-link'>
+Next
+</a>
+</div>
+{% ifmemberof %}
+<div class='big-link-container'>
+<a href='{{servlet-context}}/form-followuprequests-Followuprequest' class='big-link'>
+Add a new Followuprequest
+</a>
+</div>
+{% endifmemberof %}
+</div>
+
+{% endblock %}
+{% block content %}
+<form id='list-followuprequests-Followuprequests' class='list' action='{{servlet-context}}/list-followuprequests-Followuprequests' method='POST'>
+{% csrf-field %}
+<input id='offset' name='offset' type='hidden' value='{{params.offset|default:0}}'/>
+<input id='limit' name='limit' type='hidden' value='{{params.limit|default:50}}'/>
+<table caption='followuprequests'>
+<thead>
+<tr>
+<th>
+Id
+</th>
+<th>
+Elector_id
+</th>
+<th>
+Visit_id
+</th>
+<th>
+Issue_id
+</th>
+<th>
+Method_id
+</th>
+<th>
+&nbsp;
+</th>
+</tr>
+<tr>
+<th>
+<input id='id' type='text' name='id' value='{{ params.id }}'/>
+</th>
+<th>
+<input id='elector_id' type='text' name='elector_id' value='{{ params.elector_id }}'/>
+</th>
+<th>
+<input id='visit_id' type='text' name='visit_id' value='{{ params.visit_id }}'/>
+</th>
+<th>
+<input id='issue_id' type='text' name='issue_id' value='{{ params.issue_id }}'/>
+</th>
+<th>
+<input id='method_id' type='text' name='method_id' value='{{ params.method_id }}'/>
+</th>
+<th>
+<input type='submit' id='search-widget' value='Search'/>
+</th>
+</tr>
+</thead>
+<tbody>
+{% for record in records %}
+<tr>
+<td>
+{{ record.id }}
+</td>
+<td>
+<a href='{{servlet-context}}/form-electors-Elector?id={{ record.elector_id }}'>
+{{ record.elector_id_expanded }}
+</a>
+</td>
+<td>
+<a href='{{servlet-context}}/form-visits-Visit?id={{ record.visit_id }}'>
+{{ record.visit_id_expanded }}
+</a>
+</td>
+<td>
+<a href='{{servlet-context}}/form-issues-Issue?id={{ record.issue_id }}'>
+{{ record.issue_id_expanded }}
+</a>
+</td>
+<td>
+<a href='{{servlet-context}}/form-followupmethods-Followupmethod?id={{ record.method_id }}'>
+{{ record.method_id_expanded }}
+</a>
+</td>
+<td>
+<a href='{{servlet-context}}/issue-expert/followup-request?id={{ record.id }}'>
+View
+</a>
+</td>
+</tr>
+{% endfor %}
+</tbody>
+</table>
+</form>
+
+{% endblock %}
+{% block extra-script %}
+
+          var form = document.getElementById('list-followuprequests-Followuprequests');
+          var ow = document.getElementById('offset');
+          var lw = document.getElementById('limit');
+          form.addEventListener('submit', function() {
+            ow.value='0';
+          });
+
+          var prevSelector = document.getElementById('prev-selector');
+          if (prevSelector != null) {
+            prevSelector.addEventListener('click', function () {
+              if (parseInt(ow.value)===0) {
+                window.location = '{{servlet-context}}/admin';
+              } else {
+                ow.value=(parseInt(ow.value)-parseInt(lw.value));
+                console.log('Updated offset to ' + ow.value);
+                form.submit();
+              }
+            });
+          }
+
+          document.getElementById('next-selector').addEventListener('click', function () {
+            ow.value=(parseInt(ow.value)+parseInt(lw.value));
+            console.log('Updated offset to ' + ow.value);
+            form.submit();
+          });
+{% endblock %}
diff --git a/resources/templates/issue-expert/request.html b/resources/templates/issue-expert/request.html
index 5e18b1f..51ea0a0 100644
--- a/resources/templates/issue-expert/request.html
+++ b/resources/templates/issue-expert/request.html
@@ -1,55 +1,157 @@
 {% extends "base.html" %}
-<!-- File form-followupmethods-Followupmethod.html generated 2018-07-09T16:38:56.477Z by adl.to-selmer-templates.
-See [Application Description Language](https://github.com/simon-brooke/adl).-->
+<!--
+  Form for issue experts to handle a request from an elector.
+-->
 
 
 {% block extra-head %}
-
+           {% script "/js/lib/node_modules/simplemde/dist/simplemde.min.js" %}
+           {% style "/js/lib/node_modules/simplemde/dist/simplemde.min.css" %}
 {% endblock %}
 {% block content %}
 <div id='content' class='edit'>
-<form action='{{servlet-context}}/form-followupmethods-Followupmethod' method='POST'>
-{% csrf-field %}
-<p class='widget'>
-<label for='id'>
-Id
-</label>
-{% ifmemberof admin %}
-<input id='id' name='id' type='string' value='{{record.id}}' maxlength='' size='16'/>
-{% else %}
-{% ifmemberof canvassers teamorganisers issueexperts analysts issueeditors admin %}
-<span id='id' name='id' class='pseudo-widget disabled'>
-{{record.id}}
-</span>
-{% else %}
-<span id='id' name='id' class='pseudo-widget not-authorised'>
-You are not permitted to view id of followupmethods
-</span>
-{% endifmemberof %}
-{% endifmemberof %}
-</p>
-{% ifmemberof admin %}
-<p class='widget action-safe'>
-<label for='save-button' class='action-safe'>
-To save this followupmethods record
-</label>
-<input id='save-button' name='save-button' class='action-safe' type='submit' value='Save!'/>
-</p>
-{% endifmemberof %}
-{% ifmemberof admin %}
-<p class='widget action-dangerous'>
-<label for='delete-button' class='action-dangerous'>
-To delete this followupmethods record
-</label>
-<input id='delete-button' name='delete-button' class='action-dangerous' type='submit' value='Delete!'/>
-</p>
-{% endifmemberof %}
-</form>
+  <form action='{{servlet-context}}/issue-expert/followup-action' method='POST'>
+    {% csrf-field %}
+    <input id='id' name='id' type='hidden' value='{{record.id}}'/>
+    <p class='widget'>
+      <label for='elector_id'>
+        Elector
+      </label>
+      {% ifmemberof canvassers teamorganisers issueexperts analysts issueeditors admin %}
+      <span id='elector_id' name='elector_id' class='pseudo-widget disabled'>
+        {{elector.name}} ({{elector.gender}})
+      </span>
+      {% else %}
+      <span id='elector_id' name='elector_id' class='pseudo-widget not-authorised'>
+        You are not permitted to view elector of followuprequests
+      </span>
+      {% endifmemberof %}
+    </p>
+    <p class='widget'>
+      <label for='visit'>
+        Visit
+      </label>
+      {% ifmemberof issueexperts analysts issueeditors admin %}
+      <span id='visit' name='visit' class='pseudo-widget disabled'>
+        by {{visit.canvasser_id_expanded}} at {{visit.date}}
+      </span>
+      {% else %}
+      <span id='visit_id' name='visit_id' class='pseudo-widget not-authorised'>
+        You are not permitted to view visit of followuprequests
+      </span>
+      {% endifmemberof %}
+    </p>
+    <p class='widget'>
+      <label for='issue_id'>
+        {{issue.id}}
+      </label>
+      {% ifmemberof issueexperts admin %}
+      <div id="issue-brief">
+        {{issue.brief|safe}}
+      </div>
+      {% else %}
+      <span id='issue_id' name='issue_id' class='pseudo-widget not-authorised'>
+        You are not permitted to view issue of followuprequests
+      </span>
+      {% endifmemberof %}
+    </p>
+    <p class='widget'>
+      <label for='method_id'>
+        {{record.method_id}}
+      </label>
+      {% ifmemberof issueexperts admin %}
+      <span id='method_id' name='method_id' class='pseudo-widget disabled'>
+        {% ifequal record.method_id "Phone" %}{{elector.phone}}{% endifequal %}
+        {% ifequal record.method_id "eMail" %}<a href="mailto:{{elector.email}}">{{elector.email}}</a>{% endifequal %}
+      </span>
+      {% else %}
+      <span id='method_id' name='method_id' class='pseudo-widget not-authorised'>
+        You are not permitted to view method of followuprequests
+      </span>
+      {% endifmemberof %}
+    </p>
+    {% if actions|length > 0 %}
+    <p>
+      <label for='history'>
+        History
+      </label>
+      <table id='history'>
+        <thead>
+          <tr>
+            <th>
+              Actor
+            </th>
+            <th>
+              Date
+            </th>
+            <th>
+              Closed?
+            </th>
+          </tr>
+        </thead>
+        <tbody>
+          {% for action in actions %}
+          <tr>
+            <td>{{action.actor}}</td>
+            <td>{{action.date}}</td>
+            <td>{{action.closed}}</td>
+          </tr>
+          <tr>
+            <td colspan="3">{{action.notes|safe}}</td>
+          </tr>
+          {% endfor %}
+        </tbody>
+      </table>
+    </p>
+    {% endif %}
+    <p class='widget'>
+      <label for='notes'>
+        Your notes
+      </label>
+      {% ifmemberof admin issueexperts %}
+      <textarea rows='8' cols='60' id='notes' name='notes'>
+      </textarea>
+      {% endifmemberof %}
+    </p>
+    <p class='widget'>
+      <label for='closed'>
+        Has the elector's query been satisfied?
+      </label>
+      {% ifmemberof admin issueexperts %}
+        <input id='closed' name='closed' type='checkbox' maxlength='' size='16'/>
+      {% endifmemberof %}
+    </p>
+
+    {% ifmemberof admin issueexperts %}
+    <p class='widget action-safe'>
+    <label for='save-button' class='action-safe'>
+    To save this followuprequests record
+    </label>
+    <input id='save-button' name='save-button' class='action-safe' type='submit' value='Save!'/>
+    </p>
+    {% endifmemberof %}
+  </form>
 </div>
 
 {% endblock %}
 {% block extra-tail %}
 <script type='text/javascript'>
+  var simplemde = new SimpleMDE({
+    autosave: {
+      enabled: true,
+      uniqueId: "adl-generated-{{page}}",
+      delay: 1000,
+    },
+    indentWithTabs: true,
+    insertTexts: {
+      horizontalRule: ["", "\n\n-----\n\n"],
+      image: ["![](http://", ")"],
+      link: ["[", "](http://)"],
+      table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text     | Text      | Text     |\n\n"],
+    },
+    showIcons: ["code"], //, "table"], - sadly, markdown-clj does not support tables
+    spellChecker: true,
+    status: ["autosave", "lines", "words", "cursor"]
+  });
 </script>
-
 {% endblock %}
diff --git a/src/clj/youyesyet/routes/issue_experts.clj b/src/clj/youyesyet/routes/issue_experts.clj
index f157133..8a54566 100644
--- a/src/clj/youyesyet/routes/issue_experts.clj
+++ b/src/clj/youyesyet/routes/issue_experts.clj
@@ -1,6 +1,7 @@
 (ns ^{:doc "Routes/pages available to issue experts."
       :author "Simon Brooke"} youyesyet.routes.issue-experts
-  (:require [adl-support.utils :refer [safe-name]]
+  (:require [adl-support.core :as support]
+            [adl-support.utils :refer [safe-name]]
             [clojure.java.io :as io]
             [clojure.string :as s]
             [clojure.tools.logging :as log]
@@ -40,7 +41,7 @@
 
 (defn list-page [request]
   (layout/render
-    "auto/list-followuprequests-Followuprequests.html"
+    "issue-expert/list.html"
     (:session request)
     (let [user (:user (:session request))]
     {:title "Open requests"
@@ -49,14 +50,44 @@
 
 
 (defn followup-request-page [request]
-  (layout/render
-    "issue-expert/request.html"
-    (:session request)
-    {:title "Open requests"
-             :user (:user (:session request))
-             :request (db/get-followuprequest
-                        db/*db*
-                        {:id (:id (keywordize-keys (:params request)))})}))
+  (let
+    [params (support/massage-params
+              (keywordize-keys (:params request))
+              (keywordize-keys (:form-params request))
+              #{:id})
+     id (:id (keywordize-keys params))
+     record (db/get-followuprequest db/*db* {:id id})
+     elector (if
+               record
+               (first
+                 (db/search-strings-electors
+                   db/*db* {:id (:elector_id record)})))
+     visit (if
+             record
+             (first
+               (db/search-strings-visits
+                 db/*db* {:id (:visit_id record)})))]
+    (layout/render
+      "issue-expert/request.html"
+      (:session request)
+      {:title (str "Request from " (:name elector) " at " (:date visit))
+       :user (:user (:session request))
+       :visit visit
+       :actions (map
+                  ;; HTML-ise the notes in each action record
+                  #(merge % {:notes (md-to-html-string (:notes %))})
+                  (db/list-followupactions-by-followuprequest
+                    db/*db* {:id id}))
+       :record record
+       :elector elector
+       :issue (let
+                [raw-issue (if
+                             record
+                             (db/get-issue db/*db* {:id (:issue_id record)}))]
+                (if raw-issue
+                  (merge
+                    raw-issue
+                    {:brief (md-to-html-string (:brief raw-issue))})))})))
 
 
 (defroutes issue-expert-routes