diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,15 +1,27 @@
 User-visible changes in hledger-web.
 See also the hledger changelog.
 
+# 1.18 2020-06-07
 
+- Hyperlinks are now more robust when there are multiple journal
+  files, eg links from register to journal now work properly. (#1041)
+
+## add form
+
+- Fixed a 2016 regression causing too many rows to be added by
+  keypresses in the last amount field or CTRL-plus (#422, #1059).
+
+- Always start with four rows when opened.
+
+- Drop unneeded C-minus/C-plus keys & related help text.
+
+
 # 1.17.1 2020-03-19
 
-- require newer Decimal, math-functions, fixing inconsistent rounding
-  Decimal 0.5.1+ changed to banker's rounding (round to nearest even
-  number), and math-functions 0.3.3.0 (used by roi) fixed various
-  precision-related issues. Now we require the latest versions of these.
-  This was causing some functional test failures when building with old
-  GHCs/snapshots.
+- require newer Decimal, math-functions libs to ensure consistent
+  rounding behaviour, even when built with old GHCs/snapshots. 
+  hledger uses banker's rounding (rounds to nearest even number, eg
+  0.5 displayed with zero decimal places is "0").
 
 # 1.17 2020-03-01
 
diff --git a/Hledger/Web/Foundation.hs b/Hledger/Web/Foundation.hs
--- a/Hledger/Web/Foundation.hs
+++ b/Hledger/Web/Foundation.hs
@@ -122,7 +122,7 @@
         -- flip the default for items with zero amounts, show them by default
         ropts' = ropts { empty_ = not (empty_ ropts) }
         accounts =
-          balanceReportAsHtml (JournalR, RegisterR) here hideEmptyAccts j qopts $
+          balanceReportAsHtml (JournalR, RegisterR) here hideEmptyAccts j q qopts $
           balanceReport ropts' m j
 
         topShowmd = if showSidebar then "col-md-4" else "col-any-0" :: Text
diff --git a/Hledger/Web/Handler/JournalR.hs b/Hledger/Web/Handler/JournalR.hs
--- a/Hledger/Web/Handler/JournalR.hs
+++ b/Hledger/Web/Handler/JournalR.hs
@@ -12,20 +12,23 @@
 import Hledger.Web.Import
 import Hledger.Web.WebOptions
 import Hledger.Web.Widget.AddForm (addModal)
-import Hledger.Web.Widget.Common (accountQuery, mixedAmountAsHtml)
+import Hledger.Web.Widget.Common
+            (accountQuery, mixedAmountAsHtml,
+             transactionFragment, replaceInacct)
 
 -- | The formatted journal view, with sidebar.
 getJournalR :: Handler Html
 getJournalR = do
   checkServerSideUiEnabled
-  VD{caps, j, m, opts, qopts, today} <- getViewData
+  VD{caps, j, m, opts, q, qopts, today} <- getViewData
   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability")
   let title = case inAccount qopts of
         Nothing -> "General Journal"
         Just (a, inclsubs) -> "Transactions in " <> a <> if inclsubs then "" else " (excluding subaccounts)"
       title' = title <> if m /= Any then ", filtered" else ""
-      acctlink a = (RegisterR, [("q", accountQuery a)])
+      acctlink a = (RegisterR, [("q", replaceInacct q $ accountQuery a)])
       (_, items) = transactionsReport (reportopts_ $ cliopts_ opts) j m
+      transactionFrag = transactionFragment j
 
   defaultLayout $ do
     setTitle "journal - hledger-web"
diff --git a/Hledger/Web/Handler/RegisterR.hs b/Hledger/Web/Handler/RegisterR.hs
--- a/Hledger/Web/Handler/RegisterR.hs
+++ b/Hledger/Web/Handler/RegisterR.hs
@@ -17,13 +17,15 @@
 import Hledger.Web.Import
 import Hledger.Web.WebOptions
 import Hledger.Web.Widget.AddForm (addModal)
-import Hledger.Web.Widget.Common (accountQuery, mixedAmountAsHtml)
+import Hledger.Web.Widget.Common
+             (accountQuery, mixedAmountAsHtml,
+              transactionFragment, removeInacct, replaceInacct)
 
 -- | The main journal/account register view, with accounts sidebar.
 getRegisterR :: Handler Html
 getRegisterR = do
   checkServerSideUiEnabled
-  VD{caps, j, m, opts, qopts, today} <- getViewData
+  VD{caps, j, m, opts, q, qopts, today} <- getViewData
   when (CapView `notElem` caps) (permissionDenied "Missing the 'view' capability")
 
   let (a,inclsubs) = fromMaybe ("all accounts",True) $ inAccount qopts
@@ -33,7 +35,7 @@
 
   let ropts = reportopts_ (cliopts_ opts)
       acctQuery = fromMaybe Any (inAccountQuery qopts)
-      acctlink acc = (RegisterR, [("q", accountQuery acc)])
+      acctlink acc = (RegisterR, [("q", replaceInacct q $ accountQuery acc)])
       otherTransAccounts =
           map (\(acct,(name,comma)) -> (acct, (T.pack name, T.pack comma))) .
           undecorateLinks . elideRightDecorated 40 . decorateLinks .
@@ -44,6 +46,7 @@
           tail $ (", "<$xs) ++ [""]
       r@(balancelabel,items) = accountTransactionsReport ropts j m acctQuery
       balancelabel' = if isJust (inAccount qopts) then balancelabel else "Total"
+      transactionFrag = transactionFragment j
   defaultLayout $ do
     setTitle "register - hledger-web"
     $(widgetFile "register")
diff --git a/Hledger/Web/Widget/Common.hs b/Hledger/Web/Widget/Common.hs
--- a/Hledger/Web/Widget/Common.hs
+++ b/Hledger/Web/Widget/Common.hs
@@ -14,6 +14,9 @@
   , fromFormSuccess
   , writeJournalTextIfValidAndChanged
   , journalFile404
+  , transactionFragment
+  , removeInacct
+  , replaceInacct
   ) where
 
 import Data.Default (def)
@@ -23,17 +26,20 @@
 #endif
 import Data.Text (Text)
 import qualified Data.Text as T
+import qualified Data.HashMap.Strict as HashMap
 import System.FilePath (takeFileName)
 import Text.Blaze ((!), textValue)
 import qualified Text.Blaze.Html5 as H
 import qualified Text.Blaze.Html5.Attributes as A
 import Text.Blaze.Internal (preEscapedString)
 import Text.Hamlet (hamletFile)
+import Text.Printf (printf)
 import Yesod
 
 import Hledger
 import Hledger.Cli.Utils (writeFileWithBackupIfChanged)
 import Hledger.Web.Settings (manualurl)
+import qualified Hledger.Query as Query
 
 #if MIN_VERSION_yesod(1,6,0)
 journalFile404 :: FilePath -> Journal -> HandlerFor m (FilePath, Text)
@@ -79,8 +85,8 @@
   where u = textValue $ manualurl <> if T.null topic then "" else T.cons '#' topic
 
 -- | Render a "BalanceReport" as html.
-balanceReportAsHtml :: Eq r => (r, r) -> r -> Bool -> Journal -> [QueryOpt] -> BalanceReport -> HtmlUrl r
-balanceReportAsHtml (journalR, registerR) here hideEmpty j qopts (items, total) =
+balanceReportAsHtml :: Eq r => (r, r) -> r -> Bool -> Journal -> Text -> [QueryOpt] -> BalanceReport -> HtmlUrl r
+balanceReportAsHtml (journalR, registerR) here hideEmpty j q qopts (items, total) =
   $(hamletFile "templates/balance-report.hamlet")
   where
     l = ledgerFromJournal Any j
@@ -103,3 +109,20 @@
     c = case isNegativeMixedAmount b of
       Just True -> "negative amount"
       _ -> "positive amount"
+
+transactionFragment :: Journal -> Transaction -> String
+transactionFragment j =
+    let hm = HashMap.fromList $ zip (map fst $ jfiles j) [(1::Integer) ..]
+    in  \t ->
+            printf "transaction-%d-%d"
+                (hm HashMap.! sourceFilePath (tsourcepos t)) (tindex t)
+
+removeInacct :: Text -> [Text]
+removeInacct =
+    map quoteIfSpaced .
+    filter (\term ->
+        not $ T.isPrefixOf "inacct:" term || T.isPrefixOf "inacctonly:" term) .
+    Query.words'' Query.prefixes
+
+replaceInacct :: Text -> Text -> Text
+replaceInacct q acct = T.unwords $ acct : removeInacct q
diff --git a/README b/README
deleted file mode 100644
--- a/README
+++ /dev/null
@@ -1,1 +0,0 @@
-A basic web UI for hledger data. Intended to be robust and somewhat useful.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+# hledger-web
+
+A simple web-based user interface for the hledger accounting system,
+providing a more modern UI than the command-line or terminal interfaces.
+It can be used as a local single-user UI, or as a multi-user UI for
+viewing/adding/editing on the web.
+
+See also:
+the [project README](https://hledger.org/README.html)
+and [home page](https://hledger.org).
diff --git a/app/main.hs b/app/main.hs
--- a/app/main.hs
+++ b/app/main.hs
@@ -1,5 +1,3 @@
-import Prelude              (IO)
-
 import Hledger.Web.Main
 
 main :: IO ()
diff --git a/hledger-web.1 b/hledger-web.1
--- a/hledger-web.1
+++ b/hledger-web.1
@@ -1,5 +1,5 @@
 
-.TH "hledger-web" "1" "March 2020" "hledger-web 1.17.1" "hledger User Manuals"
+.TH "hledger-web" "1" "June 2020" "hledger-web 1.18" "hledger User Manuals"
 
 
 
@@ -316,60 +316,215 @@
 that both machine clocks are roughly in step.)
 .SH JSON API
 .PP
-In addition to the web UI, hledger-web provides some API routes that
-serve JSON in response to GET requests.
-(And when started with \f[C]--serve-api\f[R], it provides only these
-routes.):
+In addition to the web UI, hledger-web also serves a JSON API that can
+be used to get data or add new transactions.
+If you want the JSON API only, you can use the \f[C]--serve-api\f[R]
+flag.
+Eg:
 .IP
 .nf
 \f[C]
+$ hledger-web -f examples/sample.journal --serve-api
+\&...
+\f[R]
+.fi
+.PP
+You can get JSON data from these routes:
+.IP
+.nf
+\f[C]
 /accountnames
 /transactions
 /prices
 /commodities
 /accounts
-/accounttransactions/#AccountName
+/accounttransactions/ACCOUNTNAME
 \f[R]
 .fi
 .PP
-Also, you can append a new transaction to the journal by sending a PUT
-request to \f[C]/add\f[R] (hledger-web only).
-As with the web UI\[aq]s add form, hledger-web must be started with the
-\f[C]add\f[R] capability for this (enabled by default).
+Eg, all account names in the journal (similar to the accounts command).
+(hledger-web\[aq]s JSON does not include newlines, here we use python to
+prettify it):
+.IP
+.nf
+\f[C]
+$ curl -s http://127.0.0.1:5000/accountnames | python -m json.tool
+[
+    \[dq]assets\[dq],
+    \[dq]assets:bank\[dq],
+    \[dq]assets:bank:checking\[dq],
+    \[dq]assets:bank:saving\[dq],
+    \[dq]assets:cash\[dq],
+    \[dq]expenses\[dq],
+    \[dq]expenses:food\[dq],
+    \[dq]expenses:supplies\[dq],
+    \[dq]income\[dq],
+    \[dq]income:gifts\[dq],
+    \[dq]income:salary\[dq],
+    \[dq]liabilities\[dq],
+    \[dq]liabilities:debts\[dq]
+]
+\f[R]
+.fi
 .PP
-The payload should be a valid hledger transaction as JSON, similar to
-what you get from \f[C]/transactions\f[R] or
-\f[C]/accounttransactions\f[R].
+Or all transactions:
+.IP
+.nf
+\f[C]
+$ curl -s http://127.0.0.1:5000/transactions | python -m json.tool
+[
+    {
+        \[dq]tcode\[dq]: \[dq]\[dq],
+        \[dq]tcomment\[dq]: \[dq]\[dq],
+        \[dq]tdate\[dq]: \[dq]2008-01-01\[dq],
+        \[dq]tdate2\[dq]: null,
+        \[dq]tdescription\[dq]: \[dq]income\[dq],
+        \[dq]tindex\[dq]: 1,
+        \[dq]tpostings\[dq]: [
+            {
+                \[dq]paccount\[dq]: \[dq]assets:bank:checking\[dq],
+                \[dq]pamount\[dq]: [
+                    {
+                        \[dq]acommodity\[dq]: \[dq]$\[dq],
+                        \[dq]aismultiplier\[dq]: false,
+                        \[dq]aprice\[dq]: null,
+\&...
+\f[R]
+.fi
 .PP
-Another way to generate test data is with the
-\f[C]readJsonFile\f[R]/\f[C]writeJsonFile\f[R] helpers in
-Hledger.Web.Json, which can write or read most of hledger\[aq]s data
-types to or from a file.
-Eg here we write the first transaction of a sample journal:
+Most of the JSON corresponds to hledger\[aq]s data types; for details of
+what the fields mean, see the Hledger.Data.Json haddock docs and click
+on the various data types, eg Transaction.
+And for a higher level understanding, see the journal manual.
+.PP
+In some cases there is outer JSON corresponding to a \[dq]Report\[dq]
+type.
+To understand that, go to the Hledger.Web.Handler.MiscR haddock and look
+at the source for the appropriate handler to see what it returns.
+Eg for \f[C]/accounttransactions\f[R] it\[aq]s getAccounttransactionsR,
+returning a \[dq]\f[C]accountTransactionsReport ...\f[R]\[dq].
+Looking up the haddock for that we can see that /accounttransactions
+returns an AccountTransactionsReport, which consists of a report title
+and a list of AccountTransactionsReportItem (etc).
+.PP
+You can add a new transaction to the journal with a PUT request to
+\f[C]/add\f[R], if hledger-web was started with the \f[C]add\f[R]
+capability (enabled by default).
+The payload must be the full, exact JSON representation of a hledger
+transaction (partial data won\[aq]t do).
+You can get sample JSON from hledger-web\[aq]s \f[C]/transactions\f[R]
+or \f[C]/accounttransactions\f[R], or you can export it with
+hledger-lib, eg like so:
 .IP
 .nf
 \f[C]
-$ make ghci-web
->>> :m +*Hledger.Web.Json
+\&.../hledger$ stack ghci hledger-lib
 >>> writeJsonFile \[dq]txn.json\[dq] (head $ jtxns samplejournal)
 >>> :q
-$ python -m json.tool <txn.json >txn.pretty.json  # optional: make human-readable
 \f[R]
 .fi
 .PP
-(sample output & discussion)
-.PP
-And here\[aq]s how to test adding that with curl:
+Here\[aq]s how it looks as of hledger-1.17 (remember, this JSON
+corresponds to hledger\[aq]s Transaction and related data types):
 .IP
 .nf
 \f[C]
-$ curl -s http://127.0.0.1:5000/add -X PUT -H \[aq]Content-Type: application/json\[aq] --data-binary \[at]txn.pretty.json; echo
+{
+    \[dq]tcomment\[dq]: \[dq]\[dq],
+    \[dq]tpostings\[dq]: [
+        {
+            \[dq]pbalanceassertion\[dq]: null,
+            \[dq]pstatus\[dq]: \[dq]Unmarked\[dq],
+            \[dq]pamount\[dq]: [
+                {
+                    \[dq]aprice\[dq]: null,
+                    \[dq]acommodity\[dq]: \[dq]$\[dq],
+                    \[dq]aquantity\[dq]: {
+                        \[dq]floatingPoint\[dq]: 1,
+                        \[dq]decimalPlaces\[dq]: 10,
+                        \[dq]decimalMantissa\[dq]: 10000000000
+                    },
+                    \[dq]aismultiplier\[dq]: false,
+                    \[dq]astyle\[dq]: {
+                        \[dq]ascommodityside\[dq]: \[dq]L\[dq],
+                        \[dq]asdigitgroups\[dq]: null,
+                        \[dq]ascommodityspaced\[dq]: false,
+                        \[dq]asprecision\[dq]: 2,
+                        \[dq]asdecimalpoint\[dq]: \[dq].\[dq]
+                    }
+                }
+            ],
+            \[dq]ptransaction_\[dq]: \[dq]1\[dq],
+            \[dq]paccount\[dq]: \[dq]assets:bank:checking\[dq],
+            \[dq]pdate\[dq]: null,
+            \[dq]ptype\[dq]: \[dq]RegularPosting\[dq],
+            \[dq]pcomment\[dq]: \[dq]\[dq],
+            \[dq]pdate2\[dq]: null,
+            \[dq]ptags\[dq]: [],
+            \[dq]poriginal\[dq]: null
+        },
+        {
+            \[dq]pbalanceassertion\[dq]: null,
+            \[dq]pstatus\[dq]: \[dq]Unmarked\[dq],
+            \[dq]pamount\[dq]: [
+                {
+                    \[dq]aprice\[dq]: null,
+                    \[dq]acommodity\[dq]: \[dq]$\[dq],
+                    \[dq]aquantity\[dq]: {
+                        \[dq]floatingPoint\[dq]: -1,
+                        \[dq]decimalPlaces\[dq]: 10,
+                        \[dq]decimalMantissa\[dq]: -10000000000
+                    },
+                    \[dq]aismultiplier\[dq]: false,
+                    \[dq]astyle\[dq]: {
+                        \[dq]ascommodityside\[dq]: \[dq]L\[dq],
+                        \[dq]asdigitgroups\[dq]: null,
+                        \[dq]ascommodityspaced\[dq]: false,
+                        \[dq]asprecision\[dq]: 2,
+                        \[dq]asdecimalpoint\[dq]: \[dq].\[dq]
+                    }
+                }
+            ],
+            \[dq]ptransaction_\[dq]: \[dq]1\[dq],
+            \[dq]paccount\[dq]: \[dq]income:salary\[dq],
+            \[dq]pdate\[dq]: null,
+            \[dq]ptype\[dq]: \[dq]RegularPosting\[dq],
+            \[dq]pcomment\[dq]: \[dq]\[dq],
+            \[dq]pdate2\[dq]: null,
+            \[dq]ptags\[dq]: [],
+            \[dq]poriginal\[dq]: null
+        }
+    ],
+    \[dq]ttags\[dq]: [],
+    \[dq]tsourcepos\[dq]: {
+        \[dq]tag\[dq]: \[dq]JournalSourcePos\[dq],
+        \[dq]contents\[dq]: [
+            \[dq]\[dq],
+            [
+                1,
+                1
+            ]
+        ]
+    },
+    \[dq]tdate\[dq]: \[dq]2008-01-01\[dq],
+    \[dq]tcode\[dq]: \[dq]\[dq],
+    \[dq]tindex\[dq]: 1,
+    \[dq]tprecedingcomment\[dq]: \[dq]\[dq],
+    \[dq]tdate2\[dq]: null,
+    \[dq]tdescription\[dq]: \[dq]income\[dq],
+    \[dq]tstatus\[dq]: \[dq]Unmarked\[dq]
+}
 \f[R]
 .fi
 .PP
-By default, both the server-side HTML UI and the JSON API are served.
-Running with \f[C]--serve-api\f[R] disables the former, useful if you
-only want to serve the API.
+And here\[aq]s how to test adding it with curl.
+This should add a new entry to your journal:
+.IP
+.nf
+\f[C]
+$ curl http://127.0.0.1:5000/add -X PUT -H \[aq]Content-Type: application/json\[aq] --data-binary \[at]txn.json
+\f[R]
+.fi
 .SH ENVIRONMENT
 .PP
 \f[B]LEDGER_FILE\f[R] The journal file path when not specified with
diff --git a/hledger-web.cabal b/hledger-web.cabal
--- a/hledger-web.cabal
+++ b/hledger-web.cabal
@@ -1,24 +1,25 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.31.2.
+-- This file has been generated from package.yaml by hpack version 0.33.0.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 9a8cb3ef7223d8bdfbc8e2c5a8caa8d9c80b645289d749fdec200b98094ecd60
+-- hash: daab2dd5e0ca3ed383252d6577500975576694432c856b386d9cbc2c5f7b75c0
 
 name:           hledger-web
-version:        1.17.1
-synopsis:       Web interface for the hledger accounting tool
-description:    This is hledger's web interface.
-                It provides a more user-friendly and collaborative UI than the
-                command-line or terminal interfaces.
+version:        1.18
+synopsis:       Web-based user interface for the hledger accounting system
+description:    A simple web-based user interface for the hledger accounting system,
+                providing a more modern UI than the command-line or terminal interfaces.
+                It can be used as a local single-user UI, or as a multi-user UI for
+                viewing/adding/editing on the web.
                 .
-                hledger is a cross-platform program for tracking money, time, or
-                any other commodity, using double-entry accounting and a simple,
-                editable file format. It is inspired by and largely compatible
-                with ledger(1).  hledger provides command-line, terminal and web
-                interfaces, and aims to be a reliable, practical tool for daily
-                use.
+                hledger is a robust, cross-platform set of tools for tracking money,
+                time, or any other commodity, using double-entry accounting and a
+                simple, editable file format, with command-line, terminal and web
+                interfaces. It is a Haskell rewrite of Ledger, and one of the leading
+                implementations of Plain Text Accounting. Read more at:
+                <https://hledger.org>
 category:       Finance
 stability:      stable
 homepage:       http://hledger.org
@@ -27,11 +28,11 @@
 maintainer:     Simon Michael <simon@joyful.com>
 license:        GPL-3
 license-file:   LICENSE
-tested-with:    GHC==8.2.2, GHC==8.4.3, GHC==8.6.5, GHC==8.8.2, GHC==8.10
+tested-with:    GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, GHC==8.8.3, GHC==8.10.0.20200123
 build-type:     Simple
 extra-source-files:
     CHANGES.md
-    README
+    README.md
     config/favicon.ico
     config/keter.yaml
     config/robots.txt
@@ -150,7 +151,7 @@
   hs-source-dirs:
       ./.
   ghc-options: -Wall -fwarn-tabs -Wcompat -Wincomplete-uni-patterns -Wincomplete-record-updates -Wredundant-constraints
-  cpp-options: -DVERSION="1.17.1"
+  cpp-options: -DVERSION="1.18"
   build-depends:
       Decimal >=0.5.1
     , aeson
@@ -169,8 +170,8 @@
     , extra >=1.6.3
     , filepath
     , hjsmin
-    , hledger >=1.17.1 && <1.18
-    , hledger-lib >=1.17.1 && <1.18
+    , hledger >=1.18 && <1.19
+    , hledger-lib >=1.18 && <1.19
     , http-client
     , http-conduit
     , http-types
@@ -184,6 +185,7 @@
     , time >=1.5
     , transformers
     , unix-compat
+    , unordered-containers
     , utf8-string
     , wai
     , wai-cors
@@ -208,7 +210,7 @@
   hs-source-dirs:
       app
   ghc-options: -Wall -fwarn-tabs -Wcompat -Wincomplete-uni-patterns -Wincomplete-record-updates -Wredundant-constraints
-  cpp-options: -DVERSION="1.17.1"
+  cpp-options: -DVERSION="1.18"
   build-depends:
       base
     , hledger-web
diff --git a/hledger-web.info b/hledger-web.info
--- a/hledger-web.info
+++ b/hledger-web.info
@@ -3,8 +3,8 @@
 
 File: hledger-web.info,  Node: Top,  Next: OPTIONS,  Up: (dir)
 
-hledger-web(1) hledger-web 1.17.1
-*********************************
+hledger-web(1) hledger-web 1.18
+*******************************
 
 hledger-web - web interface for the hledger accounting tool
 
@@ -330,45 +330,182 @@
 5 JSON API
 **********
 
-In addition to the web UI, hledger-web provides some API routes that
-serve JSON in response to GET requests.  (And when started with
-'--serve-api', it provides only these routes.):
+In addition to the web UI, hledger-web also serves a JSON API that can
+be used to get data or add new transactions.  If you want the JSON API
+only, you can use the '--serve-api' flag.  Eg:
 
+$ hledger-web -f examples/sample.journal --serve-api
+...
+
+   You can get JSON data from these routes:
+
 /accountnames
 /transactions
 /prices
 /commodities
 /accounts
-/accounttransactions/#AccountName
+/accounttransactions/ACCOUNTNAME
 
-   Also, you can append a new transaction to the journal by sending a
-PUT request to '/add' (hledger-web only).  As with the web UI's add
-form, hledger-web must be started with the 'add' capability for this
-(enabled by default).
+   Eg, all account names in the journal (similar to the accounts
+command).  (hledger-web's JSON does not include newlines, here we use
+python to prettify it):
 
-   The payload should be a valid hledger transaction as JSON, similar to
-what you get from '/transactions' or '/accounttransactions'.
+$ curl -s http://127.0.0.1:5000/accountnames | python -m json.tool
+[
+    "assets",
+    "assets:bank",
+    "assets:bank:checking",
+    "assets:bank:saving",
+    "assets:cash",
+    "expenses",
+    "expenses:food",
+    "expenses:supplies",
+    "income",
+    "income:gifts",
+    "income:salary",
+    "liabilities",
+    "liabilities:debts"
+]
 
-   Another way to generate test data is with the
-'readJsonFile'/'writeJsonFile' helpers in Hledger.Web.Json, which can
-write or read most of hledger's data types to or from a file.  Eg here
-we write the first transaction of a sample journal:
+   Or all transactions:
 
-$ make ghci-web
->>> :m +*Hledger.Web.Json
+$ curl -s http://127.0.0.1:5000/transactions | python -m json.tool
+[
+    {
+        "tcode": "",
+        "tcomment": "",
+        "tdate": "2008-01-01",
+        "tdate2": null,
+        "tdescription": "income",
+        "tindex": 1,
+        "tpostings": [
+            {
+                "paccount": "assets:bank:checking",
+                "pamount": [
+                    {
+                        "acommodity": "$",
+                        "aismultiplier": false,
+                        "aprice": null,
+...
+
+   Most of the JSON corresponds to hledger's data types; for details of
+what the fields mean, see the Hledger.Data.Json haddock docs and click
+on the various data types, eg Transaction.  And for a higher level
+understanding, see the journal manual.
+
+   In some cases there is outer JSON corresponding to a "Report" type.
+To understand that, go to the Hledger.Web.Handler.MiscR haddock and look
+at the source for the appropriate handler to see what it returns.  Eg
+for '/accounttransactions' it's getAccounttransactionsR, returning a
+"'accountTransactionsReport ...'".  Looking up the haddock for that we
+can see that /accounttransactions returns an AccountTransactionsReport,
+which consists of a report title and a list of
+AccountTransactionsReportItem (etc).
+
+   You can add a new transaction to the journal with a PUT request to
+'/add', if hledger-web was started with the 'add' capability (enabled by
+default).  The payload must be the full, exact JSON representation of a
+hledger transaction (partial data won't do).  You can get sample JSON
+from hledger-web's '/transactions' or '/accounttransactions', or you can
+export it with hledger-lib, eg like so:
+
+.../hledger$ stack ghci hledger-lib
 >>> writeJsonFile "txn.json" (head $ jtxns samplejournal)
 >>> :q
-$ python -m json.tool <txn.json >txn.pretty.json  # optional: make human-readable
 
-   (sample output & discussion)
+   Here's how it looks as of hledger-1.17 (remember, this JSON
+corresponds to hledger's Transaction and related data types):
 
-   And here's how to test adding that with curl:
+{
+    "tcomment": "",
+    "tpostings": [
+        {
+            "pbalanceassertion": null,
+            "pstatus": "Unmarked",
+            "pamount": [
+                {
+                    "aprice": null,
+                    "acommodity": "$",
+                    "aquantity": {
+                        "floatingPoint": 1,
+                        "decimalPlaces": 10,
+                        "decimalMantissa": 10000000000
+                    },
+                    "aismultiplier": false,
+                    "astyle": {
+                        "ascommodityside": "L",
+                        "asdigitgroups": null,
+                        "ascommodityspaced": false,
+                        "asprecision": 2,
+                        "asdecimalpoint": "."
+                    }
+                }
+            ],
+            "ptransaction_": "1",
+            "paccount": "assets:bank:checking",
+            "pdate": null,
+            "ptype": "RegularPosting",
+            "pcomment": "",
+            "pdate2": null,
+            "ptags": [],
+            "poriginal": null
+        },
+        {
+            "pbalanceassertion": null,
+            "pstatus": "Unmarked",
+            "pamount": [
+                {
+                    "aprice": null,
+                    "acommodity": "$",
+                    "aquantity": {
+                        "floatingPoint": -1,
+                        "decimalPlaces": 10,
+                        "decimalMantissa": -10000000000
+                    },
+                    "aismultiplier": false,
+                    "astyle": {
+                        "ascommodityside": "L",
+                        "asdigitgroups": null,
+                        "ascommodityspaced": false,
+                        "asprecision": 2,
+                        "asdecimalpoint": "."
+                    }
+                }
+            ],
+            "ptransaction_": "1",
+            "paccount": "income:salary",
+            "pdate": null,
+            "ptype": "RegularPosting",
+            "pcomment": "",
+            "pdate2": null,
+            "ptags": [],
+            "poriginal": null
+        }
+    ],
+    "ttags": [],
+    "tsourcepos": {
+        "tag": "JournalSourcePos",
+        "contents": [
+            "",
+            [
+                1,
+                1
+            ]
+        ]
+    },
+    "tdate": "2008-01-01",
+    "tcode": "",
+    "tindex": 1,
+    "tprecedingcomment": "",
+    "tdate2": null,
+    "tdescription": "income",
+    "tstatus": "Unmarked"
+}
 
-$ curl -s http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.pretty.json; echo
+   And here's how to test adding it with curl.  This should add a new
+entry to your journal:
 
-   By default, both the server-side HTML UI and the JSON API are served.
-Running with '--serve-api' disables the former, useful if you only want
-to serve the API.
+$ curl http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.json
 
 
 File: hledger-web.info,  Node: ENVIRONMENT,  Next: FILES,  Prev: JSON API,  Up: Top
@@ -427,22 +564,22 @@
 
 Tag Table:
 Node: Top72
-Node: OPTIONS1750
-Ref: #options1855
-Node: PERMISSIONS8199
-Ref: #permissions8338
-Node: EDITING UPLOADING DOWNLOADING9550
-Ref: #editing-uploading-downloading9731
-Node: RELOADING10565
-Ref: #reloading10699
-Node: JSON API11132
-Ref: #json-api11246
-Node: ENVIRONMENT12684
-Ref: #environment12800
-Node: FILES13533
-Ref: #files13633
-Node: BUGS13846
-Ref: #bugs13924
+Node: OPTIONS1746
+Ref: #options1851
+Node: PERMISSIONS8195
+Ref: #permissions8334
+Node: EDITING UPLOADING DOWNLOADING9546
+Ref: #editing-uploading-downloading9727
+Node: RELOADING10561
+Ref: #reloading10695
+Node: JSON API11128
+Ref: #json-api11242
+Node: ENVIRONMENT16723
+Ref: #environment16839
+Node: FILES17572
+Ref: #files17672
+Node: BUGS17885
+Ref: #bugs17963
 
 End Tag Table
 
diff --git a/hledger-web.txt b/hledger-web.txt
--- a/hledger-web.txt
+++ b/hledger-web.txt
@@ -289,45 +289,182 @@
        that both machine clocks are roughly in step.)
 
 JSON API
-       In addition to the web UI, hledger-web provides some  API  routes  that
-       serve  JSON  in  response  to  GET  requests.   (And  when started with
-       --serve-api, it provides only these routes.):
+       In addition to the web UI, hledger-web also serves a JSON API that  can
+       be  used to get data or add new transactions.  If you want the JSON API
+       only, you can use the --serve-api flag.  Eg:
 
+              $ hledger-web -f examples/sample.journal --serve-api
+              ...
+
+       You can get JSON data from these routes:
+
               /accountnames
               /transactions
               /prices
               /commodities
               /accounts
-              /accounttransactions/#AccountName
+              /accounttransactions/ACCOUNTNAME
 
-       Also, you can append a new transaction to the journal by sending a  PUT
-       request  to  /add  (hledger-web  only).  As with the web UI's add form,
-       hledger-web must be started with the add capability for  this  (enabled
-       by default).
+       Eg, all account names in the journal (similar to the accounts command).
+       (hledger-web's  JSON  does  not include newlines, here we use python to
+       prettify it):
 
-       The  payload  should be a valid hledger transaction as JSON, similar to
-       what you get from /transactions or /accounttransactions.
+              $ curl -s http://127.0.0.1:5000/accountnames | python -m json.tool
+              [
+                  "assets",
+                  "assets:bank",
+                  "assets:bank:checking",
+                  "assets:bank:saving",
+                  "assets:cash",
+                  "expenses",
+                  "expenses:food",
+                  "expenses:supplies",
+                  "income",
+                  "income:gifts",
+                  "income:salary",
+                  "liabilities",
+                  "liabilities:debts"
+              ]
 
-       Another way to generate test data is with  the  readJsonFile/writeJson-
-       File  helpers  in  Hledger.Web.Json,  which  can  write or read most of
-       hledger's data types to or from a file.  Eg here  we  write  the  first
-       transaction of a sample journal:
+       Or all transactions:
 
-              $ make ghci-web
-              >>> :m +*Hledger.Web.Json
+              $ curl -s http://127.0.0.1:5000/transactions | python -m json.tool
+              [
+                  {
+                      "tcode": "",
+                      "tcomment": "",
+                      "tdate": "2008-01-01",
+                      "tdate2": null,
+                      "tdescription": "income",
+                      "tindex": 1,
+                      "tpostings": [
+                          {
+                              "paccount": "assets:bank:checking",
+                              "pamount": [
+                                  {
+                                      "acommodity": "$",
+                                      "aismultiplier": false,
+                                      "aprice": null,
+              ...
+
+       Most of the JSON corresponds to hledger's data types;  for  details  of
+       what  the fields mean, see the Hledger.Data.Json haddock docs and click
+       on the various data types, eg Transaction.  And for a higher level  un-
+       derstanding, see the journal manual.
+
+       In some cases there is outer JSON corresponding to a "Report" type.  To
+       understand that, go to the Hledger.Web.Handler.MiscR haddock  and  look
+       at  the  source for the appropriate handler to see what it returns.  Eg
+       for /accounttransactions it's getAccounttransactionsR, returning a "ac-
+       countTransactionsReport  ...".   Looking up the haddock for that we can
+       see that  /accounttransactions  returns  an  AccountTransactionsReport,
+       which  consists  of a report title and a list of AccountTransactionsRe-
+       portItem (etc).
+
+       You can add a new transaction to the journal  with  a  PUT  request  to
+       /add,  if  hledger-web  was started with the add capability (enabled by
+       default).  The payload must be the full, exact JSON representation of a
+       hledger  transaction  (partial data won't do).  You can get sample JSON
+       from hledger-web's /transactions or /accounttransactions,  or  you  can
+       export it with hledger-lib, eg like so:
+
+              .../hledger$ stack ghci hledger-lib
               >>> writeJsonFile "txn.json" (head $ jtxns samplejournal)
               >>> :q
-              $ python -m json.tool <txn.json >txn.pretty.json  # optional: make human-readable
 
-       (sample output & discussion)
+       Here's how it looks as of hledger-1.17 (remember, this JSON corresponds
+       to hledger's Transaction and related data types):
 
-       And here's how to test adding that with curl:
+              {
+                  "tcomment": "",
+                  "tpostings": [
+                      {
+                          "pbalanceassertion": null,
+                          "pstatus": "Unmarked",
+                          "pamount": [
+                              {
+                                  "aprice": null,
+                                  "acommodity": "$",
+                                  "aquantity": {
+                                      "floatingPoint": 1,
+                                      "decimalPlaces": 10,
+                                      "decimalMantissa": 10000000000
+                                  },
+                                  "aismultiplier": false,
+                                  "astyle": {
+                                      "ascommodityside": "L",
+                                      "asdigitgroups": null,
+                                      "ascommodityspaced": false,
+                                      "asprecision": 2,
+                                      "asdecimalpoint": "."
+                                  }
+                              }
+                          ],
+                          "ptransaction_": "1",
+                          "paccount": "assets:bank:checking",
+                          "pdate": null,
+                          "ptype": "RegularPosting",
+                          "pcomment": "",
+                          "pdate2": null,
+                          "ptags": [],
+                          "poriginal": null
+                      },
+                      {
+                          "pbalanceassertion": null,
+                          "pstatus": "Unmarked",
+                          "pamount": [
+                              {
+                                  "aprice": null,
+                                  "acommodity": "$",
+                                  "aquantity": {
+                                      "floatingPoint": -1,
+                                      "decimalPlaces": 10,
+                                      "decimalMantissa": -10000000000
+                                  },
+                                  "aismultiplier": false,
+                                  "astyle": {
+                                      "ascommodityside": "L",
+                                      "asdigitgroups": null,
+                                      "ascommodityspaced": false,
+                                      "asprecision": 2,
+                                      "asdecimalpoint": "."
+                                  }
+                              }
+                          ],
+                          "ptransaction_": "1",
+                          "paccount": "income:salary",
+                          "pdate": null,
+                          "ptype": "RegularPosting",
+                          "pcomment": "",
+                          "pdate2": null,
+                          "ptags": [],
+                          "poriginal": null
+                      }
+                  ],
+                  "ttags": [],
+                  "tsourcepos": {
+                      "tag": "JournalSourcePos",
+                      "contents": [
+                          "",
+                          [
+                              1,
+                              1
+                          ]
+                      ]
+                  },
+                  "tdate": "2008-01-01",
+                  "tcode": "",
+                  "tindex": 1,
+                  "tprecedingcomment": "",
+                  "tdate2": null,
+                  "tdescription": "income",
+                  "tstatus": "Unmarked"
+              }
 
-              $ curl -s http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.pretty.json; echo
+       And here's how to test adding it with curl.  This should add a new  en-
+       try to your journal:
 
-       By  default,  both the server-side HTML UI and the JSON API are served.
-       Running with --serve-api disables the former, useful if you  only  want
-       to serve the API.
+              $ curl http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.json
 
 ENVIRONMENT
        LEDGER_FILE The journal file path when not specified with -f.  Default:
@@ -392,4 +529,4 @@
 
 
 
-hledger-web 1.17.1                March 2020                    hledger-web(1)
+hledger-web 1.18                   June 2020                    hledger-web(1)
diff --git a/static/hledger.js b/static/hledger.js
--- a/static/hledger.js
+++ b/static/hledger.js
@@ -33,11 +33,6 @@
   $('body').bind('keydown', 'a',       function(){ addformShow(); return false; });
   $('body').bind('keydown', 'n',       function(){ addformShow(); return false; });
   $('body').bind('keydown', 'f',       function(){ $('#searchform input').focus(); return false; });
-  $('body, #addform input, #addform select').bind('keydown', 'ctrl++',       addformAddPosting);
-  $('body, #addform input, #addform select').bind('keydown', 'ctrl+shift+=', addformAddPosting);
-  $('body, #addform input, #addform select').bind('keydown', 'ctrl+=',       addformAddPosting);
-  $('body, #addform input, #addform select').bind('keydown', 'ctrl+-',       addformDeletePosting);
-  $('.amount-input:last').keypress(addformAddPosting);
 
   // highlight the entry from the url hash
   if (window.location.hash && $(window.location.hash)[0]) {
@@ -138,18 +133,27 @@
   $('#addmodal').modal('show');
 }
 
-// Make sure the add form is empty and clean for display.
+// Make sure the add form is empty and clean and has the default number of rows.
 function addformReset(showmsg) {
   showmsg = typeof showmsg !== 'undefined' ? showmsg : false;
   if ($('form#addform').length > 0) {
     if (!showmsg) $('div#message').html('');
-    $('form#addform')[0].reset();
+    $('#addform .account-group.added-row').remove();
+    addformLastAmountBindKey();
+    $('#addform')[0].reset();
     // reset typehead state (though not fetched completions)
     $('.typeahead').typeahead('val', '');
     $('.tt-dropdown-menu').hide();
   }
 }
 
+// Set the add-new-row-on-keypress handler on the add form's current last amount field, only.
+// (NB: removes all other keypress handlers from all amount fields).
+function addformLastAmountBindKey() {
+  $('.amount-input').off('keypress');
+  $('.amount-input:last').keypress(addformAddPosting);
+}
+
 // Focus the first add form field.
 function addformFocus() {
   focus($('#addform input#date'));
@@ -167,28 +171,14 @@
   if (!$('#addform').is(':visible')) {
     return;
   }
-
-  var prevLastRow = $('#addform .account-group:last');
-  prevLastRow.off('keypress');
-
-  // Clone the currently last row
-  $('#addform .account-postings').append(prevLastRow.clone());
-  var num = $('#addform .account-group').length;
-
-  // clear and renumber the field, add keybindings
-  // XXX Enable typehead on dynamically created inputs
-  $('.amount-input:last')
-    .val('')
-    .prop('placeholder','Amount ' + num)
-    .keypress(addformAddPosting);
-
-  $('.account-input:last')
-    .val('')
-    .prop('placeholder', 'Account ' + num)
-    .bind('keydown', 'ctrl++', addformAddPosting)
-    .bind('keydown', 'ctrl+shift+=', addformAddPosting)
-    .bind('keydown', 'ctrl+=', addformAddPosting)
-    .bind('keydown', 'ctrl+-', addformDeletePosting);
+  // Clone the old last row to make a new last row
+  $('#addform .account-postings').append( $('#addform .account-group:last').clone().addClass('added-row') );
+  // renumber and clear the new last account and amount fields
+  var n = $('#addform .account-group').length;
+  $('.account-input:last').prop('placeholder', 'Account '+n).val('');
+  $('.amount-input:last').prop('placeholder','Amount '+n).val('');  // XXX Enable typehead on dynamically created inputs
+  // and move the keypress handler to the new last amount field
+  addformLastAmountBindKey();
 }
 
 // Remove the add form's last posting row, if empty, keeping at least two.
@@ -205,8 +195,8 @@
   if (focuslost) {
     focus($('.account-input:last'));
   }
-  // Rebind keypress
-  $('.amount-input:last').keypress(addformAddPosting);
+  // move the keypress handler to the new last amount field
+  addformLastAmountBindKey();
 }
 
 //----------------------------------------------------------------------
diff --git a/templates/add-form.hamlet b/templates/add-form.hamlet
--- a/templates/add-form.hamlet
+++ b/templates/add-form.hamlet
@@ -65,7 +65,3 @@
       $forall p <- journals
         <option value=#{p}>#{p}
 <span .small style="padding-left:2em;">
-  Enter a value in the last field for #
-  <a href="#" onclick="addformAddPosting(); return false;">
-    more
-  \ (or ctrl +, ctrl -)
diff --git a/templates/balance-report.hamlet b/templates/balance-report.hamlet
--- a/templates/balance-report.hamlet
+++ b/templates/balance-report.hamlet
@@ -7,15 +7,15 @@
 $forall (acct, adisplay, aindent, abal) <- items
   <tr
      :matchesAcctSelector acct:.inacct
-     :isZeroMixedAmount abal && hideEmpty:.hide>
-    <td .acct :isZeroMixedAmount abal:.empty>
+     :mixedAmountLooksZero abal && hideEmpty:.hide>
+    <td .acct :mixedAmountLooksZero abal:.empty>
       <div .ff-wrapper>
         \#{indent aindent}
-        <a.acct-name href="@?{(registerR, [("q", accountQuery acct)])}"
+        <a.acct-name href="@?{(registerR, [("q", replaceInacct q $ accountQuery acct)])}"
            title="Show transactions affecting this account and subaccounts">
           #{adisplay}
         $if hasSubAccounts acct
-          <a href="@?{(registerR, [("q", accountOnlyQuery acct)])}" .only.hidden-sm.hidden-xs
+          <a href="@?{(registerR, [("q", replaceInacct q $ accountOnlyQuery acct)])}" .only.hidden-sm.hidden-xs
              title="Show transactions affecting this account but not subaccounts">only
     <td>
       ^{mixedAmountAsHtml abal}
diff --git a/templates/journal.hamlet b/templates/journal.hamlet
--- a/templates/journal.hamlet
+++ b/templates/journal.hamlet
@@ -15,13 +15,13 @@
       <th .amount style="text-align:right;">Amount
 
     $forall (torig, _, split, _, amt, _) <- items
-      <tr .title #transaction-#{tindex torig} title="#{showTransaction torig}">
+      <tr .title ##{transactionFrag torig} title="#{showTransaction torig}">
         <td .date nowrap>
           #{show (tdate torig)}
         <td colspan=2>
           #{textElideRight 60 (tdescription torig)}
         <td .amount style="text-align:right;">
-          $if not split && not (isZeroMixedAmount amt)
+          $if not split && not (mixedAmountLooksZero amt)
             ^{mixedAmountAsHtml amt}
 
       $forall Posting { paccount = acc, pamount = amt } <- tpostings torig
diff --git a/templates/register.hamlet b/templates/register.hamlet
--- a/templates/register.hamlet
+++ b/templates/register.hamlet
@@ -21,7 +21,7 @@
       $forall (torig, tacct, split, _acct, amt, bal) <- items
         <tr ##{tindex torig} title="#{showTransaction torig}" style="vertical-align:top;">
           <td .date>
-            <a href="@{JournalR}#transaction-#{tindex torig}">
+            <a href="@?{(JournalR, [("q", T.unwords $ removeInacct q)])}##{transactionFrag torig}">
               #{show (tdate tacct)}
           <td>
             #{textElideRight 30 (tdescription tacct)}
@@ -30,7 +30,7 @@
               <a href="@?{acctlink acc}##{tindex torig}" title="#{acc}">
                 #{summName}</a>#{comma}
           <td .amount style="text-align:right; white-space:nowrap;">
-            $if not split || not (isZeroMixedAmount amt)
+            $if not split || not (mixedAmountLooksZero amt)
               ^{mixedAmountAsHtml amt}
           <td style="text-align:right;">
             ^{mixedAmountAsHtml bal}
