diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -9,6 +9,23 @@
 User-visible changes in hledger-ui.
 See also the hledger changelog.
 
+# 1.23 2021-09-21
+
+Improvements
+
+- Require base >=4.11, prevent red squares on Hackage's build matrix.
+
+API changes
+
+- Lenses are now available for UIState etc., saving a lot of boilerplate. (Stephen Morgan)
+
+- Renamed:
+  ```
+  version -> packageversion
+  versiondescription -> versionStringFor
+  UIOpts fields
+  ```
+
 # 1.22.2 2021-08-07
 
 - Use hledger 1.22.2.
@@ -17,8 +34,7 @@
 
 Improvements
 
-- Document watch mode and its limitations.
-  (#1617, #911, #836)
+- Document watch mode and its limitations. (#1617, #911, #836)
 
 - Allow megaparsec 9.1.
 
diff --git a/Hledger/UI/AccountsScreen.hs b/Hledger/UI/AccountsScreen.hs
--- a/Hledger/UI/AccountsScreen.hs
+++ b/Hledger/UI/AccountsScreen.hs
@@ -19,7 +19,7 @@
 import Data.List
 import Data.Maybe
 import qualified Data.Text as T
-import Data.Time.Calendar (Day, addDays)
+import Data.Time.Calendar (Day)
 import qualified Data.Vector as V
 import Graphics.Vty (Event(..),Key(..),Modifier(..))
 import Lens.Micro.Platform
@@ -49,7 +49,7 @@
 
 asInit :: Day -> Bool -> UIState -> UIState
 asInit d reset ui@UIState{
-  aopts=UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}},
+  aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}},
   ajournal=j,
   aScreen=s@AccountsScreen{}
   } =
@@ -69,7 +69,7 @@
                    (_, Nothing)            -> 0
                    (_, Just (_,AccountsScreenItem{asItemAccountName=a})) ->
                      headDef 0 $ catMaybes [
-                       findIndex (a ==) as
+                       elemIndex a as
                       ,findIndex (a `isAccountNamePrefixOf`) as
                       ,Just $ max 0 (length (filter (< a) as) - 1)
                       ]
@@ -77,16 +77,7 @@
                         as = map asItemAccountName displayitems
 
     -- Further restrict the query based on the current period and future/forecast mode.
-    rspec' = rspec{rsQuery=simplifyQuery $ And [rsQuery rspec, periodq, excludeforecastq (forecast_ $ inputopts_ copts)]}
-      where
-        periodq = Date $ periodAsDateSpan $ period_ ropts
-        -- Except in forecast mode, exclude future/forecast transactions.
-        excludeforecastq (Just _) = Any
-        excludeforecastq Nothing  =  -- not:date:tomorrow- not:tag:generated-transaction
-          And [
-             Not (Date $ DateSpan (Just $ addDays 1 d) Nothing)
-            ,Not generatedTransactionTag
-          ]
+    rspec' = reportSpecSetFutureAndForecast d (forecast_ $ inputopts_ copts) rspec
 
     -- run the report
     (items,_total) = balanceReport rspec' j
@@ -112,7 +103,7 @@
 asInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
 
 asDraw :: UIState -> [Widget Name]
-asDraw UIState{aopts=_uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}
+asDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}}
               ,ajournal=j
               ,aScreen=s@AccountsScreen{}
               ,aMode=mode
@@ -152,8 +143,8 @@
       render $ defaultLayout toplabel bottomlabel $ renderList (asDrawItem colwidths) True (_asList s)
 
       where
-        ropts = rsOpts rspec
-        ishistorical = balancetype_ ropts == HistoricalBalance
+        ropts = _rsReportOpts rspec
+        ishistorical = balanceaccum_ ropts == Historical
 
         toplabel =
               withAttr ("border" <> "filename") files
@@ -232,12 +223,12 @@
 asHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
 asHandle ui0@UIState{
    aScreen=scr@AccountsScreen{..}
-  ,aopts=UIOpts{cliopts_=copts}
+  ,aopts=UIOpts{uoCliOpts=copts}
   ,ajournal=j
   ,aMode=mode
   } ev = do
-  d <- liftIO getCurrentDay
   let
+    d = copts^.rsDay
     nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ _asList^.listElementsL
     lastnonblankidx = max 0 (length nonblanks - 1)
 
@@ -259,8 +250,8 @@
         VtyEvent ev        -> do ed' <- handleEditorEvent ev ed
                                  continue $ ui{aMode=Minibuffer ed'}
         AppEvent _        -> continue ui
-        MouseDown _ _ _ _ -> continue ui
-        MouseUp _ _ _     -> continue ui
+        MouseDown{}       -> continue ui
+        MouseUp{}         -> continue ui
 
     Help ->
       case ev of
@@ -274,7 +265,7 @@
         VtyEvent (EvKey (KChar 'q') []) -> halt ui
         -- EvKey (KChar 'l') [MCtrl] -> do
         VtyEvent (EvKey KEsc        []) -> continue $ resetScreens d ui
-        VtyEvent (EvKey (KChar c)   []) | c `elem` ['?'] -> continue $ setMode Help ui
+        VtyEvent (EvKey (KChar c)   []) | c == '?' -> continue $ setMode Help ui
         -- XXX AppEvents currently handled only in Normal mode
         -- XXX be sure we don't leave unconsumed events piling up
         AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
@@ -365,8 +356,8 @@
                                     }
 
         AppEvent _        -> continue ui
-        MouseDown _ _ _ _ -> continue ui
-        MouseUp _ _ _     -> continue ui
+        MouseDown{}       -> continue ui
+        MouseUp{}         -> continue ui
 
   where
     journalspan = journalDateSpan False j
diff --git a/Hledger/UI/ErrorScreen.hs b/Hledger/UI/ErrorScreen.hs
--- a/Hledger/UI/ErrorScreen.hs
+++ b/Hledger/UI/ErrorScreen.hs
@@ -19,6 +19,7 @@
 import Data.Time.Calendar (Day)
 import Data.Void (Void)
 import Graphics.Vty (Event(..),Key(..),Modifier(..))
+import Lens.Micro ((^.))
 import Text.Megaparsec
 import Text.Megaparsec.Char
 
@@ -28,7 +29,6 @@
 import Hledger.UI.UIState
 import Hledger.UI.UIUtils
 import Hledger.UI.Editor
-import Data.Foldable (asum)
 
 errorScreen :: Screen
 errorScreen = ErrorScreen{
@@ -43,7 +43,7 @@
 esInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
 
 esDraw :: UIState -> [Widget Name]
-esDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{}}
+esDraw UIState{aopts=UIOpts{uoCliOpts=copts}
               ,aScreen=ErrorScreen{..}
               ,aMode=mode
               } =
@@ -59,9 +59,10 @@
               withAttr ("border" <> "bold") (str "Oops. Please fix this problem then press g to reload")
               -- <+> (if ignore_assertions_ copts then withAttr ("border" <> "query") (str " ignoring") else str " not ignoring")
 
-        bottomlabel = case mode of
+        bottomlabel = quickhelp
+                        -- case mode of
                         -- Minibuffer ed -> minibuffer ed
-                        _             -> quickhelp
+                        -- _             -> quickhelp
           where
             quickhelp = borderKeysStr [
                ("h", "help")
@@ -75,7 +76,7 @@
 
 esHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
 esHandle ui@UIState{aScreen=ErrorScreen{..}
-                   ,aopts=UIOpts{cliopts_=copts}
+                   ,aopts=UIOpts{uoCliOpts=copts}
                    ,ajournal=j
                    ,aMode=mode
                    }
@@ -89,7 +90,7 @@
         _                    -> helpHandle ui ev
 
     _ -> do
-      d <- liftIO getCurrentDay
+      let d = copts^.rsDay
       case ev of
         VtyEvent (EvKey (KChar 'q') []) -> halt ui
         VtyEvent (EvKey KEsc        []) -> continue $ uiCheckBalanceAssertions d $ resetScreens d ui
@@ -177,26 +178,12 @@
         UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}
         _                                -> screenEnter d errorScreen{esError=err} ui
 
--- | Ensure this CliOpts enables forecasted transactions.
--- If a forecast period was specified in the old CliOpts,
--- or in the provided UIState's startup options,
--- it is preserved.
-enableForecastPreservingPeriod :: UIState -> CliOpts -> CliOpts
-enableForecastPreservingPeriod ui copts@CliOpts{inputopts_=iopts} =
-    copts{inputopts_=iopts{forecast_=mforecast}}
-  where
-    mforecast = asum [mprovidedforecastperiod, mstartupforecastperiod, mdefaultforecastperiod]
-      where
-        mprovidedforecastperiod = forecast_ $ inputopts_ copts
-        mstartupforecastperiod  = forecast_ $ inputopts_ $ cliopts_ $ astartupopts ui
-        mdefaultforecastperiod  = Just nulldatespan
-
 -- Re-check any balance assertions in the current journal, and if any
 -- fail, enter (or update) the error screen. Or if balance assertions
 -- are disabled, do nothing.
 uiCheckBalanceAssertions :: Day -> UIState -> UIState
-uiCheckBalanceAssertions d ui@UIState{aopts=UIOpts{cliopts_=copts}, ajournal=j}
-  | ignore_assertions_ . balancingopts_ $ inputopts_ copts = ui
+uiCheckBalanceAssertions d ui@UIState{ajournal=j}
+  | ui^.ignore_assertions = ui
   | otherwise =
     case journalCheckBalanceAssertions j of
       Nothing  -> ui
diff --git a/Hledger/UI/Main.hs b/Hledger/UI/Main.hs
--- a/Hledger/UI/Main.hs
+++ b/Hledger/UI/Main.hs
@@ -3,6 +3,7 @@
 Copyright (c) 2007-2015 Simon Michael <simon@joyful.com>
 Released under GPL version 3 or later.
 -}
+{-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings     #-}
 
@@ -17,6 +18,7 @@
 import Data.Maybe (fromMaybe)
 import qualified Data.Text as T
 import Graphics.Vty (mkVty)
+import Lens.Micro ((^.))
 import System.Directory (canonicalizePath)
 import System.FilePath (takeDirectory)
 import System.FSNotify (Event(Modified), isPollingManager, watchDir, withManager)
@@ -28,7 +30,6 @@
 import Hledger.Cli hiding (progname,prognameandversion)
 import Hledger.UI.UIOptions
 import Hledger.UI.UITypes
-import Hledger.UI.UIState (toggleHistorical)
 import Hledger.UI.Theme
 import Hledger.UI.AccountsScreen
 import Hledger.UI.RegisterScreen
@@ -44,7 +45,7 @@
 
 main :: IO ()
 main = do
-  opts@UIOpts{cliopts_=copts@CliOpts{inputopts_=iopts,rawopts_=rawopts}} <- getHledgerUIOpts
+  opts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=iopts,rawopts_=rawopts}} <- getHledgerUIOpts
   -- when (debug_ $ cliopts_ opts) $ printf "%s\n" prognameandversion >> printf "opts: %s\n" (show opts)
 
   -- always generate forecasted periodic transactions; their visibility will be toggled by the UI.
@@ -55,14 +56,13 @@
     _ | "info"            `inRawOpts` rawopts -> runInfoForTopic "hledger-ui" Nothing
     _ | "man"             `inRawOpts` rawopts -> runManForTopic  "hledger-ui" Nothing
     _ | "version"         `inRawOpts` rawopts -> putStrLn prognameandversion
-    _ | "binary-filename" `inRawOpts` rawopts -> putStrLn (binaryfilename progname)
+    -- _ | "binary-filename" `inRawOpts` rawopts -> putStrLn (binaryfilename progname)
     _                                         -> withJournalDo copts' (runBrickUi opts)
 
 runBrickUi :: UIOpts -> Journal -> IO ()
-runBrickUi uopts@UIOpts{cliopts_=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{rsOpts=ropts}}} j = do
-  d <- getCurrentDay
-
+runBrickUi uopts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} j = do
   let
+    today = copts^.rsDay
 
     -- hledger-ui's query handling is currently in flux, mixing old and new approaches.
     -- Related: #1340, #1383, #1387. Some notes and terminology:
@@ -102,32 +102,26 @@
     -- It's cleaner and less conflicting to keep the former out of the latter.
 
     uopts' = uopts{
-      cliopts_=copts{
+      uoCliOpts=copts{
          reportspec_=rspec{
-            rsQuery=filteredQuery $ rsQuery rspec,  -- query with depth/date parts removed
-            rsOpts=ropts{
-               depth_ =queryDepth $ rsQuery rspec,  -- query's depth part
+            _rsQuery=filteredQuery $ _rsQuery rspec,  -- query with depth/date parts removed
+            _rsReportOpts=ropts{
+               depth_ =queryDepth $ _rsQuery rspec,  -- query's depth part
                period_=periodfromoptsandargs,       -- query's date part
                no_elide_=True,  -- avoid squashing boring account names, for a more regular tree (unlike hledger)
-               empty_=not $ empty_ ropts,  -- show zero items by default, hide them with -E (unlike hledger)
-               balancetype_=HistoricalBalance  -- show historical balances by default (unlike hledger)
+               empty_=not $ empty_ ropts  -- show zero items by default, hide them with -E (unlike hledger)
                }
             }
          }
       }
       where
-        datespanfromargs = queryDateSpan (date2_ ropts) $ rsQuery rspec
+        datespanfromargs = queryDateSpan (date2_ ropts) $ _rsQuery rspec
         periodfromoptsandargs =
           dateSpanAsPeriod $ spansIntersect [periodAsDateSpan $ period_ ropts, datespanfromargs]
         filteredQuery q = simplifyQuery $ And [queryFromFlags ropts, filtered q]
           where filtered = filterQuery (\x -> not $ queryIsDepth x || queryIsDate x)
 
-    -- XXX move this stuff into Options, UIOpts
-    theme = maybe defaultTheme (fromMaybe defaultTheme . getTheme) $
-            maybestringopt "theme" $ rawopts_ copts
-    mregister = maybestringopt "register" $ rawopts_ copts
-
-    (scr, prevscrs) = case mregister of
+    (scr, prevscrs) = case uoRegister uopts' of
       Nothing   -> (accountsScreen, [])
       -- with --register, start on the register screen, and also put
       -- the accounts screen on the prev screens stack so you can exit
@@ -142,7 +136,7 @@
           -- Initialising the accounts screen is awkward, requiring
           -- another temporary UIState value..
           ascr' = aScreen $
-                  asInit d True
+                  asInit today True
                     UIState{
                      astartupopts=uopts'
                     ,aopts=uopts'
@@ -153,8 +147,7 @@
                     }
 
     ui =
-      (sInit scr) d True $
-        (if change_ uopts' then toggleHistorical else id) -- XXX
+      (sInit scr) today True $
           UIState{
            astartupopts=uopts'
           ,aopts=uopts'
@@ -167,7 +160,7 @@
     brickapp :: App UIState AppEvent Name
     brickapp = App {
         appStartEvent   = return
-      , appAttrMap      = const theme
+      , appAttrMap      = const $ fromMaybe defaultTheme $ getTheme =<< uoTheme uopts'
       , appChooseCursor = showFirstCursor
       , appHandleEvent  = \ui ev -> sHandle (aScreen ui) ui ev
       , appDraw         = \ui    -> sDraw   (aScreen ui) ui
@@ -175,7 +168,7 @@
 
   -- print (length (show ui)) >> exitSuccess  -- show any debug output to this point & quit
 
-  if not (watch_ uopts')
+  if not (uoWatch uopts')
   then
     void $ Brick.defaultMain brickapp ui
 
@@ -197,9 +190,10 @@
         watchDate new
 
     withAsync
+      -- run this small task asynchronously:
       (getCurrentDay >>= watchDate)
-      $ \_ ->
-
+      -- until this main task terminates:
+      $ \_async ->
       -- start one or more background threads reporting changes in the directories of our files
       -- XXX many quick successive saves causes the problems listed in BUGS
       -- with Debounce increased to 1s it easily gets stuck on an error or blank screen
@@ -218,7 +212,7 @@
           mgr
           d
           -- predicate: ignore changes not involving our files
-          (\fev -> case fev of
+          (\case
             Modified f _ False -> f `elem` files
             -- Added    f _ -> f `elem` files
             -- Removed  f _ -> f `elem` files
diff --git a/Hledger/UI/RegisterScreen.hs b/Hledger/UI/RegisterScreen.hs
--- a/Hledger/UI/RegisterScreen.hs
+++ b/Hledger/UI/RegisterScreen.hs
@@ -56,7 +56,7 @@
 rsSetAccount _ _ scr = scr
 
 rsInit :: Day -> Bool -> UIState -> UIState
-rsInit d reset ui@UIState{aopts=_uopts@UIOpts{cliopts_=CliOpts{inputopts_=iopts,reportspec_=rspec@ReportSpec{rsOpts=ropts}}}, ajournal=j, aScreen=s@RegisterScreen{..}} =
+rsInit d reset ui@UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}, ajournal=j, aScreen=s@RegisterScreen{..}} =
   ui{aScreen=s{rsList=newitems'}}
   where
     -- gather arguments and queries
@@ -70,24 +70,12 @@
         depth_=Nothing
       -- XXX aregister also has this, needed ?
         -- always show historical balance
-      -- , balancetype_= HistoricalBalance
+      -- , balanceaccum_= Historical
       }
-    rspec' =
+    rspec' = reportSpecSetFutureAndForecast d (forecast_ $ inputopts_ copts) .
       either (error "rsInit: adjusting the query for register, should not have failed") id $ -- PARTIAL:
-      updateReportSpec ropts' rspec
-
-    -- Further restrict the query based on the current period and future/forecast mode.
-    q = simplifyQuery $ And [rsQuery rspec', periodq, excludeforecastq (forecast_ iopts)]
-      where
-        periodq = Date $ periodAsDateSpan $ period_ ropts
-        -- Except in forecast mode, exclude future/forecast transactions.
-        excludeforecastq (Just _) = Any
-        excludeforecastq Nothing  =  -- not:date:tomorrow- not:tag:generated-transaction
-          And [
-             Not (Date $ DateSpan (Just $ addDays 1 d) Nothing)
-            ,Not generatedTransactionTag
-          ]
-    items = accountTransactionsReport rspec' j q thisacctq
+      updateReportSpec ropts' rspec{_rsDay=d}
+    items = accountTransactionsReport rspec' j thisacctq
     items' = (if empty_ ropts then id else filter (not . mixedAmountLooksZero . fifth6)) $  -- without --empty, exclude no-change txns
              reverse  -- most recent last
              items
@@ -96,7 +84,7 @@
     displayitems = map displayitem items'
       where
         displayitem (t, _, _issplit, otheracctsstr, change, bal) =
-          RegisterScreenItem{rsItemDate          = showDate $ transactionRegisterDate q thisacctq t
+          RegisterScreenItem{rsItemDate          = showDate $ transactionRegisterDate (_rsQuery rspec') thisacctq t
                             ,rsItemStatus        = tstatus t
                             ,rsItemDescription   = tdescription t
                             ,rsItemOtherAccounts = otheracctsstr
@@ -141,12 +129,12 @@
                 nearestidbydatethenid = third3 <$> (headMay $ sort
                   [(abs $ diffDays (tdate t) prevseld, abs (tindex t - prevselidx), tindex t) | t <- ts])
                 ts = map rsItemTransaction displayitems
-        endidx = length displayitems - 1
+        endidx = max 0 $ length displayitems - 1
 
 rsInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
 
 rsDraw :: UIState -> [Widget Name]
-rsDraw UIState{aopts=_uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}
+rsDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}}
               ,aScreen=RegisterScreen{..}
               ,aMode=mode
               } =
@@ -200,8 +188,8 @@
       render $ defaultLayout toplabel bottomlabel $ renderList (rsDrawItem colwidths) True rsList
 
       where
-        ropts = rsOpts rspec
-        ishistorical = balancetype_ ropts == HistoricalBalance
+        ropts = _rsReportOpts rspec
+        ishistorical = balanceaccum_ ropts == Historical
         -- inclusive = tree_ ropts || rsForceInclusive
 
         toplabel =
@@ -287,12 +275,12 @@
 rsHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
 rsHandle ui@UIState{
    aScreen=s@RegisterScreen{..}
-  ,aopts=UIOpts{cliopts_=copts}
+  ,aopts=UIOpts{uoCliOpts=copts}
   ,ajournal=j
   ,aMode=mode
   } ev = do
-  d <- liftIO getCurrentDay
   let
+    d = copts^.rsDay
     journalspan = journalDateSpan False j
     nonblanks = V.takeWhile (not . T.null . rsItemDate) $ rsList^.listElementsL
     lastnonblankidx = max 0 (length nonblanks - 1)
@@ -308,8 +296,8 @@
         VtyEvent ev              -> do ed' <- handleEditorEvent ev ed
                                        continue $ ui{aMode=Minibuffer ed'}
         AppEvent _        -> continue ui
-        MouseDown _ _ _ _ -> continue ui
-        MouseUp _ _ _     -> continue ui
+        MouseDown{}       -> continue ui
+        MouseUp{}         -> continue ui
 
     Help ->
       case ev of
@@ -322,7 +310,7 @@
       case ev of
         VtyEvent (EvKey (KChar 'q') []) -> halt ui
         VtyEvent (EvKey KEsc        []) -> continue $ resetScreens d ui
-        VtyEvent (EvKey (KChar c)   []) | c `elem` ['?'] -> continue $ setMode Help ui
+        VtyEvent (EvKey (KChar c)   []) | c == '?' -> continue $ setMode Help ui
         AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
           continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
           where
@@ -338,9 +326,7 @@
             (pos,f) = case listSelectedElement rsList of
                         Nothing -> (endPosition, journalFilePath j)
                         Just (_, RegisterScreenItem{
-                          rsItemTransaction=Transaction{tsourcepos=GenericSourcePos f l c}}) -> (Just (l, Just c),f)
-                        Just (_, RegisterScreenItem{
-                          rsItemTransaction=Transaction{tsourcepos=JournalSourcePos f (l,_)}}) -> (Just (l, Nothing),f)
+                          rsItemTransaction=Transaction{tsourcepos=(SourcePos f l c,_)}}) -> (Just (unPos l, Just $ unPos c),f)
 
         -- display mode/query toggles
         VtyEvent (EvKey (KChar 'B') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleCost ui
@@ -396,8 +382,8 @@
           continue ui{aScreen=s{rsList=newitems}}
 
         AppEvent _        -> continue ui
-        MouseDown _ _ _ _ -> continue ui
-        MouseUp _ _ _     -> continue ui
+        MouseDown{}       -> continue ui
+        MouseUp{}         -> continue ui
 
 rsHandle _ _ = error "event handler called with wrong screen type, should not happen"  -- PARTIAL:
 
diff --git a/Hledger/UI/TransactionScreen.hs b/Hledger/UI/TransactionScreen.hs
--- a/Hledger/UI/TransactionScreen.hs
+++ b/Hledger/UI/TransactionScreen.hs
@@ -41,7 +41,7 @@
   }
 
 tsInit :: Day -> Bool -> UIState -> UIState
-tsInit _d _reset ui@UIState{aopts=UIOpts{cliopts_=CliOpts{reportspec_=_rspec}}
+tsInit _d _reset ui@UIState{aopts=UIOpts{}
                            ,ajournal=_j
                            ,aScreen=s@TransactionScreen{tsTransaction=(_,t),tsTransactions=nts}
                            ,aPrevScreens=prevscreens
@@ -60,7 +60,7 @@
 tsInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
 
 tsDraw :: UIState -> [Widget Name]
-tsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}
+tsDraw UIState{aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}
               ,ajournal=j
               ,aScreen=TransactionScreen{tsTransaction=(i,t')
                                         ,tsTransactions=nts
@@ -77,7 +77,7 @@
     t = transactionMapPostingAmounts mixedAmountSetFullPrecision t'
     maincontent = Widget Greedy Greedy $ do
       let
-        prices = journalPriceOracle (infer_value_ ropts) j
+        prices = journalPriceOracle (infer_prices_ ropts) j
         styles = journalCommodityStyles j
         periodlast =
           fromMaybe (error' "TransactionScreen: expected a non-empty journal") $  -- PARTIAL: shouldn't happen
@@ -85,7 +85,7 @@
 
       render . defaultLayout toplabel bottomlabel . str
         . T.unpack . showTransactionOneLineAmounts
-        . maybe id (transactionApplyValuation prices styles periodlast (rsToday rspec)) (value_ ropts)
+        . maybe id (transactionApplyValuation prices styles periodlast (_rsDay rspec)) (value_ ropts)
         $ case cost_ ropts of
                Cost   -> transactionToCost styles t
                NoCost -> t
@@ -113,9 +113,10 @@
                 [] -> str ""
                 fs -> withAttr ("border" <> "query") (str $ " " ++ intercalate ", " fs)
 
-        bottomlabel = case mode of
+        bottomlabel = quickhelp
+                        -- case mode of
                         -- Minibuffer ed -> minibuffer ed
-                        _             -> quickhelp
+                        -- _             -> quickhelp
           where
             quickhelp = borderKeysStr [
                ("?", "help")
@@ -132,7 +133,7 @@
 
 tsHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
 tsHandle ui@UIState{aScreen=TransactionScreen{tsTransaction=(i,t), tsTransactions=nts}
-                   ,aopts=UIOpts{cliopts_=copts}
+                   ,aopts=UIOpts{uoCliOpts=copts}
                    ,ajournal=j
                    ,aMode=mode
                    }
@@ -146,26 +147,24 @@
         _                    -> helpHandle ui ev
 
     _ -> do
-      d <- liftIO getCurrentDay
       let
+        d = copts^.rsDay
         (iprev,tprev) = maybe (i,t) ((i-1),) $ lookup (i-1) nts
         (inext,tnext) = maybe (i,t) ((i+1),) $ lookup (i+1) nts
       case ev of
         VtyEvent (EvKey (KChar 'q') []) -> halt ui
         VtyEvent (EvKey KEsc        []) -> continue $ resetScreens d ui
-        VtyEvent (EvKey (KChar c)   []) | c `elem` ['?'] -> continue $ setMode Help ui
+        VtyEvent (EvKey (KChar c)   []) | c == '?' -> continue $ setMode Help ui
         VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui
           where
             (pos,f) = case tsourcepos t of
-                        GenericSourcePos f l c    -> (Just (l, Just c),f)
-                        JournalSourcePos f (l1,_) -> (Just (l1, Nothing),f)
+                        (SourcePos f l1 c1,_) -> (Just (unPos l1, Just $ unPos c1),f)
         AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
           continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
           where
             p = reportPeriod ui
         e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> do
           -- plog (if e == AppEvent FileChange then "file change" else "manual reload") "" `seq` return ()
-          d <- liftIO getCurrentDay
           ej <- liftIO $ journalReload copts
           case ej of
             Left err -> continue $ screenEnter d errorScreen{esError=err} ui
diff --git a/Hledger/UI/UIOptions.hs b/Hledger/UI/UIOptions.hs
--- a/Hledger/UI/UIOptions.hs
+++ b/Hledger/UI/UIOptions.hs
@@ -5,22 +5,32 @@
 
 module Hledger.UI.UIOptions
 where
-import Data.Default
+
+import Data.Default (def)
 import Data.List (intercalate)
-import System.Environment
+import qualified Data.Map as M
+import Data.Maybe (fromMaybe)
+import Lens.Micro (set)
+import System.Environment (getArgs)
 
-import Hledger.Cli hiding (progname,version,prognameandversion)
-import Hledger.UI.Theme (themeNames)
+import Hledger.Cli hiding (packageversion, progname, prognameandversion)
+import Hledger.UI.Theme (themes, themeNames)
 
-progname, version, prognameandversion :: String
-progname = "hledger-ui"
+-- cf Hledger.Cli.Version
+
+packageversion :: String
 #ifdef VERSION
-version = VERSION
+packageversion = VERSION
 #else
-version = ""
+packageversion = ""
 #endif
-prognameandversion = versiondescription progname
 
+progname :: String
+progname = "hledger-ui"
+
+prognameandversion :: String
+prognameandversion = versionStringForProgname progname
+
 uiflags = [
   -- flagNone ["debug-ui"] (setboolopt "rules-file") "run with no terminal output, showing console"
    flagNone ["watch"] (setboolopt "watch") "watch for data and date changes and reload automatically"
@@ -55,35 +65,35 @@
            }
 
 -- hledger-ui options, used in hledger-ui and above
-data UIOpts = UIOpts {
-     watch_       :: Bool
-    ,change_      :: Bool
-    ,cliopts_     :: CliOpts
- } deriving (Show)
+data UIOpts = UIOpts
+  { uoWatch    :: Bool
+  , uoTheme    :: Maybe String
+  , uoRegister :: Maybe String
+  , uoCliOpts  :: CliOpts
+  } deriving (Show)
 
 defuiopts = UIOpts
-  { watch_   = False
-  , change_  = False
-  , cliopts_ = def
+  { uoWatch    = False
+  , uoTheme    = Nothing
+  , uoRegister = Nothing
+  , uoCliOpts  = defcliopts
   }
 
--- instance Default CliOpts where def = defcliopts
-
+-- | Process a RawOpts into a UIOpts.
+-- This will return a usage error if provided an invalid theme.
 rawOptsToUIOpts :: RawOpts -> IO UIOpts
-rawOptsToUIOpts rawopts = checkUIOpts <$> do
-  cliopts <- rawOptsToCliOpts rawopts
-  return defuiopts {
-              watch_       = boolopt "watch" rawopts
-             ,change_      = boolopt "change" rawopts
-             ,cliopts_     = cliopts
-             }
-
-checkUIOpts :: UIOpts -> UIOpts
-checkUIOpts opts =
-  either usageError (const opts) $ do
-    case maybestringopt "theme" $ rawopts_ $ cliopts_ opts of
-      Just t | not $ elem t themeNames -> Left $ "invalid theme name: "++t
-      _                                -> Right ()
+rawOptsToUIOpts rawopts = do
+    cliopts <- set balanceaccum accum <$> rawOptsToCliOpts rawopts
+    return defuiopts {
+                uoWatch    = boolopt "watch" rawopts
+               ,uoTheme    = checkTheme <$> maybestringopt "theme" rawopts
+               ,uoRegister = maybestringopt "register" rawopts
+               ,uoCliOpts  = cliopts
+               }
+  where
+    -- show historical balance by default (unlike hledger)
+    accum = fromMaybe Historical $ balanceAccumulationOverride rawopts
+    checkTheme t = if t `M.member` themes then t else usageError $ "invalid theme name: " ++ t
 
 -- XXX some refactoring seems due
 getHledgerUIOpts :: IO UIOpts
@@ -93,3 +103,21 @@
   let args' = replaceNumericFlags args
   let cmdargopts = either usageError id $ process uimode args'
   rawOptsToUIOpts cmdargopts
+
+instance HasCliOpts UIOpts where
+    cliOpts f uiopts = (\x -> uiopts{uoCliOpts=x}) <$> f (uoCliOpts uiopts)
+
+instance HasInputOpts UIOpts where
+    inputOpts = cliOpts.inputOpts
+
+instance HasBalancingOpts UIOpts where
+    balancingOpts = cliOpts.balancingOpts
+
+instance HasReportSpec UIOpts where
+    reportSpec = cliOpts.reportSpec
+
+instance HasReportOptsNoUpdate UIOpts where
+    reportOptsNoUpdate = cliOpts.reportOptsNoUpdate
+
+instance HasReportOpts UIOpts where
+    reportOpts = cliOpts.reportOpts
diff --git a/Hledger/UI/UIState.hs b/Hledger/UI/UIState.hs
--- a/Hledger/UI/UIState.hs
+++ b/Hledger/UI/UIState.hs
@@ -7,32 +7,30 @@
 where
 
 import Brick.Widgets.Edit
-import Control.Applicative ((<|>))
+import Data.Foldable (asum)
+import Data.Either (fromRight)
 import Data.List ((\\), foldl', sort)
 import Data.Semigroup (Max(..))
 import qualified Data.Text as T
 import Data.Text.Zipper (gotoEOL)
 import Data.Time.Calendar (Day)
+import Lens.Micro ((^.), over, set)
 
 import Hledger
 import Hledger.Cli.CliOptions
 import Hledger.UI.UITypes
-import Hledger.UI.UIOptions
 
 -- | Toggle between showing only unmarked items or all items.
 toggleUnmarked :: UIState -> UIState
-toggleUnmarked ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=reportSpecToggleStatus Unmarked copts rspec}}}
+toggleUnmarked = over statuses (toggleStatus1 Unmarked)
 
 -- | Toggle between showing only pending items or all items.
 togglePending :: UIState -> UIState
-togglePending ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=reportSpecToggleStatus Pending copts rspec}}}
+togglePending = over statuses (toggleStatus1 Pending)
 
 -- | Toggle between showing only cleared items or all items.
 toggleCleared :: UIState -> UIState
-toggleCleared ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=reportSpecToggleStatus Cleared copts rspec}}}
+toggleCleared = over statuses (toggleStatus1 Cleared)
 
 -- TODO testing different status toggle styles
 
@@ -52,17 +50,11 @@
     showstatus Pending  = "pending"
     showstatus Unmarked = "unmarked"
 
-reportSpecToggleStatus :: Status -> CliOpts -> ReportSpec -> ReportSpec
-reportSpecToggleStatus s _copts =
-    either (error "reportSpecToggleStatus: changing status should not have caused this error") id  -- PARTIAL:
-    . updateReportSpecWith (reportOptsToggleStatus1 s)
-
 -- various toggle behaviours:
 
 -- 1 UPC toggles only X/all
-reportOptsToggleStatus1 s ropts@ReportOpts{statuses_=ss}
-  | ss == [s]  = ropts{statuses_=[]}
-  | otherwise = ropts{statuses_=[s]}
+toggleStatus1 :: Status -> [Status] -> [Status]
+toggleStatus1 s ss = if ss == [s] then [] else [s]
 
 -- 2 UPC cycles X/not-X/all
 -- repeatedly pressing X cycles:
@@ -72,25 +64,25 @@
 -- pressing Y after first or second step starts new cycle:
 -- [u] P [p]
 -- [pc] P [p]
--- reportOptsToggleStatus2 s ropts@ReportOpts{statuses_=ss}
---   | ss == [s]            = ropts{statuses_=complement [s]}
---   | ss == complement [s] = ropts{statuses_=[]}
---   | otherwise            = ropts{statuses_=[s]}  -- XXX assume only three values
+-- toggleStatus2 s ss
+--   | ss == [s]            = complement [s]
+--   | ss == complement [s] = []
+--   | otherwise            = [s]  -- XXX assume only three values
 
 -- 3 UPC toggles each X
--- reportOptsToggleStatus3 s ropts@ReportOpts{statuses_=ss}
---   | s `elem` ss = ropts{statuses_=filter (/= s) ss}
---   | otherwise   = ropts{statuses_=simplifyStatuses (s:ss)}
+-- toggleStatus3 s ss
+--   | s `elem` ss = filter (/= s) ss
+--   | otherwise   = simplifyStatuses (s:ss)
 
 -- 4 upc sets X, UPC sets not-X
---reportOptsToggleStatus4 s ropts@ReportOpts{statuses_=ss}
---  | s `elem` ss = ropts{statuses_=filter (/= s) ss}
---  | otherwise   = ropts{statuses_=simplifyStatuses (s:ss)}
+-- toggleStatus4 s ss
+--  | s `elem` ss = filter (/= s) ss
+--  | otherwise   = simplifyStatuses (s:ss)
 
 -- 5 upc toggles X, UPC toggles not-X
---reportOptsToggleStatus5 s ropts@ReportOpts{statuses_=ss}
---  | s `elem` ss = ropts{statuses_=filter (/= s) ss}
---  | otherwise   = ropts{statuses_=simplifyStatuses (s:ss)}
+-- toggleStatus5 s ss
+--  | s `elem` ss = filter (/= s) ss
+--  | otherwise   = simplifyStatuses (s:ss)
 
 -- | Given a list of unique enum values, list the other possible values of that enum.
 complement :: (Bounded a, Enum a, Eq a) => [a] -> [a]
@@ -100,56 +92,44 @@
 
 -- | Toggle between showing all and showing only nonempty (more precisely, nonzero) items.
 toggleEmpty :: UIState -> UIState
-toggleEmpty ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{rsOpts=toggleEmpty ropts}}}}
-  where
-    toggleEmpty ropts = ropts{empty_=not $ empty_ ropts}
+toggleEmpty = over empty__ not
 
 -- | Toggle between showing the primary amounts or costs.
 toggleCost :: UIState -> UIState
-toggleCost ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-    ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{rsOpts=ropts{cost_ = toggle $ cost_ ropts}}}}}
+toggleCost = over cost toggleCostMode
   where
-    toggle Cost   = NoCost
-    toggle NoCost = Cost
+    toggleCostMode Cost   = NoCost
+    toggleCostMode NoCost = Cost
 
 -- | Toggle between showing primary amounts or default valuation.
 toggleValue :: UIState -> UIState
-toggleValue ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{rsOpts=ropts{
-    value_ = valuationToggleValue $ value_ ropts}}}}}
-
--- | Basic toggling of -V, for hledger-ui.
-valuationToggleValue :: Maybe ValuationType -> Maybe ValuationType
-valuationToggleValue (Just (AtEnd _)) = Nothing
-valuationToggleValue _                = Just $ AtEnd Nothing
+toggleValue = over value valuationToggleValue
+  where
+    -- | Basic toggling of -V, for hledger-ui.
+    valuationToggleValue (Just (AtEnd _)) = Nothing
+    valuationToggleValue _                = Just $ AtEnd Nothing
 
 -- | Set hierarchic account tree mode.
 setTree :: UIState -> UIState
-setTree ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{rsOpts=ropts{accountlistmode_=ALTree}}}}}
+setTree = set accountlistmode ALTree
 
 -- | Set flat account list mode.
 setList :: UIState -> UIState
-setList ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{rsOpts=ropts{accountlistmode_=ALFlat}}}}}
+setList = set accountlistmode ALFlat
 
 -- | Toggle between flat and tree mode. If current mode is unspecified/default, assume it's flat.
 toggleTree :: UIState -> UIState
-toggleTree ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{rsOpts=toggleTreeMode ropts}}}}
+toggleTree = over accountlistmode toggleTreeMode
   where
-    toggleTreeMode ropts
-      | accountlistmode_ ropts == ALTree = ropts{accountlistmode_=ALFlat}
-      | otherwise                        = ropts{accountlistmode_=ALTree}
+    toggleTreeMode ALTree = ALFlat
+    toggleTreeMode ALFlat = ALTree
 
 -- | Toggle between historical balances and period balances.
 toggleHistorical :: UIState -> UIState
-toggleHistorical ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{rsOpts=ropts{balancetype_=b}}}}}
+toggleHistorical = over balanceaccum toggleBalanceAccum
   where
-    b | balancetype_ ropts == HistoricalBalance = PeriodChange
-      | otherwise                               = HistoricalBalance
+    toggleBalanceAccum Historical = PerPeriod
+    toggleBalanceAccum _          = Historical
 
 -- | Toggle hledger-ui's "forecast/future mode". When this mode is enabled,
 -- hledger-shows regular transactions which have future dates, and
@@ -157,32 +137,33 @@
 -- (which are usually but not necessarily future-dated).
 -- In normal mode, both of these are hidden.
 toggleForecast :: Day -> UIState -> UIState
-toggleForecast d ui@UIState{aopts=UIOpts{cliopts_=copts@CliOpts{inputopts_=iopts}}} =
-  uiSetForecast ui $
-    case forecast_ iopts of
+toggleForecast _d ui = set forecast newForecast ui
+  where
+    newForecast = case ui^.forecast of
       Just _  -> Nothing
-      Nothing -> forecastPeriodFromRawOpts d (rawopts_ copts) <|> Just nulldatespan
+      Nothing -> enableForecastPreservingPeriod ui (ui^.cliOpts) ^. forecast
 
--- | Helper: set forecast mode (with the given forecast period) on or off in the UI state.
-uiSetForecast :: UIState -> Maybe DateSpan -> UIState
-uiSetForecast
-  ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{inputopts_=iopts}}}
-  mforecast =
-  -- we assume forecast mode has no effect on ReportSpec's derived fields
-  ui{aopts=uopts{cliopts_=copts{inputopts_=iopts{forecast_=mforecast}}}}
+-- | Ensure this CliOpts enables forecasted transactions.
+-- If a forecast period was specified in the old CliOpts,
+-- or in the provided UIState's startup options,
+-- it is preserved.
+enableForecastPreservingPeriod :: UIState -> CliOpts -> CliOpts
+enableForecastPreservingPeriod ui copts = set forecast mforecast copts
+  where
+    mforecast = asum [mprovidedforecastperiod, mstartupforecastperiod, mdefaultforecastperiod]
+      where
+        mprovidedforecastperiod = copts ^. forecast
+        mstartupforecastperiod  = astartupopts ui ^. forecast
+        mdefaultforecastperiod  = Just nulldatespan
 
 -- | Toggle between showing all and showing only real (non-virtual) items.
 toggleReal :: UIState -> UIState
-toggleReal ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}} =
-    ui{aopts=uopts{cliopts_=copts{reportspec_=update rspec}}}
-  where
-    update = either (error "toggleReal: updating Real should not result in an error") id  -- PARTIAL:
-           . updateReportSpecWith (\ropts -> ropts{real_=not $ real_ ropts})
+toggleReal = fromRight err . overEither real not  -- PARTIAL:
+  where err = error "toggleReal: updating Real should not result in an error"
 
 -- | Toggle the ignoring of balance assertions.
 toggleIgnoreBalanceAssertions :: UIState -> UIState
-toggleIgnoreBalanceAssertions ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{inputopts_=iopts@InputOpts{balancingopts_=bopts}}}} =
-  ui{aopts=uopts{cliopts_=copts{inputopts_=iopts{balancingopts_=bopts{ignore_assertions_=not $ ignore_assertions_ bopts}}}}}
+toggleIgnoreBalanceAssertions = over ignore_assertions not
 
 -- | Step through larger report periods, up to all.
 growReportPeriod :: Day -> UIState -> UIState
@@ -207,47 +188,34 @@
 moveReportPeriodToDate :: Day -> UIState -> UIState
 moveReportPeriodToDate d = updateReportPeriod (periodMoveTo d)
 
+-- | Clear any report period limits.
+resetReportPeriod :: UIState -> UIState
+resetReportPeriod = setReportPeriod PeriodAll
+
 -- | Get the report period.
 reportPeriod :: UIState -> Period
-reportPeriod = period_ . rsOpts . reportspec_ . cliopts_ . aopts
+reportPeriod = (^.period)
 
 -- | Set the report period.
 setReportPeriod :: Period -> UIState -> UIState
 setReportPeriod p = updateReportPeriod (const p)
 
--- | Clear any report period limits.
-resetReportPeriod :: UIState -> UIState
-resetReportPeriod = setReportPeriod PeriodAll
-
 -- | Update report period by a applying a function.
 updateReportPeriod :: (Period -> Period) -> UIState -> UIState
-updateReportPeriod updatePeriod ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}} =
-    ui{aopts=uopts{cliopts_=copts{reportspec_=update rspec}}}
-  where
-    update = either (error "updateReportPeriod: updating period should not result in an error") id  -- PARTIAL:
-           . updateReportSpecWith (\ropts -> ropts{period_=updatePeriod $ period_ ropts})
+updateReportPeriod updatePeriod = fromRight err . overEither period updatePeriod  -- PARTIAL:
+  where err = error "updateReportPeriod: updating period should not result in an error"
 
 -- | Apply a new filter query.
 setFilter :: String -> UIState -> UIState
-setFilter s ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}} =
-    ui{aopts=uopts{cliopts_=copts{reportspec_=update rspec}}}
+setFilter s = over reportSpec update
   where
-    update = either (const rspec) id . updateReportSpecWith (\ropts -> ropts{querystring_=querystring})  -- XXX silently ignores an error
-    querystring = words'' prefixes $ T.pack s
+    update rspec = fromRight rspec $ setEither querystring (words'' prefixes $ T.pack s) rspec  -- XXX silently ignores an error
 
 -- | Reset some filters & toggles.
 resetFilter :: UIState -> UIState
-resetFilter ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} =
-  ui{aopts=uopts{cliopts_=copts{reportspec_=rspec{
-     rsQuery=Any
-    ,rsQueryOpts=[]
-    ,rsOpts=ropts{
-       empty_=True
-      ,statuses_=[]
-      ,real_=False
-      ,querystring_=[]
-      --,period_=PeriodAll
-    }}}}}
+resetFilter = set querystringNoUpdate [] . set realNoUpdate False . set statusesNoUpdate []
+            . set empty__ True  -- set period PeriodAll
+            . set rsQuery Any . set rsQueryOpts []
 
 -- | Reset all options state to exactly what it was at startup
 -- (preserving any command-line options/arguments).
@@ -282,15 +250,14 @@
 setDepth mdepth = updateReportDepth (const mdepth)
 
 getDepth :: UIState -> Maybe Int
-getDepth = depth_ . rsOpts . reportspec_ . cliopts_ . aopts
+getDepth = (^.depth)
 
 -- | Update report depth by a applying a function. If asked to set a depth less
 -- than zero, it will leave it unchanged.
 updateReportDepth :: (Maybe Int -> Maybe Int) -> UIState -> UIState
-updateReportDepth updateDepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}}} =
-    ui{aopts=uopts{cliopts_=copts{reportspec_=update rspec}}}
+updateReportDepth updateDepth ui = over reportSpec update ui
   where
-    update = either (error "updateReportDepth: updating depth should not result in an error") id  -- PARTIAL:
+    update = fromRight (error "updateReportDepth: updating depth should not result in an error")  -- PARTIAL:
            . updateReportSpecWith (\ropts -> ropts{depth_=updateDepth (depth_ ropts) >>= clipDepth ropts})
     clipDepth ropts d | d < 0            = depth_ ropts
                       | d >= maxDepth ui = Nothing
@@ -301,8 +268,7 @@
 showMinibuffer ui = setMode (Minibuffer e) ui
   where
     e = applyEdit gotoEOL $ editor MinibufferEditor (Just 1) oldq
-    oldq = T.unpack . T.unwords . map textQuoteIfNeeded
-         . querystring_ . rsOpts . reportspec_ . cliopts_ $ aopts ui
+    oldq = T.unpack . T.unwords . map textQuoteIfNeeded $ ui^.querystring
 
 -- | Close the minibuffer, discarding any edit in progress.
 closeMinibuffer :: UIState -> UIState
@@ -351,4 +317,3 @@
 screenEnter d scr ui = (sInit scr) d True $
                        pushScreen scr
                        ui
-
diff --git a/Hledger/UI/UITypes.hs b/Hledger/UI/UITypes.hs
--- a/Hledger/UI/UITypes.hs
+++ b/Hledger/UI/UITypes.hs
@@ -48,6 +48,7 @@
   -- import the Show instance for functions. Warning, this also re-exports it
 
 import Hledger
+import Hledger.Cli (HasCliOpts(..))
 import Hledger.UI.UIOptions
 
 -- | hledger-ui's application state. This holds one or more stateful screens.
@@ -160,7 +161,24 @@
 --    mempty        = list "" V.empty 1  -- XXX problem in 0.7, every list requires a unique Name
 --    mappend l1 l2 = l1 & listElementsL .~ (l1^.listElementsL <> l2^.listElementsL)
 
-concat <$> mapM makeLenses [
-   ''Screen
-  ]
+makeLenses ''Screen
 
+uioptslens f ui = (\x -> ui{aopts=x}) <$> f (aopts ui)
+
+instance HasCliOpts UIState where
+    cliOpts = uioptslens.cliOpts
+
+instance HasInputOpts UIState where
+    inputOpts = uioptslens.inputOpts
+
+instance HasBalancingOpts UIState where
+    balancingOpts = uioptslens.balancingOpts
+
+instance HasReportSpec UIState where
+    reportSpec = uioptslens.reportSpec
+
+instance HasReportOptsNoUpdate UIState where
+    reportOptsNoUpdate = uioptslens.reportOptsNoUpdate
+
+instance HasReportOpts UIState where
+    reportOpts = uioptslens.reportOpts
diff --git a/Hledger/UI/UIUtils.hs b/Hledger/UI/UIUtils.hs
--- a/Hledger/UI/UIUtils.hs
+++ b/Hledger/UI/UIUtils.hs
@@ -24,6 +24,7 @@
   ,scrollSelectionToMiddle
   ,suspend
   ,redraw
+  ,reportSpecSetFutureAndForecast
 )
 where
 
@@ -34,15 +35,17 @@
 import Brick.Widgets.Edit
 import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL)
 import Control.Monad.IO.Class
+import Data.Bifunctor (second)
 import Data.List
 import qualified Data.Text as T
+import Data.Time (Day, addDays)
 import Graphics.Vty
   (Event(..),Key(..),Modifier(..),Vty(..),Color,Attr,currentAttr,refresh
   -- ,Output(displayBounds,mkDisplayContext),DisplayContext(..)
   )
 import Lens.Micro.Platform
 
-import Hledger hiding (Color)
+import Hledger
 import Hledger.Cli (CliOpts)
 import Hledger.Cli.DocFiles
 import Hledger.UI.UITypes
@@ -190,7 +193,7 @@
 borderPeriodStr preposition p         = str (" "++preposition++" ") <+> withAttr ("border" <> "query") (str . T.unpack $ showPeriod p)
 
 borderKeysStr :: [(String,String)] -> Widget Name
-borderKeysStr = borderKeysStr' . map (\(a,b) -> (a, str b))
+borderKeysStr = borderKeysStr' . map (second str)
 
 borderKeysStr' :: [(String,Widget Name)] -> Widget Name
 borderKeysStr' keydescs =
@@ -280,8 +283,7 @@
 -- XXX May disrupt border style of inner widgets.
 -- XXX Should reduce the available size visible to inner widget, but doesn't seem to (cf rsDraw2).
 margin :: Int -> Int -> Maybe Color -> Widget Name -> Widget Name
-margin h v mcolour = \w ->
-  Widget Greedy Greedy $ do
+margin h v mcolour w = Widget Greedy Greedy $ do
     c <- getContext
     let w' = vLimit (c^.availHeightL - v*2) $ hLimit (c^.availWidthL - h*2) w
         attr = maybe currentAttr (\c -> c `on` c) mcolour
@@ -342,3 +344,19 @@
   | ev `elem` moveLeftEvents  = EvKey KLeft []
   | ev `elem` moveRightEvents = EvKey KRight []
   | otherwise = ev
+
+-- | Update the ReportSpec's query to exclude future transactions (later than the given day)
+-- and forecast transactions (generated by --forecast), if the given forecast DateSpan is Nothing,
+-- and include them otherwise.
+reportSpecSetFutureAndForecast :: Day -> Maybe DateSpan -> ReportSpec -> ReportSpec
+reportSpecSetFutureAndForecast d forecast rspec =
+    rspec{_rsQuery=simplifyQuery $ And [_rsQuery rspec, periodq, excludeforecastq forecast]}
+  where
+    periodq = Date . periodAsDateSpan . period_ $ _rsReportOpts rspec
+    -- Except in forecast mode, exclude future/forecast transactions.
+    excludeforecastq (Just _) = Any
+    excludeforecastq Nothing  =  -- not:date:tomorrow- not:tag:generated-transaction
+      And [
+         Not (Date $ DateSpan (Just $ addDays 1 d) Nothing)
+        ,Not generatedTransactionTag
+      ]
diff --git a/hledger-ui.1 b/hledger-ui.1
--- a/hledger-ui.1
+++ b/hledger-ui.1
@@ -1,5 +1,5 @@
 
-.TH "HLEDGER-UI" "1" "August 2021" "hledger-ui-1.22.2 " "hledger User Manuals"
+.TH "HLEDGER-UI" "1" "September 2021" "hledger-ui-1.23 " "hledger User Manuals"
 
 
 
@@ -7,7 +7,7 @@
 .PP
 hledger-ui is a terminal interface (TUI) for the hledger accounting
 tool.
-This manual is for hledger-ui 1.22.2.
+This manual is for hledger-ui 1.23.
 .SH SYNOPSIS
 .PP
 \f[C]hledger-ui [OPTIONS] [QUERYARGS]\f[R]
@@ -126,6 +126,10 @@
 \f[B]\f[CB]--date2\f[B]\f[R]
 match the secondary date instead (see command help for other effects)
 .TP
+\f[B]\f[CB]--today=DATE\f[B]\f[R]
+override today\[aq]s date (affects relative smart dates, for
+tests/examples)
+.TP
 \f[B]\f[CB]-U --unmarked\f[B]\f[R]
 include only unmarked postings/txns (can combine with -P or -C)
 .TP
@@ -169,6 +173,10 @@
 next 6 months or till report end date.
 In hledger-ui, also make ordinary future transactions visible.
 .TP
+\f[B]\f[CB]--commodity-style\f[B]\f[R]
+Override the commodity style in the output for the specified commodity.
+For example \[aq]EUR1.000,00\[aq].
+.TP
 \f[B]\f[CB]--color=WHEN (or --colour=WHEN)\f[B]\f[R]
 Should color-supporting commands use ANSI color codes in text output.
 \[aq]auto\[aq] (default): whenever stdout seems to be a color-supporting
@@ -177,6 +185,14 @@
 into \[aq]less -R\[aq].
 \[aq]never\[aq] or \[aq]no\[aq]: never.
 A NO_COLOR environment variable overrides this.
+.TP
+\f[B]\f[CB]--pretty[=WHEN]\f[B]\f[R]
+Show prettier output, e.g.
+using unicode box-drawing characters.
+Accepts \[aq]yes\[aq] (the default) or \[aq]no\[aq] (\[aq]y\[aq],
+\[aq]n\[aq], \[aq]always\[aq], \[aq]never\[aq] also work).
+If you provide an argument you must use \[aq]=\[aq], e.g.
+\[aq]--pretty=yes\[aq].
 .PP
 When a reporting option appears more than once in the command line, the
 last one takes precedence.
diff --git a/hledger-ui.cabal b/hledger-ui.cabal
--- a/hledger-ui.cabal
+++ b/hledger-ui.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hledger-ui
-version:        1.22.2
+version:        1.23
 synopsis:       Curses-style terminal interface for the hledger accounting system
 description:    A simple curses-style terminal user interface for the hledger accounting system.
                 It can be a more convenient way to browse your accounts than the CLI.
@@ -27,7 +27,7 @@
 license-file:   LICENSE
 build-type:     Simple
 tested-with:
-    GHC==8.6.5, GHC==8.8.4, GHC==8.10.4
+    GHC==8.6.5, GHC==8.8.4, GHC==8.10.4, GHC==9.0.1
 extra-source-files:
     CHANGES.md
     README.md
@@ -63,11 +63,11 @@
   hs-source-dirs:
       ./
   ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans
-  cpp-options: -DVERSION="1.22.2"
+  cpp-options: -DVERSION="1.23"
   build-depends:
       ansi-terminal >=0.9
     , async
-    , base >=4.10.1.0 && <4.16
+    , base >=4.11 && <4.16
     , base-compat-batteries >=0.10.1 && <0.12
     , brick >=0.23
     , cmdargs >=0.8
@@ -77,8 +77,8 @@
     , extra >=1.6.3
     , filepath
     , fsnotify >=0.2.1.2 && <0.4
-    , hledger >=1.22.2 && <1.23
-    , hledger-lib >=1.22.2 && <1.23
+    , hledger ==1.23.*
+    , hledger-lib ==1.23.*
     , megaparsec >=7.0.0 && <9.2
     , microlens >=0.4
     , microlens-platform >=0.2.3.1
diff --git a/hledger-ui.info b/hledger-ui.info
--- a/hledger-ui.info
+++ b/hledger-ui.info
@@ -12,7 +12,7 @@
 *************
 
 hledger-ui is a terminal interface (TUI) for the hledger accounting
-tool.  This manual is for hledger-ui 1.22.2.
+tool.  This manual is for hledger-ui 1.23.
 
    'hledger-ui [OPTIONS] [QUERYARGS]'
 'hledger ui -- [OPTIONS] [QUERYARGS]'
@@ -144,6 +144,10 @@
 
      match the secondary date instead (see command help for other
      effects)
+'--today=DATE'
+
+     override today's date (affects relative smart dates, for
+     tests/examples)
 '-U --unmarked'
 
      include only unmarked postings/txns (can combine with -P or -C)
@@ -189,6 +193,10 @@
      generate future transactions from periodic transaction rules, for
      the next 6 months or till report end date.  In hledger-ui, also
      make ordinary future transactions visible.
+'--commodity-style'
+
+     Override the commodity style in the output for the specified
+     commodity.  For example 'EUR1.000,00'.
 '--color=WHEN (or --colour=WHEN)'
 
      Should color-supporting commands use ANSI color codes in text
@@ -196,7 +204,13 @@
      color-supporting terminal.  'always' or 'yes': always, useful eg
      when piping output into 'less -R'. 'never' or 'no': never.  A
      NO_COLOR environment variable overrides this.
+'--pretty[=WHEN]'
 
+     Show prettier output, e.g.  using unicode box-drawing characters.
+     Accepts 'yes' (the default) or 'no' ('y', 'n', 'always', 'never'
+     also work).  If you provide an argument you must use '=', e.g.
+     '-pretty=yes'.
+
    When a reporting option appears more than once in the command line,
 the last one takes precedence.
 
@@ -594,32 +608,32 @@
 
 Tag Table:
 Node: Top221
-Node: OPTIONS1646
-Ref: #options1743
-Node: KEYS6144
-Ref: #keys6239
-Node: SCREENS10310
-Ref: #screens10408
-Node: Accounts screen10498
-Ref: #accounts-screen10626
-Node: Register screen12841
-Ref: #register-screen12996
-Node: Transaction screen14993
-Ref: #transaction-screen15151
-Node: Error screen16021
-Ref: #error-screen16143
-Node: TIPS16387
-Ref: #tips16486
-Node: Watch mode16538
-Ref: #watch-mode16655
-Node: Watch mode limitations17401
-Ref: #watch-mode-limitations17542
-Node: ENVIRONMENT18678
-Ref: #environment18789
-Node: FILES19596
-Ref: #files19695
-Node: BUGS19908
-Ref: #bugs19985
+Node: OPTIONS1644
+Ref: #options1741
+Node: KEYS6620
+Ref: #keys6715
+Node: SCREENS10786
+Ref: #screens10884
+Node: Accounts screen10974
+Ref: #accounts-screen11102
+Node: Register screen13317
+Ref: #register-screen13472
+Node: Transaction screen15469
+Ref: #transaction-screen15627
+Node: Error screen16497
+Ref: #error-screen16619
+Node: TIPS16863
+Ref: #tips16962
+Node: Watch mode17014
+Ref: #watch-mode17131
+Node: Watch mode limitations17877
+Ref: #watch-mode-limitations18018
+Node: ENVIRONMENT19154
+Ref: #environment19265
+Node: FILES20072
+Ref: #files20171
+Node: BUGS20384
+Ref: #bugs20461
 
 End Tag Table
 
diff --git a/hledger-ui.txt b/hledger-ui.txt
--- a/hledger-ui.txt
+++ b/hledger-ui.txt
@@ -5,7 +5,7 @@
 
 NAME
        hledger-ui  is  a  terminal  interface (TUI) for the hledger accounting
-       tool.  This manual is for hledger-ui 1.22.2.
+       tool.  This manual is for hledger-ui 1.23.
 
 SYNOPSIS
        hledger-ui [OPTIONS] [QUERYARGS]
@@ -122,6 +122,10 @@
               match the secondary date instead (see  command  help  for  other
               effects)
 
+       --today=DATE
+              override   today's  date  (affects  relative  smart  dates,  for
+              tests/examples)
+
        -U --unmarked
               include only unmarked postings/txns (can combine with -P or -C)
 
@@ -138,34 +142,38 @@
               hide/aggregate accounts or postings more than NUM levels deep
 
        -E --empty
-              show  items with zero amount, normally hidden (and vice-versa in
+              show items with zero amount, normally hidden (and vice-versa  in
               hledger-ui/hledger-web)
 
        -B --cost
               convert amounts to their cost/selling amount at transaction time
 
        -V --market
-              convert  amounts to their market value in default valuation com-
+              convert amounts to their market value in default valuation  com-
               modities
 
        -X --exchange=COMM
               convert amounts to their market value in commodity COMM
 
        --value
-              convert amounts to cost or  market  value,  more  flexibly  than
+              convert  amounts  to  cost  or  market value, more flexibly than
               -B/-V/-X
 
        --infer-market-prices
-              use  transaction  prices  (recorded  with @ or @@) as additional
+              use transaction prices (recorded with @  or  @@)  as  additional
               market prices, as if they were P directives
 
        --auto apply automated posting rules to modify transactions.
 
        --forecast
-              generate future transactions from  periodic  transaction  rules,
-              for  the  next 6 months or till report end date.  In hledger-ui,
+              generate  future  transactions  from periodic transaction rules,
+              for the next 6 months or till report end date.   In  hledger-ui,
               also make ordinary future transactions visible.
 
+       --commodity-style
+              Override  the  commodity  style  in the output for the specified
+              commodity.  For example 'EUR1.000,00'.
+
        --color=WHEN (or --colour=WHEN)
               Should color-supporting commands use ANSI color  codes  in  text
               output.   'auto' (default): whenever stdout seems to be a color-
@@ -173,6 +181,12 @@
               piping  output  into  'less  -R'.   'never'  or  'no': never.  A
               NO_COLOR environment variable overrides this.
 
+       --pretty[=WHEN]
+              Show prettier output, e.g.  using  unicode  box-drawing  charac-
+              ters.   Accepts 'yes' (the default) or 'no' ('y', 'n', 'always',
+              'never' also work).  If you provide an  argument  you  must  use
+              '=', e.g.  '--pretty=yes'.
+
        When a reporting option appears more than once in the command line, the
        last one takes precedence.
 
@@ -194,86 +208,86 @@
               show debug output (levels 1-9, default: 1)
 
        A @FILE argument will be expanded to the contents of FILE, which should
-       contain one command line option/argument per line.  (To  prevent  this,
+       contain  one  command line option/argument per line.  (To prevent this,
        insert a -- argument before.)
 
 KEYS
-       ?  shows a help dialog listing all keys.  (Some of these also appear in
+       ? shows a help dialog listing all keys.  (Some of these also appear  in
        the quick help at the bottom of each screen.) Press ? again (or ESCAPE,
        or LEFT, or q) to close it.  The following keys work on most screens:
 
        The cursor keys navigate: right (or enter) goes deeper, left returns to
-       the previous screen, up/down/page up/page  down/home/end  move  up  and
+       the  previous  screen,  up/down/page  up/page down/home/end move up and
        down through lists.  Emacs-style (ctrl-p/ctrl-n/ctrl-f/ctrl-b) movement
-       keys are also supported (but not  vi-style  keys,  since  hledger-1.19,
-       sorry!).   A  tip:  movement  speed  is limited by your keyboard repeat
-       rate, to move faster you may want to adjust it.  (If you're on  a  mac,
+       keys  are  also  supported  (but not vi-style keys, since hledger-1.19,
+       sorry!).  A tip: movement speed is  limited  by  your  keyboard  repeat
+       rate,  to  move faster you may want to adjust it.  (If you're on a mac,
        the karabiner app is one way to do that.)
 
-       With  shift pressed, the cursor keys adjust the report period, limiting
-       the transactions to be shown  (by  default,  all  are  shown).   shift-
-       down/up  steps downward and upward through these standard report period
-       durations: year, quarter, month,  week,  day.   Then,  shift-left/right
-       moves  to the previous/next period.  T sets the report period to today.
-       With the --watch option, when viewing a "current" period  (the  current
+       With shift pressed, the cursor keys adjust the report period,  limiting
+       the  transactions  to  be  shown  (by  default, all are shown).  shift-
+       down/up steps downward and upward through these standard report  period
+       durations:  year,  quarter,  month,  week, day.  Then, shift-left/right
+       moves to the previous/next period.  T sets the report period to  today.
+       With  the  --watch option, when viewing a "current" period (the current
        day, week, month, quarter, or year), the period will move automatically
        to track the current date.  To set a non-standard period, you can use /
        and a date: query.
 
-       /  lets  you  set a general filter query limiting the data shown, using
-       the same query terms as in hledger and hledger-web.  While editing  the
-       query,  you  can  use CTRL-a/e/d/k, BS, cursor keys; press ENTER to set
+       / lets you set a general filter query limiting the  data  shown,  using
+       the  same query terms as in hledger and hledger-web.  While editing the
+       query, you can use CTRL-a/e/d/k, BS, cursor keys; press  ENTER  to  set
        it, or ESCAPEto cancel.  There are also keys for quickly adjusting some
-       common  filters  like account depth and transaction status (see below).
+       common filters like account depth and transaction status  (see  below).
        BACKSPACE or DELETE removes all filters, showing all transactions.
 
-       As mentioned above, by default hledger-ui hides future  transactions  -
+       As  mentioned  above, by default hledger-ui hides future transactions -
        both ordinary transactions recorded in the journal, and periodic trans-
-       actions  generated  by  rule.   F  toggles  forecast  mode,  in   which
+       actions   generated  by  rule.   F  toggles  forecast  mode,  in  which
        future/forecasted transactions are shown.
 
-       ESCAPE  resets the UI state and jumps back to the top screen, restoring
-       the app's initial state at startup.  Or,  it  cancels  minibuffer  data
+       ESCAPE resets the UI state and jumps back to the top screen,  restoring
+       the  app's  initial  state  at startup.  Or, it cancels minibuffer data
        entry or the help dialog.
 
        CTRL-l redraws the screen and centers the selection if possible (selec-
-       tions near the top won't be centered, since we don't scroll  above  the
+       tions  near  the top won't be centered, since we don't scroll above the
        top).
 
-       g  reloads from the data file(s) and updates the current screen and any
-       previous screens.  (With large files, this  could  cause  a  noticeable
+       g reloads from the data file(s) and updates the current screen and  any
+       previous  screens.   (With  large  files, this could cause a noticeable
        pause.)
 
-       I  toggles  balance  assertion  checking.  Disabling balance assertions
+       I toggles balance assertion  checking.   Disabling  balance  assertions
        temporarily can be useful for troubleshooting.
 
-       a runs command-line hledger's add  command,  and  reloads  the  updated
+       a  runs  command-line  hledger's  add  command, and reloads the updated
        file.  This allows some basic data entry.
 
-       A  is like a, but runs the hledger-iadd tool, which provides a terminal
-       interface.  This key will be available if hledger-iadd is installed  in
+       A is like a, but runs the hledger-iadd tool, which provides a  terminal
+       interface.   This key will be available if hledger-iadd is installed in
        $path.
 
-       E  runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default (emacsclient -a ""
-       -nw) on the journal file.  With some editors (emacs,  vi),  the  cursor
-       will  be  positioned  at  the current transaction when invoked from the
-       register and transaction screens, and at the error location (if  possi-
+       E runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default (emacsclient -a  ""
+       -nw)  on  the  journal file.  With some editors (emacs, vi), the cursor
+       will be positioned at the current transaction  when  invoked  from  the
+       register  and transaction screens, and at the error location (if possi-
        ble) when invoked from the error screen.
 
-       B  toggles cost mode, showing amounts in their transaction price's com-
+       B toggles cost mode, showing amounts in their transaction price's  com-
        modity (like toggling the -B/--cost flag).
 
-       V toggles value mode, showing amounts' current market  value  in  their
-       default  valuation  commodity  (like  toggling  the  -V/--market flag).
-       Note, "current market value" means the value on the report end date  if
-       specified,  otherwise today.  To see the value on another date, you can
-       temporarily set that as the report end date.  Eg: to see a  transaction
-       as  it  was  valued  on july 30, go to the accounts or register screen,
+       V  toggles  value  mode, showing amounts' current market value in their
+       default valuation  commodity  (like  toggling  the  -V/--market  flag).
+       Note,  "current market value" means the value on the report end date if
+       specified, otherwise today.  To see the value on another date, you  can
+       temporarily  set that as the report end date.  Eg: to see a transaction
+       as it was valued on july 30, go to the  accounts  or  register  screen,
        press /, and add date:-7/30 to the query.
 
        At most one of cost or value mode can be active at once.
 
-       There's not yet any visual reminder when cost or value mode is  active;
+       There's  not yet any visual reminder when cost or value mode is active;
        for now pressing b b v should reliably reset to normal mode.
 
        q quits the application.
@@ -282,44 +296,44 @@
 
 SCREENS
    Accounts screen
-       This  is  normally  the  first screen displayed.  It lists accounts and
-       their balances, like hledger's balance command.  By default,  it  shows
-       all  accounts  and their latest ending balances (including the balances
-       of subaccounts).  If you specify a query on the command line, it  shows
+       This is normally the first screen displayed.   It  lists  accounts  and
+       their  balances,  like hledger's balance command.  By default, it shows
+       all accounts and their latest ending balances (including  the  balances
+       of  subaccounts).  If you specify a query on the command line, it shows
        just the matched accounts and the balances from matched transactions.
 
-       Account  names  are  shown as a flat list by default; press t to toggle
-       tree mode.  In list mode, account  balances  are  exclusive  of  subac-
-       counts,  except  where  subaccounts  are  hidden  by a depth limit (see
-       below).  In tree mode, all account balances  are  inclusive  of  subac-
+       Account names are shown as a flat list by default; press  t  to  toggle
+       tree  mode.   In  list  mode,  account balances are exclusive of subac-
+       counts, except where subaccounts are  hidden  by  a  depth  limit  (see
+       below).   In  tree  mode,  all account balances are inclusive of subac-
        counts.
 
-       To  see  less detail, press a number key, 1 to 9, to set a depth limit.
+       To see less detail, press a number key, 1 to 9, to set a  depth  limit.
        Or use - to decrease and +/= to increase the depth limit.  0 shows even
-       less  detail, collapsing all accounts to a single total.  To remove the
-       depth limit, set it higher than the maximum  account  depth,  or  press
+       less detail, collapsing all accounts to a single total.  To remove  the
+       depth  limit,  set  it  higher than the maximum account depth, or press
        ESCAPE.
 
        H toggles between showing historical balances or period balances.  His-
-       torical balances (the default) are ending balances at the  end  of  the
-       report  period,  taking  into account all transactions before that date
-       (filtered by the filter query if any),  including  transactions  before
-       the  start  of  the report period.  In other words, historical balances
-       are what you would see on a bank statement  for  that  account  (unless
-       disturbed  by  a  filter  query).   Period balances ignore transactions
+       torical  balances  (the  default) are ending balances at the end of the
+       report period, taking into account all transactions  before  that  date
+       (filtered  by  the  filter query if any), including transactions before
+       the start of the report period.  In other  words,  historical  balances
+       are  what  you  would  see on a bank statement for that account (unless
+       disturbed by a filter  query).   Period  balances  ignore  transactions
        before the report start date, so they show the change in balance during
        the report period.  They are more useful eg when viewing a time log.
 
        U toggles filtering by unmarked status, including or excluding unmarked
        postings in the balances.  Similarly, P toggles pending postings, and C
-       toggles  cleared postings.  (By default, balances include all postings;
-       if you activate one or two status  filters,  only  those  postings  are
+       toggles cleared postings.  (By default, balances include all  postings;
+       if  you  activate  one  or  two status filters, only those postings are
        included; and if you activate all three, the filter is removed.)
 
        R toggles real mode, in which virtual postings are ignored.
 
-       Z  toggles  nonzero  mode, in which only accounts with nonzero balances
-       are shown (hledger-ui shows zero items by default, unlike  command-line
+       Z toggles nonzero mode, in which only accounts  with  nonzero  balances
+       are  shown (hledger-ui shows zero items by default, unlike command-line
        hledger).
 
        Press right or enter to view an account's transactions register.
@@ -328,124 +342,124 @@
        This screen shows the transactions affecting a particular account, like
        a check register.  Each line represents one transaction and shows:
 
-       o the other account(s) involved, in abbreviated form.   (If  there  are
-         both  real  and virtual postings, it shows only the accounts affected
+       o the  other  account(s)  involved, in abbreviated form.  (If there are
+         both real and virtual postings, it shows only the  accounts  affected
          by real postings.)
 
-       o the overall change to the current account's balance; positive for  an
+       o the  overall change to the current account's balance; positive for an
          inflow to this account, negative for an outflow.
 
        o the running historical total or period total for the current account,
-         after the transaction.  This can be toggled with H.  Similar  to  the
-         accounts  screen,  the  historical  total is affected by transactions
-         (filtered by the filter query) before the report  start  date,  while
+         after  the  transaction.  This can be toggled with H.  Similar to the
+         accounts screen, the historical total  is  affected  by  transactions
+         (filtered  by  the  filter query) before the report start date, while
          the period total is not.  If the historical total is not disturbed by
-         a filter query, it will be the running historical balance  you  would
+         a  filter  query, it will be the running historical balance you would
          see on a bank register for the current account.
 
-       Transactions  affecting  this account's subaccounts will be included in
+       Transactions affecting this account's subaccounts will be  included  in
        the register if the accounts screen is in tree mode, or if it's in list
-       mode  but  this  account  has  subaccounts which are not shown due to a
-       depth limit.  In other words, the register always  shows  the  transac-
-       tions  contributing  to the balance shown on the accounts screen.  Tree
+       mode but this account has subaccounts which are  not  shown  due  to  a
+       depth  limit.   In  other words, the register always shows the transac-
+       tions contributing to the balance shown on the accounts  screen.   Tree
        mode/list mode can be toggled with t here also.
 
-       U toggles filtering by unmarked  status,  showing  or  hiding  unmarked
+       U  toggles  filtering  by  unmarked  status, showing or hiding unmarked
        transactions.  Similarly, P toggles pending transactions, and C toggles
-       cleared transactions.  (By default, transactions with all statuses  are
-       shown;  if  you activate one or two status filters, only those transac-
+       cleared  transactions.  (By default, transactions with all statuses are
+       shown; if you activate one or two status filters, only  those  transac-
        tions are shown; and if you activate all three, the filter is removed.)
 
        R toggles real mode, in which virtual postings are ignored.
 
-       Z  toggles  nonzero  mode, in which only transactions posting a nonzero
-       change are shown (hledger-ui shows zero items by default,  unlike  com-
+       Z toggles nonzero mode, in which only transactions  posting  a  nonzero
+       change  are  shown (hledger-ui shows zero items by default, unlike com-
        mand-line hledger).
 
        Press right (or enter) to view the selected transaction in detail.
 
    Transaction screen
-       This  screen  shows  a  single transaction, as a general journal entry,
-       similar to hledger's print command and  journal  format  (hledger_jour-
+       This screen shows a single transaction, as  a  general  journal  entry,
+       similar  to  hledger's  print command and journal format (hledger_jour-
        nal(5)).
 
-       The  transaction's  date(s)  and  any  cleared  flag, transaction code,
-       description, comments, along with  all  of  its  account  postings  are
-       shown.   Simple  transactions  have two postings, but there can be more
+       The transaction's date(s)  and  any  cleared  flag,  transaction  code,
+       description,  comments,  along  with  all  of  its account postings are
+       shown.  Simple transactions have two postings, but there  can  be  more
        (or in certain cases, fewer).
 
-       up and down will step through all transactions listed in  the  previous
-       account  register screen.  In the title bar, the numbers in parentheses
-       show your position  within  that  account  register.   They  will  vary
+       up  and  down will step through all transactions listed in the previous
+       account register screen.  In the title bar, the numbers in  parentheses
+       show  your  position  within  that  account  register.   They will vary
        depending on which account register you came from (remember most trans-
        actions appear in multiple account registers).  The #N number preceding
        them is the transaction's position within the complete unfiltered jour-
        nal, which is a more stable id (at least until the next reload).
 
    Error screen
-       This screen will appear if there is a problem, such as a  parse  error,
-       when  you  press g to reload.  Once you have fixed the problem, press g
+       This  screen  will appear if there is a problem, such as a parse error,
+       when you press g to reload.  Once you have fixed the problem,  press  g
        again to reload and resume normal operation.  (Or, you can press escape
        to cancel the reload attempt.)
 
 TIPS
    Watch mode
-       One  of  hledger-ui's best features is the auto-reloading --watch mode.
-       With this flag, it  will  update  the  display  automatically  whenever
+       One of hledger-ui's best features is the auto-reloading  --watch  mode.
+       With  this  flag,  it  will  update  the display automatically whenever
        changes are saved to the data files.
 
-       This  is very useful when reconciling.  A good workflow is to have your
-       bank's online register open in a browser  window,  for  reference;  the
-       journal  file open in an editor window; and hledger-ui in watch mode in
+       This is very useful when reconciling.  A good workflow is to have  your
+       bank's  online  register  open  in a browser window, for reference; the
+       journal file open in an editor window; and hledger-ui in watch mode  in
        a terminal window, eg:
 
               $ hledger-ui --watch --register checking -C
 
-       As you mark things cleared in the editor, you can see the effect  imme-
-       diately  without  having  to  context  switch.  This leaves more mental
-       bandwidth for your accounting.  Of course you can still  interact  with
-       hledger-ui  when  needed,  eg to toggle cleared mode, or to explore the
+       As  you mark things cleared in the editor, you can see the effect imme-
+       diately without having to context  switch.   This  leaves  more  mental
+       bandwidth  for  your accounting.  Of course you can still interact with
+       hledger-ui when needed, eg to toggle cleared mode, or  to  explore  the
        history.
 
    Watch mode limitations
-       There are situations in which it won't work, ie the  display  will  not
-       update  when  you save a change (because the underlying inotify library
+       There  are  situations  in which it won't work, ie the display will not
+       update when you save a change (because the underlying  inotify  library
        does not support it).  Here are some that we know of:
 
-       o Certain editors: saving with gedit, and perhaps  any  Gnome  applica-
-         tion,  won't be detected (#1617).  Jetbrains IDEs, such as IDEA, also
+       o Certain  editors:  saving  with gedit, and perhaps any Gnome applica-
+         tion, won't be detected (#1617).  Jetbrains IDEs, such as IDEA,  also
          may not work (#911).
 
-       o Certain unusual filesystems might not be supported.  (All  the  usual
+       o Certain  unusual  filesystems might not be supported.  (All the usual
          ones on unix, mac and windows are supported.)
 
        In such cases, the workaround is to switch to the hledger-ui window and
-       press g each time you want it to  reload.   (Actually,  see  #1617  for
+       press  g  each  time  you  want it to reload.  (Actually, see #1617 for
        another workaround, and let us know if it works for you.)
 
-       If  you leave hledger-ui --watch running for days, on certain platforms
-       (?), perhaps with many transactions in your journal (?),  perhaps  with
-       large  numbers  of  other  files  present (?), you may see it gradually
-       using more and more memory and CPU over time, as seen in top or  Activ-
+       If you leave hledger-ui --watch running for days, on certain  platforms
+       (?),  perhaps  with many transactions in your journal (?), perhaps with
+       large numbers of other files present (?),  you  may  see  it  gradually
+       using  more and more memory and CPU over time, as seen in top or Activ-
        ity Monitor or Task Manager.
 
-       A  workaround  is to quit and restart it, or to suspend it (CTRL-z) and
+       A workaround is to quit and restart it, or to suspend it  (CTRL-z)  and
        restart it (fg) if your shell supports that.
 
 ENVIRONMENT
        COLUMNS The screen width to use.  Default: the full terminal width.
 
        LEDGER_FILE The journal file path when not specified with -f.  Default:
-       ~/.hledger.journal  (on  windows,  perhaps C:/Users/USER/.hledger.jour-
+       ~/.hledger.journal (on  windows,  perhaps  C:/Users/USER/.hledger.jour-
        nal).
 
-       A typical value is ~/DIR/YYYY.journal,  where  DIR  is  a  version-con-
-       trolled  finance directory and YYYY is the current year.  Or ~/DIR/cur-
+       A  typical  value  is  ~/DIR/YYYY.journal,  where DIR is a version-con-
+       trolled finance directory and YYYY is the current year.  Or  ~/DIR/cur-
        rent.journal, where current.journal is a symbolic link to YYYY.journal.
 
        On Mac computers, you can set this and other environment variables in a
-       more thorough way that also affects applications started from  the  GUI
-       (say,   an   Emacs   dock  icon).   Eg  on  MacOS  Catalina  I  have  a
+       more  thorough  way that also affects applications started from the GUI
+       (say,  an  Emacs  dock  icon).   Eg  on  MacOS  Catalina   I   have   a
        ~/.MacOSX/environment.plist file containing
 
               {
@@ -455,13 +469,13 @@
        To see the effect you may need to killall Dock, or reboot.
 
 FILES
-       Reads data from one or more files in hledger journal, timeclock,  time-
-       dot,   or   CSV   format   specified   with  -f,  or  $LEDGER_FILE,  or
-       $HOME/.hledger.journal          (on          windows,           perhaps
+       Reads  data from one or more files in hledger journal, timeclock, time-
+       dot,  or  CSV  format  specified   with   -f,   or   $LEDGER_FILE,   or
+       $HOME/.hledger.journal           (on          windows,          perhaps
        C:/Users/USER/.hledger.journal).
 
 BUGS
-       The  need  to precede options with -- when invoked from hledger is awk-
+       The need to precede options with -- when invoked from hledger  is  awk-
        ward.
 
        -f- doesn't work (hledger-ui can't read from stdin).
@@ -469,24 +483,24 @@
        -V affects only the accounts screen.
 
        When you press g, the current and all previous screens are regenerated,
-       which  may cause a noticeable pause with large files.  Also there is no
+       which may cause a noticeable pause with large files.  Also there is  no
        visual indication that this is in progress.
 
-       --watch is not yet fully robust.  It works well for normal  usage,  but
-       many  file  changes  in  a  short time (eg saving the file thousands of
-       times with an editor macro) can cause problems at least on OSX.   Symp-
-       toms  include:  unresponsive UI, periodic resetting of the cursor posi-
+       --watch  is  not yet fully robust.  It works well for normal usage, but
+       many file changes in a short time (eg  saving  the  file  thousands  of
+       times  with an editor macro) can cause problems at least on OSX.  Symp-
+       toms include: unresponsive UI, periodic resetting of the  cursor  posi-
        tion, momentary display of parse errors, high CPU usage eventually sub-
        siding, and possibly a small but persistent build-up of CPU usage until
        the program is restarted.
 
-       Also, if you are viewing files mounted from  another  machine,  --watch
+       Also,  if  you  are viewing files mounted from another machine, --watch
        requires that both machine clocks are roughly in step.
 
 
 
 REPORTING BUGS
-       Report  bugs at http://bugs.hledger.org (or on the #hledger IRC channel
+       Report bugs at http://bugs.hledger.org (or on the #hledger IRC  channel
        or hledger mail list)
 
 
@@ -504,4 +518,4 @@
 
 
 
-hledger-ui-1.22.2                 August 2021                    HLEDGER-UI(1)
+hledger-ui-1.23                 September 2021                   HLEDGER-UI(1)
