diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -9,6 +9,16 @@
 User-visible changes in hledger-ui.
 See also the hledger changelog.
 
+# 1.27 2022-09-01
+
+Improvements
+
+- At --debug=2 and up, log debug output to ./debug.log.
+
+- Use/require brick 1.0+. (#1889)
+
+- Use hledger 1.27
+
 # 1.26.1 2022-07-11
 
 - support doclayout 0.4, brick 0.72+
diff --git a/Hledger/UI/AccountsScreen.hs b/Hledger/UI/AccountsScreen.hs
--- a/Hledger/UI/AccountsScreen.hs
+++ b/Hledger/UI/AccountsScreen.hs
@@ -29,7 +29,7 @@
 import Text.DocLayout (realLength)
 
 import Hledger
-import Hledger.Cli hiding (progname,prognameandversion)
+import Hledger.Cli hiding (mode, progname, prognameandversion)
 import Hledger.UI.UIOptions
 import Hledger.UI.UITypes
 import Hledger.UI.UIState
@@ -54,7 +54,7 @@
   aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}},
   ajournal=j,
   aScreen=s@AccountsScreen{}
-  } =
+  } = dlogUiTrace "asInit 1" $
   ui{aScreen=s & asList .~ newitems'}
    where
     newitems = list AccountsList (V.fromList $ displayitems ++ blankitems) 1
@@ -97,7 +97,7 @@
     displayitems = map displayitem items
     -- blanks added for scrolling control, cf RegisterScreen.
     -- XXX Ugly. Changing to 0 helps when debugging.
-    blankitems = replicate 100
+    blankitems = replicate uiNumBlankItems
       AccountsScreenItem{asItemIndentLevel        = 0
                         ,asItemAccountName        = ""
                         ,asItemDisplayAccountName = ""
@@ -105,14 +105,14 @@
                         }
 
 
-asInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
+asInit _ _ _ = dlogUiTrace "asInit 2" $ errorWrongScreenType "init function"  -- PARTIAL:
 
 asDraw :: UIState -> [Widget Name]
 asDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}}
               ,ajournal=j
               ,aScreen=s@AccountsScreen{}
               ,aMode=mode
-              } =
+              } = dlogUiTrace "asDraw 1" $
     case mode of
       Help       -> [helpDialog copts, maincontent]
       -- Minibuffer e -> [minibuffer e, maincontent]
@@ -152,7 +152,7 @@
         ishistorical = balanceaccum_ ropts == Historical
 
         toplabel =
-              withAttr ("border" <> "filename") files
+              withAttr (attrName "border" <> attrName "filename") files
           <+> toggles
           <+> str (" account " ++ if ishistorical then "balances" else "changes")
           <+> borderPeriodStr (if ishistorical then "at end of" else "in") (period_ ropts)
@@ -160,7 +160,7 @@
           <+> borderDepthStr mdepth
           <+> str (" ("++curidx++"/"++totidx++")")
           <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts
-               then withAttr ("border" <> "query") (str " ignoring balance assertions")
+               then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions")
                else str "")
           where
             files = case journalFilePaths j of
@@ -168,7 +168,7 @@
                            f:_ -> str $ takeFileName f
                            -- [f,_:[]] -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str " (& 1 included file)"
                            -- f:fs  -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str (" (& " ++ show (length fs) ++ " included files)")
-            toggles = withAttr ("border" <> "query") $ str $ unwords $ concat [
+            toggles = withAttr (attrName "border" <> attrName "query") $ str $ unwords $ concat [
                [""]
               ,if empty_ ropts then [] else ["nonzero"]
               ,uiShowStatus copts $ statuses_ ropts
@@ -203,7 +203,7 @@
               ,("q", str "quit")
               ]
 
-asDraw _ = error "draw function called with wrong screen type, should not happen"  -- PARTIAL:
+asDraw _ =  dlogUiTrace "asDraw 2" $ errorWrongScreenType "draw function"  -- PARTIAL:
 
 asDrawItem :: (Int,Int) -> Bool -> AccountsScreenItem -> Widget Name
 asDrawItem (acctwidth, balwidth) selected AccountsScreenItem{..} =
@@ -220,187 +220,183 @@
         balspace = T.replicate (2 + balwidth - wbWidth balBuilder) " "
         splitAmounts = foldr1 (<+>) . intersperse (str ", ") . map renderamt . T.splitOn ", " . wbToText
         renderamt :: T.Text -> Widget Name
-        renderamt a | T.any (=='-') a = withAttr (sel $ "list" <> "balance" <> "negative") $ txt a
-                    | otherwise       = withAttr (sel $ "list" <> "balance" <> "positive") $ txt a
-        sel | selected  = (<> "selected")
+        renderamt a | T.any (=='-') a = withAttr (sel $ attrName "list" <> attrName "balance" <> attrName "negative") $ txt a
+                    | otherwise       = withAttr (sel $ attrName "list" <> attrName "balance" <> attrName "positive") $ txt a
+        sel | selected  = (<> attrName "selected")
             | otherwise = id
 
-asHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
-asHandle ui0@UIState{
-   aScreen=scr@AccountsScreen{..}
-  ,aopts=UIOpts{uoCliOpts=copts}
-  ,ajournal=j
-  ,aMode=mode
-  } ev = do
-  let
-    d = copts^.rsDay
-    nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ _asList^.listElementsL
-    lastnonblankidx = max 0 (length nonblanks - 1)
-
-  -- save the currently selected account, in case we leave this screen and lose the selection
-  let
-    selacct = case listSelectedElement _asList of
-                Just (_, AccountsScreenItem{..}) -> asItemAccountName
-                Nothing -> scr ^. asSelectedAccount
-    ui = ui0{aScreen=scr & asSelectedAccount .~ selacct}
+asHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+asHandle ev = do
+  ui0 <- get'
+  dlogUiTraceM "asHandle 1"
+  case ui0 of
+    ui1@UIState{
+      aScreen=scr@AccountsScreen{..}
+      ,aopts=UIOpts{uoCliOpts=copts}
+      ,ajournal=j
+      ,aMode=mode
+      } -> do
 
-  case mode of
-    Minibuffer _ ed ->
-      case ev of
-        VtyEvent (EvKey KEsc   []) -> continue $ closeMinibuffer ui
-        VtyEvent (EvKey KEnter []) -> continue $ regenerateScreens j d $
-            case setFilter s $ closeMinibuffer ui of
-              Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui
-              Right ui' -> ui'
-          where s = chomp $ unlines $ map strip $ getEditContents ed
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        VtyEvent ev        -> do
-          ed' <- handleEditorEvent 
-#if MIN_VERSION_brick(0,72,0)
-            (VtyEvent ev)
-#else
-            ev
-#endif
-            ed
-          continue $ ui{aMode=Minibuffer "filter" ed'}
-        AppEvent _        -> continue ui
-        MouseDown{}       -> continue ui
-        MouseUp{}         -> continue ui
+      let
+        -- save the currently selected account, in case we leave this screen and lose the selection
+        selacct = case listSelectedElement _asList of
+                    Just (_, AccountsScreenItem{..}) -> asItemAccountName
+                    Nothing -> scr ^. asSelectedAccount
+        ui = ui1{aScreen=scr & asSelectedAccount .~ selacct}
+        nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ _asList^.listElementsL
+        lastnonblankidx = max 0 (length nonblanks - 1)
+        journalspan = journalDateSpan False j
+        d = copts^.rsDay
 
-    Help ->
-      case ev of
-        -- VtyEvent (EvKey (KChar 'q') []) -> halt ui
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        _                    -> helpHandle ui ev
+      case mode of
+        Minibuffer _ ed ->
+          case ev of
+            VtyEvent (EvKey KEsc   []) -> put' $ closeMinibuffer ui
+            VtyEvent (EvKey KEnter []) -> put' $ regenerateScreens j d $
+                case setFilter s $ closeMinibuffer ui of
+                  Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui
+                  Right ui' -> ui'
+              where s = chomp $ unlines $ map strip $ getEditContents ed
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            VtyEvent e -> do
+              ed' <- nestEventM' ed $ handleEditorEvent (VtyEvent e)
+              put' ui{aMode=Minibuffer "filter" ed'}
+            AppEvent _  -> return ()
+            MouseDown{} -> return ()
+            MouseUp{}   -> return ()
 
-    Normal ->
-      case ev of
-        VtyEvent (EvKey (KChar 'q') []) -> halt ui
-        -- EvKey (KChar 'l') [MCtrl] -> do
-        VtyEvent (EvKey KEsc        []) -> continue $ resetScreens d 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 ->
-          continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
-          where
-            p = reportPeriod ui
-        e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
-          liftIO (uiReloadJournal copts d ui) >>= continue
-        VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
-        VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
-        VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
-        VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor endPosition (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
-        VtyEvent (EvKey (KChar 'B') []) -> continue $ regenerateScreens j d $ toggleConversionOp ui
-        VtyEvent (EvKey (KChar 'V') []) -> continue $ regenerateScreens j d $ toggleValue ui
-        VtyEvent (EvKey (KChar '0') []) -> continue $ regenerateScreens j d $ setDepth (Just 0) ui
-        VtyEvent (EvKey (KChar '1') []) -> continue $ regenerateScreens j d $ setDepth (Just 1) ui
-        VtyEvent (EvKey (KChar '2') []) -> continue $ regenerateScreens j d $ setDepth (Just 2) ui
-        VtyEvent (EvKey (KChar '3') []) -> continue $ regenerateScreens j d $ setDepth (Just 3) ui
-        VtyEvent (EvKey (KChar '4') []) -> continue $ regenerateScreens j d $ setDepth (Just 4) ui
-        VtyEvent (EvKey (KChar '5') []) -> continue $ regenerateScreens j d $ setDepth (Just 5) ui
-        VtyEvent (EvKey (KChar '6') []) -> continue $ regenerateScreens j d $ setDepth (Just 6) ui
-        VtyEvent (EvKey (KChar '7') []) -> continue $ regenerateScreens j d $ setDepth (Just 7) ui
-        VtyEvent (EvKey (KChar '8') []) -> continue $ regenerateScreens j d $ setDepth (Just 8) ui
-        VtyEvent (EvKey (KChar '9') []) -> continue $ regenerateScreens j d $ setDepth (Just 9) ui
-        VtyEvent (EvKey (KChar '-') []) -> continue $ regenerateScreens j d $ decDepth ui
-        VtyEvent (EvKey (KChar '_') []) -> continue $ regenerateScreens j d $ decDepth ui
-        VtyEvent (EvKey (KChar c)   []) | c `elem` ['+','='] -> continue $ regenerateScreens j d $ incDepth ui
-        VtyEvent (EvKey (KChar 'T') []) -> continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
+        Help ->
+          case ev of
+            -- VtyEvent (EvKey (KChar 'q') []) -> halt
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            _ -> helpHandle ev
 
-        -- display mode/query toggles
-        VtyEvent (EvKey (KChar 'H') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleHistorical ui
-        VtyEvent (EvKey (KChar 't') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleTree ui
-        VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> asCenterAndContinue $ regenerateScreens j d $ toggleEmpty ui
-        VtyEvent (EvKey (KChar 'R') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleReal ui
-        VtyEvent (EvKey (KChar 'U') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui
-        VtyEvent (EvKey (KChar 'P') []) -> asCenterAndContinue $ regenerateScreens j d $ togglePending ui
-        VtyEvent (EvKey (KChar 'C') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleCleared ui
-        VtyEvent (EvKey (KChar 'F') []) -> continue $ regenerateScreens j d $ toggleForecast d ui
+        Normal ->
+          case ev of
+            VtyEvent (EvKey (KChar 'q') []) -> halt
+            -- EvKey (KChar 'l') [MCtrl] -> do
+            VtyEvent (EvKey KEsc        []) -> put' $ resetScreens d ui
+            VtyEvent (EvKey (KChar c)   []) | c == '?' -> put' $ 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 ->
+              put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
+              where
+                p = reportPeriod ui
+            e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
+              liftIO (uiReloadJournal copts d ui) >>= put'
+            VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
+            VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
+            VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
+            VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor endPosition (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
+            VtyEvent (EvKey (KChar 'B') []) -> put' $ regenerateScreens j d $ toggleConversionOp ui
+            VtyEvent (EvKey (KChar 'V') []) -> put' $ regenerateScreens j d $ toggleValue ui
+            VtyEvent (EvKey (KChar '0') []) -> put' $ regenerateScreens j d $ setDepth (Just 0) ui
+            VtyEvent (EvKey (KChar '1') []) -> put' $ regenerateScreens j d $ setDepth (Just 1) ui
+            VtyEvent (EvKey (KChar '2') []) -> put' $ regenerateScreens j d $ setDepth (Just 2) ui
+            VtyEvent (EvKey (KChar '3') []) -> put' $ regenerateScreens j d $ setDepth (Just 3) ui
+            VtyEvent (EvKey (KChar '4') []) -> put' $ regenerateScreens j d $ setDepth (Just 4) ui
+            VtyEvent (EvKey (KChar '5') []) -> put' $ regenerateScreens j d $ setDepth (Just 5) ui
+            VtyEvent (EvKey (KChar '6') []) -> put' $ regenerateScreens j d $ setDepth (Just 6) ui
+            VtyEvent (EvKey (KChar '7') []) -> put' $ regenerateScreens j d $ setDepth (Just 7) ui
+            VtyEvent (EvKey (KChar '8') []) -> put' $ regenerateScreens j d $ setDepth (Just 8) ui
+            VtyEvent (EvKey (KChar '9') []) -> put' $ regenerateScreens j d $ setDepth (Just 9) ui
+            VtyEvent (EvKey (KChar '-') []) -> put' $ regenerateScreens j d $ decDepth ui
+            VtyEvent (EvKey (KChar '_') []) -> put' $ regenerateScreens j d $ decDepth ui
+            VtyEvent (EvKey (KChar c)   []) | c `elem` ['+','='] -> put' $ regenerateScreens j d $ incDepth ui
+            VtyEvent (EvKey (KChar 'T') []) -> put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
 
-        VtyEvent (EvKey (KDown)     [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui
-        VtyEvent (EvKey (KUp)       [MShift]) -> continue $ regenerateScreens j d $ growReportPeriod d ui
-        VtyEvent (EvKey (KRight)    [MShift]) -> continue $ regenerateScreens j d $ nextReportPeriod journalspan ui
-        VtyEvent (EvKey (KLeft)     [MShift]) -> continue $ regenerateScreens j d $ previousReportPeriod journalspan ui
-        VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui
-        VtyEvent (EvKey k           []) | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui)
-        VtyEvent e | e `elem` moveLeftEvents -> continue $ popScreen ui
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle _asList >> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            -- display mode/query toggles
+            VtyEvent (EvKey (KChar 'H') []) -> modify' (regenerateScreens j d . toggleHistorical) >> asCenterAndContinue
+            VtyEvent (EvKey (KChar 't') []) -> modify' (regenerateScreens j d . toggleTree) >> asCenterAndContinue
+            VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> modify' (regenerateScreens j d . toggleEmpty) >> asCenterAndContinue
+            VtyEvent (EvKey (KChar 'R') []) -> modify' (regenerateScreens j d . toggleReal) >> asCenterAndContinue
+            VtyEvent (EvKey (KChar 'U') []) -> modify' (regenerateScreens j d . toggleUnmarked) >> asCenterAndContinue
+            VtyEvent (EvKey (KChar 'P') []) -> modify' (regenerateScreens j d . togglePending) >> asCenterAndContinue
+            VtyEvent (EvKey (KChar 'C') []) -> modify' (regenerateScreens j d . toggleCleared) >> asCenterAndContinue
+            VtyEvent (EvKey (KChar 'F') []) -> modify' (regenerateScreens j d . toggleForecast d)
 
-        -- enter register screen for selected account (if there is one),
-        -- centering its selected transaction if possible
-        VtyEvent e | e `elem` moveRightEvents
-                   , not $ isBlankElement $ listSelectedElement _asList -> asEnterRegister d selacct ui
+            VtyEvent (EvKey (KDown)     [MShift]) -> put' $ regenerateScreens j d $ shrinkReportPeriod d ui
+            VtyEvent (EvKey (KUp)       [MShift]) -> put' $ regenerateScreens j d $ growReportPeriod d ui
+            VtyEvent (EvKey (KRight)    [MShift]) -> put' $ regenerateScreens j d $ nextReportPeriod journalspan ui
+            VtyEvent (EvKey (KLeft)     [MShift]) -> put' $ regenerateScreens j d $ previousReportPeriod journalspan ui
+            VtyEvent (EvKey (KChar '/') []) -> put' $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui
+            VtyEvent (EvKey k           []) | k `elem` [KBS, KDel] -> (put' $ regenerateScreens j d $ resetFilter ui)
+            VtyEvent e | e `elem` moveLeftEvents -> put' $ popScreen ui
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle _asList >> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
 
-        -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347
-        -- just use it to move the selection
-        MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickedacct -> do
-          continue ui{aScreen=scr{_asList=listMoveTo y _asList}}
-          where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y
-        -- and on MouseUp, enter the subscreen
-        MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickedacct -> do
-          asEnterRegister d clickedacct ui
-          where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y
+            -- enter register screen for selected account (if there is one),
+            -- centering its selected transaction if possible
+            VtyEvent e | e `elem` moveRightEvents
+                      , not $ isBlankElement $ listSelectedElement _asList -> asEnterRegister d selacct ui
 
-        -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled
-        VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do
-          vScrollBy (viewportScroll $ _asList^.listNameL) 1 >> continue ui
-          where mnextelement = listSelectedElement $ listMoveDown _asList
+            -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347
+            -- just use it to move the selection
+            MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickedacct -> do
+              put' ui{aScreen=scr}  -- XXX does this do anything ?
+              where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y
+            -- and on MouseUp, enter the subscreen
+            MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickedacct -> do
+              asEnterRegister d clickedacct ui
+              where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y
 
-        -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,
-        -- pushing the selection when necessary.
-        MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do
-          let scrollamt = if btn==BScrollUp then -1 else 1
-          list' <- listScrollPushingSelection name _asList (asListSize _asList) scrollamt
-          continue ui{aScreen=scr{_asList=list'}}
+            -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled
+            VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do
+              vScrollBy (viewportScroll $ _asList^.listNameL) 1
+              where mnextelement = listSelectedElement $ listMoveDown _asList
 
-        -- if page down or end leads to a blank padding item, stop at last non-blank
-        VtyEvent e@(EvKey k           []) | k `elem` [KPageDown, KEnd] -> do
-          list <- handleListEvent e _asList
-          if isBlankElement $ listSelectedElement list
-          then do
-            let list' = listMoveTo lastnonblankidx list
-            scrollSelectionToMiddle list'
-            continue ui{aScreen=scr{_asList=list'}}
-          else
-            continue ui{aScreen=scr{_asList=list}}
+            -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,
+            -- pushing the selection when necessary.
+            MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do
+              let scrollamt = if btn==BScrollUp then -1 else 1
+              list' <- nestEventM' _asList $ listScrollPushingSelection name (asListSize _asList) scrollamt
+              put' ui{aScreen=scr{_asList=list'}}
 
-        -- fall through to the list's event handler (handles up/down)
-        VtyEvent ev -> do
-          newitems <- handleListEvent (normaliseMovementKeys ev) _asList
-          continue $ ui{aScreen=scr & asList .~ newitems
-                                    & asSelectedAccount .~ selacct
-                                    }
+            -- if page down or end leads to a blank padding item, stop at last non-blank
+            VtyEvent e@(EvKey k           []) | k `elem` [KPageDown, KEnd] -> do
+              l <- nestEventM' _asList $ handleListEvent e
+              if isBlankElement $ listSelectedElement l
+              then do
+                let l' = listMoveTo lastnonblankidx l
+                scrollSelectionToMiddle l'
+                put' ui{aScreen=scr{_asList=l'}}
+              else
+                put' ui{aScreen=scr{_asList=l}}
 
-        MouseDown{} -> continue ui
-        MouseUp{}   -> continue ui
-        AppEvent _  -> continue ui
+            -- fall through to the list's event handler (handles up/down)
+            VtyEvent e -> do
+              list' <- nestEventM' _asList $ handleListEvent (normaliseMovementKeys e)
+              put' ui{aScreen=scr & asList .~ list' & asSelectedAccount .~ selacct }
 
-  where
-    journalspan = journalDateSpan False j
+            MouseDown{} -> return ()
+            MouseUp{}   -> return ()
+            AppEvent _  -> return ()
 
-asHandle _ _ = error "event handler called with wrong screen type, should not happen"  -- PARTIAL:
+    _ -> dlogUiTraceM "asHandle 2" >> errorWrongScreenType "event handler"
 
+asEnterRegister :: Day -> AccountName -> UIState -> EventM Name UIState ()
 asEnterRegister d selacct ui = do
-  rsCenterAndContinue $
-  -- flip rsHandle (VtyEvent (EvKey (KChar 'l') [MCtrl])) $
-    screenEnter d regscr ui
+  dlogUiTraceM "asEnterRegister"
+  let
+    regscr = rsSetAccount selacct isdepthclipped registerScreen
       where
-        regscr = rsSetAccount selacct isdepthclipped registerScreen
         isdepthclipped = case getDepth ui of
-                            Just d  -> accountNameLevel selacct >= d
-                            Nothing -> False
+                          Just de -> accountNameLevel selacct >= de
+                          Nothing -> False
+  rsCenterSelection (screenEnter d regscr ui) >>= put'
 
 asSetSelectedAccount a s@AccountsScreen{} = s & asSelectedAccount .~ a
 asSetSelectedAccount _ s = s
 
 isBlankElement mel = ((asItemAccountName . snd) <$> mel) == Just ""
 
-asCenterAndContinue ui = do
-  scrollSelectionToMiddle $ _asList $ aScreen ui
-  continue ui
+asCenterAndContinue :: EventM Name UIState ()
+asCenterAndContinue = do
+  ui <- get'
+  scrollSelectionToMiddle (_asList $ aScreen ui)
 
 asListSize = V.length . V.takeWhile ((/="").asItemAccountName) . listElements
+
diff --git a/Hledger/UI/ErrorScreen.hs b/Hledger/UI/ErrorScreen.hs
--- a/Hledger/UI/ErrorScreen.hs
+++ b/Hledger/UI/ErrorScreen.hs
@@ -24,7 +24,7 @@
 import Text.Megaparsec
 import Text.Megaparsec.Char
 
-import Hledger.Cli hiding (progname,prognameandversion)
+import Hledger.Cli hiding (mode, progname,prognameandversion)
 import Hledger.UI.UIOptions
 import Hledger.UI.UITypes
 import Hledger.UI.UIState
@@ -54,10 +54,10 @@
     _          -> [maincontent]
   where
     maincontent = Widget Greedy Greedy $ do
-      render $ defaultLayout toplabel bottomlabel $ withAttr "error" $ str $ esError
+      render $ defaultLayout toplabel bottomlabel $ withAttr (attrName "error") $ str $ esError
       where
         toplabel =
-              withAttr ("border" <> "bold") (str "Oops. Please fix this problem then press g to reload")
+              withAttr (attrName "border" <> attrName "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 = quickhelp
@@ -75,44 +75,46 @@
 
 esDraw _ = error "draw function called with wrong screen type, should not happen"  -- PARTIAL:
 
-esHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
-esHandle ui@UIState{aScreen=ErrorScreen{..}
-                   ,aopts=UIOpts{uoCliOpts=copts}
-                   ,ajournal=j
-                   ,aMode=mode
-                   }
-         ev =
-  case mode of
-    Help ->
-      case ev of
-        VtyEvent (EvKey (KChar 'q') []) -> halt ui
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        _                    -> helpHandle ui ev
+esHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+esHandle ev = do
+  ui0 <- get'
+  case ui0 of
+    ui@UIState{aScreen=ErrorScreen{..}
+              ,aopts=UIOpts{uoCliOpts=copts}
+              ,ajournal=j
+              ,aMode=mode
+              } ->
+      case mode of
+        Help ->
+          case ev of
+            VtyEvent (EvKey (KChar 'q') []) -> halt
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            _                    -> helpHandle ev
 
-    _ -> do
-      let d = copts^.rsDay
-      case ev of
-        VtyEvent (EvKey (KChar 'q') []) -> halt ui
-        VtyEvent (EvKey KEsc        []) -> continue $ uiCheckBalanceAssertions d $ resetScreens d ui
-        VtyEvent (EvKey (KChar c)   []) | c `elem` ['h','?'] -> continue $ setMode Help ui
-        VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j (popScreen ui)
-          where
-            (pos,f) = case parsewithString hledgerparseerrorpositionp esError of
-                        Right (f,l,c) -> (Just (l, Just c),f)
-                        Left  _       -> (endPosition, journalFilePath j)
-        e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
-          liftIO (uiReloadJournal copts d (popScreen ui)) >>= continue . uiCheckBalanceAssertions d
---           (ej, _) <- liftIO $ journalReloadIfChanged copts d j
---           case ej of
---             Left err -> continue ui{aScreen=s{esError=err}} -- show latest parse error
---             Right j' -> continue $ regenerateScreens j' d $ popScreen ui  -- return to previous screen, and reload it
-        VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (popScreen $ toggleIgnoreBalanceAssertions ui)
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        _ -> continue ui
+        _ -> do
+          let d = copts^.rsDay
+          case ev of
+            VtyEvent (EvKey (KChar 'q') []) -> halt
+            VtyEvent (EvKey KEsc        []) -> put' $ uiCheckBalanceAssertions d $ resetScreens d ui
+            VtyEvent (EvKey (KChar c)   []) | c `elem` ['h','?'] -> put' $ setMode Help ui
+            VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j (popScreen ui)
+              where
+                (pos,f) = case parsewithString hledgerparseerrorpositionp esError of
+                            Right (f',l,c) -> (Just (l, Just c),f')
+                            Left  _       -> (endPosition, journalFilePath j)
+            e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
+              liftIO (uiReloadJournal copts d (popScreen ui)) >>= put' . uiCheckBalanceAssertions d
+              -- (ej, _) <- liftIO $ journalReloadIfChanged copts d j
+              -- case ej of
+              --   Left err -> continue ui{aScreen=s{esError=err}} -- show latest parse error
+              --   Right j' -> continue $ regenerateScreens j' d $ popScreen ui  -- return to previous screen, and reload it
+            VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (popScreen $ toggleIgnoreBalanceAssertions ui)
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            _ -> return ()
 
-esHandle _ _ = error "event handler called with wrong screen type, should not happen"  -- PARTIAL:
+    _ -> errorWrongScreenType "event handler"
 
 -- | Parse the file name, line and column number from a hledger parse error message, if possible.
 -- Temporary, we should keep the original parse error location. XXX
diff --git a/Hledger/UI/Main.hs b/Hledger/UI/Main.hs
--- a/Hledger/UI/Main.hs
+++ b/Hledger/UI/Main.hs
@@ -1,3 +1,5 @@
+-- TODO: brick 1 support
+-- https://hackage.haskell.org/package/brick-1.0/changelog
 {-|
 hledger-ui - a hledger add-on providing a curses-style interface.
 Copyright (c) 2007-2015 Simon Michael <simon@joyful.com>
@@ -33,6 +35,7 @@
 import Hledger.UI.Theme
 import Hledger.UI.AccountsScreen
 import Hledger.UI.RegisterScreen
+import Hledger.UI.UIUtils (dlogUiTrace)
 
 ----------------------------------------------------------------------
 
@@ -60,7 +63,8 @@
     _                                         -> withJournalDo copts' (runBrickUi opts)
 
 runBrickUi :: UIOpts -> Journal -> IO ()
-runBrickUi uopts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} j = do
+runBrickUi uopts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} j =
+  dlogUiTrace "========= runBrickUi" $ do
   let
     today = copts^.rsDay
 
@@ -159,11 +163,11 @@
 
     brickapp :: App UIState AppEvent Name
     brickapp = App {
-        appStartEvent   = return
+        appStartEvent   = return ()
       , appAttrMap      = const $ fromMaybe defaultTheme $ getTheme =<< uoTheme uopts'
       , appChooseCursor = showFirstCursor
-      , appHandleEvent  = \ui ev -> sHandle (aScreen ui) ui ev
-      , appDraw         = \ui    -> sDraw   (aScreen ui) ui
+      , appHandleEvent  = \ev -> do ui' <- get; sHandle (aScreen ui') ev
+      , appDraw         = \ui' -> sDraw (aScreen ui') ui'
       }
 
   -- print (length (show ui)) >> exitSuccess  -- show any debug output to this point & quit
diff --git a/Hledger/UI/RegisterScreen.hs b/Hledger/UI/RegisterScreen.hs
--- a/Hledger/UI/RegisterScreen.hs
+++ b/Hledger/UI/RegisterScreen.hs
@@ -4,14 +4,13 @@
 {-# LANGUAGE FlexibleContexts  #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards   #-}
-{-# LANGUAGE NamedFieldPuns #-}
 {-# OPTIONS_GHC -Wno-incomplete-record-updates #-}
 
 module Hledger.UI.RegisterScreen
  (registerScreen
  ,rsHandle
  ,rsSetAccount
- ,rsCenterAndContinue
+ ,rsCenterSelection
  )
 where
 
@@ -32,7 +31,7 @@
 
 
 import Hledger
-import Hledger.Cli hiding (progname,prognameandversion)
+import Hledger.Cli hiding (mode, progname,prognameandversion)
 import Hledger.UI.UIOptions
 -- import Hledger.UI.Theme
 import Hledger.UI.UITypes
@@ -60,6 +59,7 @@
 
 rsInit :: Day -> Bool -> UIState -> UIState
 rsInit d reset ui@UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}, ajournal=j, aScreen=s@RegisterScreen{..}} =
+  dlogUiTrace "rsInit 1" $
   ui{aScreen=s{rsList=newitems'}}
   where
     -- gather arguments and queries
@@ -102,7 +102,7 @@
             where showamt = showMixedAmountB oneLine{displayMaxWidth=Just 32}
     -- blank items are added to allow more control of scroll position; we won't allow movement over these.
     -- XXX Ugly. Changing to 0 helps when debugging.
-    blankitems = replicate 100  -- "100 ought to be enough for anyone"
+    blankitems = replicate uiNumBlankItems
           RegisterScreenItem{rsItemDate          = ""
                             ,rsItemStatus        = Unmarked
                             ,rsItemDescription   = ""
@@ -137,13 +137,13 @@
                 ts = map rsItemTransaction displayitems
         endidx = max 0 $ length displayitems - 1
 
-rsInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
+rsInit _ _ _ = dlogUiTrace "rsInit 2" $ errorWrongScreenType "init function"  -- PARTIAL:
 
 rsDraw :: UIState -> [Widget Name]
 rsDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}}
               ,aScreen=RegisterScreen{..}
               ,aMode=mode
-              } =
+              } = dlogUiTrace "rsDraw 1" $
   case mode of
     Help       -> [helpDialog copts, maincontent]
     -- Minibuffer e -> [minibuffer e, maincontent]
@@ -199,7 +199,7 @@
         -- inclusive = tree_ ropts || rsForceInclusive
 
         toplabel =
-              withAttr ("border" <> "bold") (str $ T.unpack $ replaceHiddenAccountsNameWith "All" rsAccount)
+              withAttr (attrName "border" <> attrName "bold") (str $ T.unpack $ replaceHiddenAccountsNameWith "All" rsAccount)
 --           <+> withAttr ("border" <> "query") (str $ if inclusive then "" else " exclusive")
           <+> togglefilters
           <+> str " transactions"
@@ -212,7 +212,7 @@
           <+> str "/"
           <+> total
           <+> str ")"
-          <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr ("border" <> "query") (str " ignoring balance assertions") else str "")
+          <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions") else str "")
           where
             togglefilters =
               case concat [
@@ -221,7 +221,7 @@
                   ,if empty_ ropts then [] else ["nonzero"]
                   ] of
                 [] -> str ""
-                fs -> withAttr ("border" <> "query") (str $ " " ++ intercalate ", " fs)
+                fs -> withAttr (attrName "border" <> attrName "query") (str $ " " ++ intercalate ", " fs)
             cur = str $ case rsList ^. listSelectedL of
                          Nothing -> "-"
                          Just i -> show (i + 1)
@@ -251,7 +251,7 @@
 --               ,("q", "quit")
               ]
 
-rsDraw _ = error "draw function called with wrong screen type, should not happen"  -- PARTIAL:
+rsDraw _ = dlogUiTrace "rsDraw 2" $ errorWrongScreenType "draw function"  -- PARTIAL:
 
 rsDrawItem :: (Int,Int,Int,Int,Int) -> Bool -> RegisterScreenItem -> Widget Name
 rsDrawItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected RegisterScreenItem{..} =
@@ -271,164 +271,163 @@
   where
     changeAmt  = wbToText rsItemChangeAmount
     balanceAmt = wbToText rsItemBalanceAmount
-    changeattr | T.any (=='-') changeAmt  = sel $ "list" <> "amount" <> "decrease"
-               | otherwise                = sel $ "list" <> "amount" <> "increase"
-    balattr    | T.any (=='-') balanceAmt = sel $ "list" <> "balance" <> "negative"
-               | otherwise                = sel $ "list" <> "balance" <> "positive"
-    sel | selected  = (<> "selected")
+    changeattr | T.any (=='-') changeAmt  = sel $ attrName "list" <> attrName "amount" <> attrName "decrease"
+               | otherwise                = sel $ attrName "list" <> attrName "amount" <> attrName "increase"
+    balattr    | T.any (=='-') balanceAmt = sel $ attrName "list" <> attrName "balance" <> attrName "negative"
+               | otherwise                = sel $ attrName "list" <> attrName "balance" <> attrName "positive"
+    sel | selected  = (<> attrName "selected")
         | otherwise = id
 
-rsHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
-rsHandle ui@UIState{
-   aScreen=s@RegisterScreen{..}
-  ,aopts=UIOpts{uoCliOpts=copts}
-  ,ajournal=j
-  ,aMode=mode
-  } ev = do
-  let
-    d = copts^.rsDay
-    journalspan = journalDateSpan False j
-    nonblanks = V.takeWhile (not . T.null . rsItemDate) $ rsList^.listElementsL
-    lastnonblankidx = max 0 (length nonblanks - 1)
+rsHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+rsHandle ev = do
+  ui0 <- get'
+  dlogUiTraceM "rsHandle 1"
+  case ui0 of
+    ui@UIState{
+      aScreen=scr@RegisterScreen{..}
+      ,aopts=UIOpts{uoCliOpts=copts}
+      ,ajournal=j
+      ,aMode=mode
+      } -> do
+      let
+        d = copts^.rsDay
+        journalspan = journalDateSpan False j
+        nonblanks = V.takeWhile (not . T.null . rsItemDate) $ rsList^.listElementsL
+        lastnonblankidx = max 0 (length nonblanks - 1)
 
-  case mode of
-    Minibuffer _ ed ->
-      case ev of
-        VtyEvent (EvKey KEsc   []) -> continue $ closeMinibuffer ui
-        VtyEvent (EvKey KEnter []) -> continue $ regenerateScreens j d $
-            case setFilter s $ closeMinibuffer ui of
-              Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui
-              Right ui' -> ui'
-          where s = chomp . unlines . map strip $ getEditContents ed
-        -- VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer ui
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        VtyEvent ev -> do
-          ed' <- handleEditorEvent 
-#if MIN_VERSION_brick(0,72,0)
-            (VtyEvent ev)
-#else
-            ev
-#endif
-            ed
-          continue $ ui{aMode=Minibuffer "filter" ed'}
-        AppEvent _        -> continue ui
-        MouseDown{}       -> continue ui
-        MouseUp{}         -> continue ui
+      case mode of
+        Minibuffer _ ed ->
+          case ev of
+            VtyEvent (EvKey KEsc   []) -> modify' closeMinibuffer
+            VtyEvent (EvKey KEnter []) -> put' $ regenerateScreens j d $
+                case setFilter s $ closeMinibuffer ui of
+                  Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui
+                  Right ui' -> ui'
+              where s = chomp . unlines . map strip $ getEditContents ed
+            -- VtyEvent (EvKey (KChar '/') []) -> put' $ regenerateScreens j d $ showMinibuffer ui
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            VtyEvent e -> do
+              ed' <- nestEventM' ed $ handleEditorEvent (VtyEvent e)
+              put' ui{aMode=Minibuffer "filter" ed'}
+            AppEvent _  -> return ()
+            MouseDown{} -> return ()
+            MouseUp{}   -> return ()
 
-    Help ->
-      case ev of
-        -- VtyEvent (EvKey (KChar 'q') []) -> halt ui
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        _                    -> helpHandle ui ev
+        Help ->
+          case ev of
+            -- VtyEvent (EvKey (KChar 'q') []) -> halt
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            _ -> helpHandle ev
 
-    Normal ->
-      case ev of
-        VtyEvent (EvKey (KChar 'q') []) -> halt ui
-        VtyEvent (EvKey KEsc        []) -> continue $ resetScreens d 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
-            p = reportPeriod ui
-        e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
-          liftIO (uiReloadJournal copts d ui) >>= continue
-        VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
-        VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
-        VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
-        VtyEvent (EvKey (KChar 'T') []) -> continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
-        VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui
-          where
-            (pos,f) = case listSelectedElement rsList of
-                        Nothing -> (endPosition, journalFilePath j)
-                        Just (_, RegisterScreenItem{
-                          rsItemTransaction=Transaction{tsourcepos=(SourcePos f l c,_)}}) -> (Just (unPos l, Just $ unPos c),f)
+        Normal ->
+          case ev of
+            VtyEvent (EvKey (KChar 'q') []) -> halt
+            VtyEvent (EvKey KEsc        []) -> put' $ resetScreens d ui
+            VtyEvent (EvKey (KChar c)   []) | c == '?' -> put' $ setMode Help ui
+            AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
+              put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
+              where
+                p = reportPeriod ui
+            e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
+              liftIO (uiReloadJournal copts d ui) >>= put'
+            VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
+            VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
+            VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
+            VtyEvent (EvKey (KChar 'T') []) -> put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
+            VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui
+              where
+                (pos,f) = case listSelectedElement rsList of
+                            Nothing -> (endPosition, journalFilePath j)
+                            Just (_, RegisterScreenItem{
+                              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 $ toggleConversionOp ui
-        VtyEvent (EvKey (KChar 'V') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleValue ui
-        VtyEvent (EvKey (KChar 'H') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleHistorical ui
-        VtyEvent (EvKey (KChar 't') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleTree ui
-        VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> rsCenterAndContinue $ regenerateScreens j d $ toggleEmpty ui
-        VtyEvent (EvKey (KChar 'R') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleReal ui
-        VtyEvent (EvKey (KChar 'U') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui
-        VtyEvent (EvKey (KChar 'P') []) -> rsCenterAndContinue $ regenerateScreens j d $ togglePending ui
-        VtyEvent (EvKey (KChar 'C') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleCleared ui
-        VtyEvent (EvKey (KChar 'F') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleForecast d ui
+            -- display mode/query toggles
+            VtyEvent (EvKey (KChar 'B') []) -> rsCenterSelection (regenerateScreens j d $ toggleConversionOp ui) >>= put'
+            VtyEvent (EvKey (KChar 'V') []) -> rsCenterSelection (regenerateScreens j d $ toggleValue ui) >>= put'
+            VtyEvent (EvKey (KChar 'H') []) -> rsCenterSelection (regenerateScreens j d $ toggleHistorical ui) >>= put'
+            VtyEvent (EvKey (KChar 't') []) -> rsCenterSelection (regenerateScreens j d $ toggleTree ui) >>= put'
+            VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> rsCenterSelection (regenerateScreens j d $ toggleEmpty ui) >>= put'
+            VtyEvent (EvKey (KChar 'R') []) -> rsCenterSelection (regenerateScreens j d $ toggleReal ui) >>= put'
+            VtyEvent (EvKey (KChar 'U') []) -> rsCenterSelection (regenerateScreens j d $ toggleUnmarked ui) >>= put'
+            VtyEvent (EvKey (KChar 'P') []) -> rsCenterSelection (regenerateScreens j d $ togglePending ui) >>= put'
+            VtyEvent (EvKey (KChar 'C') []) -> rsCenterSelection (regenerateScreens j d $ toggleCleared ui) >>= put'
+            VtyEvent (EvKey (KChar 'F') []) -> rsCenterSelection (regenerateScreens j d $ toggleForecast d ui) >>= put'
 
-        VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui
-        VtyEvent (EvKey (KDown)     [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui
-        VtyEvent (EvKey (KUp)       [MShift]) -> continue $ regenerateScreens j d $ growReportPeriod d ui
-        VtyEvent (EvKey (KRight)    [MShift]) -> continue $ regenerateScreens j d $ nextReportPeriod journalspan ui
-        VtyEvent (EvKey (KLeft)     [MShift]) -> continue $ regenerateScreens j d $ previousReportPeriod journalspan ui
-        VtyEvent (EvKey k           []) | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui)
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle rsList >> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            VtyEvent (EvKey (KChar '/') []) -> put' $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui
+            VtyEvent (EvKey (KDown)     [MShift]) -> put' $ regenerateScreens j d $ shrinkReportPeriod d ui
+            VtyEvent (EvKey (KUp)       [MShift]) -> put' $ regenerateScreens j d $ growReportPeriod d ui
+            VtyEvent (EvKey (KRight)    [MShift]) -> put' $ regenerateScreens j d $ nextReportPeriod journalspan ui
+            VtyEvent (EvKey (KLeft)     [MShift]) -> put' $ regenerateScreens j d $ previousReportPeriod journalspan ui
+            VtyEvent (EvKey k           []) | k `elem` [KBS, KDel] -> (put' $ regenerateScreens j d $ resetFilter ui)
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle rsList >> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
 
-        -- exit screen on LEFT
-        VtyEvent e | e `elem` moveLeftEvents  -> continue $ popScreen ui
-        -- or on a click in the app's left margin. This is a VtyEvent since not in a clickable widget.
-        VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> continue $ popScreen ui
-        -- or on clicking a blank list item.
-        MouseUp _ (Just BLeft) Location{loc=(_,y)} | clickeddate == "" -> continue $ popScreen ui
-          where clickeddate = maybe "" rsItemDate $ listElements rsList !? y
+            -- exit screen on LEFT
+            VtyEvent e | e `elem` moveLeftEvents  -> put' $ popScreen ui
+            -- or on a click in the app's left margin. This is a VtyEvent since not in a clickable widget.
+            VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> put' $ popScreen ui
+            -- or on clicking a blank list item.
+            MouseUp _ (Just BLeft) Location{loc=(_,y)} | clickeddate == "" -> put' $ popScreen ui
+              where clickeddate = maybe "" rsItemDate $ listElements rsList !? y
 
-        -- enter transaction screen on RIGHT
-        VtyEvent e | e `elem` moveRightEvents ->
-          case listSelectedElement rsList of
-            Just _  -> continue $ screenEnter d transactionScreen{tsAccount=rsAccount} ui
-            Nothing -> continue ui
-        -- or on transaction click
-        -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347
-        -- just use it to move the selection
-        MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickeddate -> do
-          continue $ ui{aScreen=s{rsList=listMoveTo y rsList}}
-          where clickeddate = maybe "" rsItemDate $ listElements rsList !? y
-        -- and on MouseUp, enter the subscreen
-        MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickeddate -> do
-          continue $ screenEnter d transactionScreen{tsAccount=rsAccount} ui
-          where clickeddate = maybe "" rsItemDate $ listElements rsList !? y
+            -- enter transaction screen on RIGHT
+            VtyEvent e | e `elem` moveRightEvents ->
+              case listSelectedElement rsList of
+                Just _  -> put' $ screenEnter d transactionScreen{tsAccount=rsAccount} ui
+                Nothing -> put' ui
+            -- or on transaction click
+            -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347
+            -- just use it to move the selection
+            MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickeddate -> do
+              put' $ ui{aScreen=scr{rsList=listMoveTo y rsList}}
+              where clickeddate = maybe "" rsItemDate $ listElements rsList !? y
+            -- and on MouseUp, enter the subscreen
+            MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickeddate -> do
+              put' $ screenEnter d transactionScreen{tsAccount=rsAccount} ui
+              where clickeddate = maybe "" rsItemDate $ listElements rsList !? y
 
-        -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled
-        VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do
-          vScrollBy (viewportScroll $ rsList ^. listNameL) 1 >> continue ui
-          where mnextelement = listSelectedElement $ listMoveDown rsList
+            -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled
+            VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do
+              vScrollBy (viewportScroll $ rsList ^. listNameL) 1
+              where mnextelement = listSelectedElement $ listMoveDown rsList
 
-        -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,
-        -- pushing the selection when necessary.
-        MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do
-          let scrollamt = if btn==BScrollUp then -1 else 1
-          list' <- listScrollPushingSelection name rsList (rsListSize rsList) scrollamt
-          continue ui{aScreen=s{rsList=list'}}
+            -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,
+            -- pushing the selection when necessary.
+            MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do
+              let scrollamt = if btn==BScrollUp then -1 else 1
+              list' <- nestEventM' rsList $ listScrollPushingSelection name (rsListSize rsList) scrollamt
+              put' ui{aScreen=scr{rsList=list'}}
 
-        -- if page down or end leads to a blank padding item, stop at last non-blank
-        VtyEvent e@(EvKey k           []) | k `elem` [KPageDown, KEnd] -> do
-          list <- handleListEvent e rsList
-          if isBlankElement $ listSelectedElement list
-          then do
-            let list' = listMoveTo lastnonblankidx list
-            scrollSelectionToMiddle list'
-            continue ui{aScreen=s{rsList=list'}}
-          else
-            continue ui{aScreen=s{rsList=list}}
+            -- if page down or end leads to a blank padding item, stop at last non-blank
+            VtyEvent e@(EvKey k           []) | k `elem` [KPageDown, KEnd] -> do
+              l <- nestEventM' rsList $ handleListEvent e
+              if isBlankElement $ listSelectedElement l
+              then do
+                let l' = listMoveTo lastnonblankidx l
+                scrollSelectionToMiddle l'
+                put' ui{aScreen=scr{rsList=l'}}
+              else
+                put' ui{aScreen=scr{rsList=l}}
 
-        -- fall through to the list's event handler (handles other [pg]up/down events)
-        VtyEvent ev -> do
-          let ev' = normaliseMovementKeys ev
-          newitems <- handleListEvent ev' rsList
-          continue ui{aScreen=s{rsList=newitems}}
+            -- fall through to the list's event handler (handles other [pg]up/down events)
+            VtyEvent e -> do
+              let e' = normaliseMovementKeys e
+              newitems <- nestEventM' rsList $ handleListEvent e'
+              put' ui{aScreen=scr{rsList=newitems}}
 
-        MouseDown{}       -> continue ui
-        MouseUp{}         -> continue ui
-        AppEvent _        -> continue ui
+            MouseDown{}       -> return ()
+            MouseUp{}         -> return ()
+            AppEvent _        -> return ()
 
-rsHandle _ _ = error "event handler called with wrong screen type, should not happen"  -- PARTIAL:
+    _ -> dlogUiTrace "rsHandle 2" $ errorWrongScreenType "event handler"
 
 isBlankElement mel = ((rsItemDate . snd) <$> mel) == Just ""
 
-rsCenterAndContinue ui = do
+rsCenterSelection :: UIState -> EventM Name UIState UIState
+rsCenterSelection ui = do
   scrollSelectionToMiddle $ rsList $ aScreen ui
-  continue ui
+  return ui  -- ui is unchanged, but this makes the function more chainable
 
 rsListSize = V.length . V.takeWhile ((/="").rsItemDate) . listElements
diff --git a/Hledger/UI/Theme.hs b/Hledger/UI/Theme.hs
--- a/Hledger/UI/Theme.hs
+++ b/Hledger/UI/Theme.hs
@@ -65,42 +65,42 @@
 themesList :: [(String, AttrMap)]
 themesList = [
    ("default", attrMap (black `on` white) [
-     ("border"                                        , white `on` black & dim)
-    ,("border" <> "bold"                              , currentAttr & bold)
-    ,("border" <> "depth"                             , active)
-    ,("border" <> "filename"                          , currentAttr)
-    ,("border" <> "key"                               , active)
-    ,("border" <> "minibuffer"                        , white `on` black & bold)
-    ,("border" <> "query"                             , active)
-    ,("border" <> "selected"                          , active)
-    ,("error"                                         , fg red)
-    ,("help"                                          , white `on` black & dim)
-    ,("help" <> "heading"                             , fg yellow)
-    ,("help" <> "key"                                 , active)
-    -- ,("list"                                          , black `on` white)
-    -- ,("list" <> "amount"                              , currentAttr)
-    ,("list" <> "amount" <> "decrease"                , fg red)
-    -- ,("list" <> "amount" <> "increase"                , fg green)
-    ,("list" <> "amount" <> "decrease" <> "selected"  , red `on` selectbg & bold)
-    -- ,("list" <> "amount" <> "increase" <> "selected"  , green `on` selectbg & bold)
-    ,("list" <> "balance"                             , currentAttr & bold)
-    ,("list" <> "balance" <> "negative"               , fg red)
-    ,("list" <> "balance" <> "positive"               , fg black)
-    ,("list" <> "balance" <> "negative" <> "selected" , red `on` selectbg & bold)
-    ,("list" <> "balance" <> "positive" <> "selected" , select & bold)
-    ,("list" <> "selected"                            , select)
-    -- ,("list" <> "accounts"                         , white `on` brightGreen)
-    -- ,("list" <> "selected"                         , black `on` brightYellow)
+     (attrName "border"                                        , white `on` black & dim)
+    ,(attrName "border" <> attrName "bold"                              , currentAttr & bold)
+    ,(attrName "border" <> attrName "depth"                             , active)
+    ,(attrName "border" <> attrName "filename"                          , currentAttr)
+    ,(attrName "border" <> attrName "key"                               , active)
+    ,(attrName "border" <> attrName "minibuffer"                        , white `on` black & bold)
+    ,(attrName "border" <> attrName "query"                             , active)
+    ,(attrName "border" <> attrName "selected"                          , active)
+    ,(attrName "error"                                         , fg red)
+    ,(attrName "help"                                          , white `on` black & dim)
+    ,(attrName "help" <> attrName "heading"                             , fg yellow)
+    ,(attrName "help" <> attrName "key"                                 , active)
+    -- ,(attrName "list"                                          , black `on` white)
+    -- ,(attrName "list" <> attrName "amount"                              , currentAttr)
+    ,(attrName "list" <> attrName "amount" <> attrName "decrease"                , fg red)
+    -- ,(attrName "list" <> attrName "amount" <> attrName "increase"                , fg green)
+    ,(attrName "list" <> attrName "amount" <> attrName "decrease" <> attrName "selected"  , red `on` selectbg & bold)
+    -- ,(attrName "list" <> attrName "amount" <> attrName "increase" <> attrName "selected"  , green `on` selectbg & bold)
+    ,(attrName "list" <> attrName "balance"                             , currentAttr & bold)
+    ,(attrName "list" <> attrName "balance" <> attrName "negative"               , fg red)
+    ,(attrName "list" <> attrName "balance" <> attrName "positive"               , fg black)
+    ,(attrName "list" <> attrName "balance" <> attrName "negative" <> attrName "selected" , red `on` selectbg & bold)
+    ,(attrName "list" <> attrName "balance" <> attrName "positive" <> attrName "selected" , select & bold)
+    ,(attrName "list" <> attrName "selected"                            , select)
+    -- ,(attrName "list" <> attrName "accounts"                         , white `on` brightGreen)
+    -- ,(attrName "list" <> attrName "selected"                         , black `on` brightYellow)
   ])
 
   ,("greenterm", attrMap (green `on` black) [
-    ("list" <> "selected"                             , black `on` green)
+    (attrName "list" <> attrName "selected"                             , black `on` green)
   ])
 
   ,("terminal", attrMap defAttr [
-    ("border"                                         , white `on` black),
-    ("list"                                           , defAttr),
-    ("list" <> "selected"                             , defAttr & reverseVideo)
+    (attrName "border"                                         , white `on` black),
+    (attrName "list"                                           , defAttr),
+    (attrName "list" <> attrName "selected"                             , defAttr & reverseVideo)
   ])
 
   ]
diff --git a/Hledger/UI/TransactionScreen.hs b/Hledger/UI/TransactionScreen.hs
--- a/Hledger/UI/TransactionScreen.hs
+++ b/Hledger/UI/TransactionScreen.hs
@@ -22,7 +22,7 @@
 import Brick.Widgets.List (listElementsL, listMoveTo, listSelectedElement)
 
 import Hledger
-import Hledger.Cli hiding (progname,prognameandversion)
+import Hledger.Cli hiding (mode, prices, progname,prognameandversion)
 import Hledger.UI.UIOptions
 -- import Hledger.UI.Theme
 import Hledger.UI.UITypes
@@ -59,7 +59,7 @@
             seltxn = maybe nulltransaction (rsItemTransaction . snd) $ listSelectedElement xs
             nonblanks = V.toList . V.takeWhile (not . T.null . rsItemDate) $ xs ^. listElementsL
         _                           -> (t, nts)
-tsInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
+tsInit _ _ _ = errorWrongScreenType "init function"  -- PARTIAL:
 
 -- Render a transaction suitably for the transaction screen.
 showTxn :: ReportOpts -> ReportSpec -> Journal -> Transaction -> T.Text
@@ -107,12 +107,12 @@
           -- <+> str (" ("++show i++" of "++show (length nts)++" in "++acct++")")
           <+> (str $ "#" ++ show (tindex t))
           <+> str " ("
-          <+> withAttr ("border" <> "bold") (str $ show i)
+          <+> withAttr (attrName "border" <> attrName "bold") (str $ show i)
           <+> str (" of "++show (length nts))
           <+> togglefilters
           <+> borderQueryStr (unwords . map (quoteIfNeeded . T.unpack) $ querystring_ ropts)
           <+> str (" in "++T.unpack (replaceHiddenAccountsNameWith "All" acct)++")")
-          <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr ("border" <> "query") (str " ignoring balance assertions") else str "")
+          <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions") else str "")
           where
             togglefilters =
               case concat [
@@ -121,7 +121,7 @@
                   ,if empty_ ropts then [] else ["nonzero"]
                   ] of
                 [] -> str ""
-                fs -> withAttr ("border" <> "query") (str $ " " ++ intercalate ", " fs)
+                fs -> withAttr (attrName "border" <> attrName "query") (str $ " " ++ intercalate ", " fs)
 
         bottomlabel = quickhelp
                         -- case mode of
@@ -139,72 +139,74 @@
               ,("q", "quit")
               ]
 
-tsDraw _ = error "draw function called with wrong screen type, should not happen"  -- PARTIAL:
+tsDraw _ = errorWrongScreenType "draw function"  -- PARTIAL:
 
-tsHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
-tsHandle ui@UIState{aScreen=TransactionScreen{tsTransaction=(i,t), tsTransactions=nts}
-                   ,aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}
-                   ,ajournal=j
-                   ,aMode=mode
-                   }
-         ev =
-  case mode of
-    Help ->
-      case ev of
-        -- VtyEvent (EvKey (KChar 'q') []) -> halt ui
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        _                    -> helpHandle ui ev
+tsHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+tsHandle ev = do
+  ui0 <- get'
+  case ui0 of
+    ui@UIState{aScreen=TransactionScreen{tsTransaction=(i,t), tsTransactions=nts}
+                    ,aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}
+                    ,ajournal=j
+                    ,aMode=mode
+                    } ->
+      case mode of
+        Help ->
+          case ev of
+            -- VtyEvent (EvKey (KChar 'q') []) -> halt
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            _ -> helpHandle ev
 
-    _ -> do
-      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 == '?' -> 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
-                        (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 ()
-          ej <- liftIO . runExceptT $ journalReload copts
-          case ej of
-            Left err -> continue $ screenEnter d errorScreen{esError=err} ui
-            Right j' -> continue $ regenerateScreens j' d ui
-        VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
+        _ -> do
+          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
+            VtyEvent (EvKey KEsc        []) -> put' $ resetScreens d ui
+            VtyEvent (EvKey (KChar c)   []) | c == '?' -> put' $ setMode Help ui
+            VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui
+              where
+                (pos,f) = case tsourcepos t of
+                            (SourcePos f' l1 c1,_) -> (Just (unPos l1, Just $ unPos c1),f')
+            AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
+              put' $ 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 ()
+              ej <- liftIO . runExceptT $ journalReload copts
+              case ej of
+                Left err -> put' $ screenEnter d errorScreen{esError=err} ui
+                Right j' -> put' $ regenerateScreens j' d ui
+            VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
 
-        -- for toggles that may change the current/prev/next transactions,
-        -- we must regenerate the transaction list, like the g handler above ? with regenerateTransactions ? TODO WIP
-        -- EvKey (KChar 'E') [] -> continue $ regenerateScreens j d $ stToggleEmpty ui
-        -- EvKey (KChar 'C') [] -> continue $ regenerateScreens j d $ stToggleCleared ui
-        -- EvKey (KChar 'R') [] -> continue $ regenerateScreens j d $ stToggleReal ui
-        VtyEvent (EvKey (KChar 'B') []) -> continue . regenerateScreens j d $ toggleConversionOp ui
-        VtyEvent (EvKey (KChar 'V') []) -> continue . regenerateScreens j d $ toggleValue ui
+            -- for toggles that may change the current/prev/next transactions,
+            -- we must regenerate the transaction list, like the g handler above ? with regenerateTransactions ? TODO WIP
+            -- EvKey (KChar 'E') [] -> put' $ regenerateScreens j d $ stToggleEmpty ui
+            -- EvKey (KChar 'C') [] -> put' $ regenerateScreens j d $ stToggleCleared ui
+            -- EvKey (KChar 'R') [] -> put' $ regenerateScreens j d $ stToggleReal ui
+            VtyEvent (EvKey (KChar 'B') []) -> put' . regenerateScreens j d $ toggleConversionOp ui
+            VtyEvent (EvKey (KChar 'V') []) -> put' . regenerateScreens j d $ toggleValue ui
 
-        VtyEvent e | e `elem` moveUpEvents   -> continue $ tsSelect iprev tprev ui
-        VtyEvent e | e `elem` moveDownEvents -> continue $ tsSelect inext tnext ui
+            VtyEvent e | e `elem` moveUpEvents   -> put' $ tsSelect iprev tprev ui
+            VtyEvent e | e `elem` moveDownEvents -> put' $ tsSelect inext tnext ui
 
-        -- exit screen on LEFT
-        VtyEvent e | e `elem` moveLeftEvents -> continue . popScreen $ tsSelect i t ui  -- Probably not necessary to tsSelect here, but it's safe.
-        -- or on a click in the app's left margin.
-        VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> continue . popScreen $ tsSelect i t ui
-        -- or on clicking the blank area below the transaction.
-        MouseUp _ (Just BLeft) Location{loc=(_,y)} | y+1 > numentrylines -> continue . popScreen $ tsSelect i t ui
-          where numentrylines = length (T.lines $ showTxn ropts rspec j t) - 1
+            -- exit screen on LEFT
+            VtyEvent e | e `elem` moveLeftEvents -> put' . popScreen $ tsSelect i t ui  -- Probably not necessary to tsSelect here, but it's safe.
+            -- or on a click in the app's left margin.
+            VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> put' . popScreen $ tsSelect i t ui
+            -- or on clicking the blank area below the transaction.
+            MouseUp _ (Just BLeft) Location{loc=(_,y)} | y+1 > numentrylines -> put' . popScreen $ tsSelect i t ui
+              where numentrylines = length (T.lines $ showTxn ropts rspec j t) - 1
 
-        VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
-        VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-        _ -> continue ui
+            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+            _ -> return ()
 
-tsHandle _ _ = error "event handler called with wrong screen type, should not happen"  -- PARTIAL:
+    _ -> errorWrongScreenType "event handler"
 
 -- | Select a new transaction and update the previous register screen
 tsSelect i t ui@UIState{aScreen=s@TransactionScreen{}} = case aPrevScreens ui of
diff --git a/Hledger/UI/UIState.hs b/Hledger/UI/UIState.hs
--- a/Hledger/UI/UIState.hs
+++ b/Hledger/UI/UIState.hs
@@ -210,7 +210,7 @@
 
 -- | Apply a new filter query, or return the failing query.
 setFilter :: String -> UIState -> Either String UIState
-setFilter s = first (const s) . setEither querystring (words'' prefixes $ T.pack s)
+setFilter s = first (const s) . setEither querystring (words'' queryprefixes $ T.pack s)
 
 -- | Reset some filters & toggles.
 resetFilter :: UIState -> UIState
@@ -286,11 +286,11 @@
   -- remove all the screens from the appstate and then add them back
   -- one at a time, regenerating as we go.
   let
-    first:rest = reverse $ s:ss :: [Screen]
-    ui0 = ui{ajournal=j, aScreen=first, aPrevScreens=[]} :: UIState
+    frst:rest = reverse $ s:ss :: [Screen]
+    ui0 = ui{ajournal=j, aScreen=frst, aPrevScreens=[]} :: UIState
 
-    ui1 = (sInit first) d False ui0 :: UIState
-    ui2 = foldl' (\ui s -> (sInit s) d False $ pushScreen s ui) ui1 rest :: UIState
+    ui1 = (sInit frst) d False ui0 :: UIState
+    ui2 = foldl' (\ui' s' -> (sInit s') d False $ pushScreen s' ui') ui1 rest :: UIState
   in
     ui2
 
diff --git a/Hledger/UI/UITypes.hs b/Hledger/UI/UITypes.hs
--- a/Hledger/UI/UITypes.hs
+++ b/Hledger/UI/UITypes.hs
@@ -33,11 +33,14 @@
 
 {-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE DeriveAnyClass     #-}
+{-# LANGUAGE FlexibleInstances  #-}
 {-# LANGUAGE OverloadedStrings  #-}
 {-# LANGUAGE TemplateHaskell    #-}
 
 module Hledger.UI.UITypes where
 
+-- import Control.Concurrent (threadDelay)
+-- import GHC.IO (unsafePerformIO)
 import Data.Text (Text)
 import Data.Time.Calendar (Day)
 import Brick
@@ -100,7 +103,7 @@
     AccountsScreen {
        sInit   :: Day -> Bool -> UIState -> UIState              -- ^ function to initialise or update this screen's state
       ,sDraw   :: UIState -> [Widget Name]                             -- ^ brick renderer for this screen
-      ,sHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)  -- ^ brick event handler for this screen
+      ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()  -- ^ brick event handler for this screen
       -- state fields.These ones have lenses:
       ,_asList            :: List Name AccountsScreenItem  -- ^ list widget showing account names & balances
       ,_asSelectedAccount :: AccountName              -- ^ a backup of the account name from the list widget's selected item (or "")
@@ -108,7 +111,7 @@
   | RegisterScreen {
        sInit   :: Day -> Bool -> UIState -> UIState
       ,sDraw   :: UIState -> [Widget Name]
-      ,sHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
+      ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
       --
       ,rsList    :: List Name RegisterScreenItem      -- ^ list widget showing transactions affecting this account
       ,rsAccount :: AccountName                       -- ^ the account this register is for
@@ -119,7 +122,7 @@
   | TransactionScreen {
        sInit   :: Day -> Bool -> UIState -> UIState
       ,sDraw   :: UIState -> [Widget Name]
-      ,sHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
+      ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
       --
       ,tsTransaction  :: NumberedTransaction          -- ^ the transaction we are currently viewing, and its position in the list
       ,tsTransactions :: [NumberedTransaction]        -- ^ list of transactions we can step through
@@ -128,11 +131,18 @@
   | ErrorScreen {
        sInit   :: Day -> Bool -> UIState -> UIState
       ,sDraw   :: UIState -> [Widget Name]
-      ,sHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
+      ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
       --
       ,esError :: String                              -- ^ error message to show
     }
   deriving (Show)
+-- XXX check for ideas: https://github.com/jtdaugherty/brick/issues/379#issuecomment-1191993357
+
+-- | Error message to use in case statements adapting to the different Screen shapes.
+errorWrongScreenType :: String -> a
+errorWrongScreenType lbl =
+  -- unsafePerformIO $ threadDelay 2000000 >>  -- delay to allow console output to be seen
+  error' (unwords [lbl, "called with wrong screen type, should not happen"])
 
 -- | An item in the accounts screen's list of accounts and balances.
 data AccountsScreenItem = AccountsScreenItem {
diff --git a/Hledger/UI/UIUtils.hs b/Hledger/UI/UIUtils.hs
--- a/Hledger/UI/UIUtils.hs
+++ b/Hledger/UI/UIUtils.hs
@@ -23,10 +23,17 @@
   ,renderToggle1
   ,replaceHiddenAccountsNameWith
   ,scrollSelectionToMiddle
+  ,get'
+  ,put'
+  ,modify'
   ,suspend
   ,redraw
   ,reportSpecSetFutureAndForecast
   ,listScrollPushingSelection
+  ,dlogUiTrace
+  ,dlogUiTraceM
+  ,uiDebugLevel
+  ,uiNumBlankItems
   )
 where
 
@@ -35,7 +42,7 @@
 import Brick.Widgets.Border.Style
 import Brick.Widgets.Dialog
 import Brick.Widgets.Edit
-import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL, listSelected, listMoveDown, listMoveUp, GenericList, Splittable)
+import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL, listSelected, listMoveDown, listMoveUp, GenericList)
 import Control.Monad.IO.Class
 import Data.Bifunctor (second)
 import Data.List
@@ -60,20 +67,38 @@
 suspendSignal = return ()
 #else
 import System.Posix.Signals
+import Data.Vector (Vector)
 suspendSignal :: IO ()
 suspendSignal = raiseSignal sigSTOP
 #endif
 
+-- Debug logging for UI state changes.
+
+get' = do
+  x <- get
+  dlogUiTraceM $ "getting state: " ++ (head $ lines $ pshow $ aScreen x)
+  return x
+
+put' x = do
+  dlogUiTraceM $ "putting state: " ++ (head $ lines $ pshow $ aScreen x)
+  put x
+
+modify' f = do
+  x <- get
+  let x' = f x
+  dlogUiTraceM $ "modifying state: " ++ (head $ lines $ pshow $ aScreen x')
+  modify f
+
 -- | On posix platforms, suspend the program using the STOP signal,
 -- like control-z in bash, returning to the original shell prompt,
 -- and when resumed, continue where we left off.
 -- On windows, does nothing.
-suspend :: s -> EventM a (Next s)
+suspend :: Ord a => s -> EventM a s ()
 suspend st = suspendAndResume $ suspendSignal >> return st
 
--- | Tell vty to redraw the whole screen, and continue.
-redraw :: s -> EventM a (Next s)
-redraw st = getVtyHandle >>= liftIO . refresh >> continue st
+-- | Tell vty to redraw the whole screen.
+redraw :: EventM a s ()
+redraw = getVtyHandle >>= liftIO . refresh
 
 -- | Wrap a widget in the default hledger-ui screen layout.
 defaultLayout :: Widget Name -> Widget Name -> Widget Name -> Widget Name
@@ -90,14 +115,14 @@
   Widget Fixed Fixed $ do
     c <- getContext
     render $
-      withDefAttr "help" $
+      withDefAttr (attrName "help") $
       renderDialog (dialog (Just "Help (LEFT/ESC/?/q to close help)") Nothing (c^.availWidthL)) $ -- (Just (0,[("ok",())]))
       padTop (Pad 0) $ padLeft (Pad 1) $ padRight (Pad 1) $
         vBox [
            hBox [
               padRight (Pad 1) $
                 vBox [
-                   withAttr ("help" <> "heading") $ str "Navigation"
+                   withAttr (attrName "help" <> attrName "heading") $ str "Navigation"
                   ,renderKey ("UP/DOWN/PUP/PDN/HOME/END/k/j/C-p/C-n", "")
                   ,str "     move selection up/down"
                   ,renderKey ("RIGHT/l/C-f", "show txns, or txn detail")
@@ -105,23 +130,23 @@
                   ,renderKey ("ESC ", "cancel, or reset app state")
 
                   ,str " "
-                  ,withAttr ("help" <> "heading") $ str "Accounts screen"
+                  ,withAttr (attrName "help" <> attrName "heading") $ str "Accounts screen"
                   ,renderKey ("1234567890-+ ", "set/adjust depth limit")
                   ,renderKey ("t ", "toggle accounts tree/list mode")
                   ,renderKey ("H ", "toggle historical balance/change")
                   ,str " "
-                  ,withAttr ("help" <> "heading") $ str "Register screen"
+                  ,withAttr (attrName "help" <> attrName "heading") $ str "Register screen"
                   ,renderKey ("t ", "toggle subaccount txns\n(and accounts tree/list mode)")
                   ,renderKey ("H ", "toggle historical/period total")
                   ,str " "
-                  ,withAttr ("help" <> "heading") $ str "Help"
+                  ,withAttr (attrName "help" <> attrName "heading") $ str "Help"
                   ,renderKey ("?    ", "toggle this help")
                   ,renderKey ("p/m/i", "while help is open:\nshow manual in pager/man/info")
                   ,str " "
                 ]
              ,padLeft (Pad 1) $ padRight (Pad 0) $
                 vBox [
-                   withAttr ("help" <> "heading") $ str "Filtering"
+                   withAttr (attrName "help" <> attrName "heading") $ str "Filtering"
                   ,renderKey ("/   ", "set a filter query")
                   ,renderKey ("F   ", "show future & periodic txns")
                   ,renderKey ("R   ", "show real/all postings")
@@ -132,7 +157,7 @@
                   ,renderKey ("T             ", "set period to today")
                   ,renderKey ("DEL ", "reset filters")
                   ,str " "
-                  ,withAttr ("help" <> "heading") $ str "Other"
+                  ,withAttr (attrName "help" <> attrName "heading") $ str "Other"
                   ,renderKey ("a   ", "add transaction (hledger add)")
                   ,renderKey ("A   ", "add transaction (hledger-iadd)")
                   ,renderKey ("B   ", "show amounts/costs")
@@ -160,39 +185,40 @@
 --             ]
           ]
   where
-    renderKey (key,desc) = withAttr ("help" <> "key") (str key) <+> str " " <+> str desc
+    renderKey (key,desc) = withAttr (attrName "help" <> attrName "key") (str key) <+> str " " <+> str desc
 
 -- | Event handler used when help mode is active.
 -- May invoke $PAGER, less, man or info, which is likely to fail on MS Windows, TODO.
-helpHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
-helpHandle ui ev = do
+helpHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+helpHandle ev = do
+  ui <- get
+  let ui' = setMode Normal ui
   case ev of
-    VtyEvent e | e `elem` closeHelpEvents -> continue $ setMode Normal ui
-    VtyEvent (EvKey (KChar 'p') []) -> suspendAndResume $ runPagerForTopic "hledger-ui" Nothing >> return ui'
-    VtyEvent (EvKey (KChar 'm') []) -> suspendAndResume $ runManForTopic   "hledger-ui" Nothing >> return ui'
-    VtyEvent (EvKey (KChar 'i') []) -> suspendAndResume $ runInfoForTopic  "hledger-ui" Nothing >> return ui'
-    _ -> continue ui
+    VtyEvent e | e `elem` closeHelpEvents -> put' ui'
+    VtyEvent (EvKey (KChar 'p') []) -> suspendAndResume (runPagerForTopic "hledger-ui" Nothing >> return ui')
+    VtyEvent (EvKey (KChar 'm') []) -> suspendAndResume (runManForTopic   "hledger-ui" Nothing >> return ui')
+    VtyEvent (EvKey (KChar 'i') []) -> suspendAndResume (runInfoForTopic  "hledger-ui" Nothing >> return ui')
+    _ -> return ()
   where
-    ui' = setMode Normal ui
     closeHelpEvents = moveLeftEvents ++ [EvKey KEsc [], EvKey (KChar '?') [], EvKey (KChar 'q') []]
 
 -- | Draw the minibuffer with the given label.
 minibuffer :: T.Text -> Editor String Name -> Widget Name
 minibuffer string ed =
-  forceAttr ("border" <> "minibuffer") $
+  forceAttr (attrName "border" <> attrName "minibuffer") $
   hBox [txt $ string <> ": ", renderEditor (str . unlines) True ed]
 
 borderQueryStr :: String -> Widget Name
 borderQueryStr ""  = str ""
-borderQueryStr qry = str " matching " <+> withAttr ("border" <> "query") (str qry)
+borderQueryStr qry = str " matching " <+> withAttr (attrName "border" <> attrName "query") (str qry)
 
 borderDepthStr :: Maybe Int -> Widget Name
 borderDepthStr Nothing  = str ""
-borderDepthStr (Just d) = str " to depth " <+> withAttr ("border" <> "query") (str $ show d)
+borderDepthStr (Just d) = str " to depth " <+> withAttr (attrName "border" <> attrName "query") (str $ show d)
 
 borderPeriodStr :: String -> Period -> Widget Name
 borderPeriodStr _           PeriodAll = str ""
-borderPeriodStr preposition p         = str (" "++preposition++" ") <+> withAttr ("border" <> "query") (str . T.unpack $ showPeriod p)
+borderPeriodStr preposition p         = str (" "++preposition++" ") <+> withAttr (attrName "border" <> attrName "query") (str . T.unpack $ showPeriod p)
 
 borderKeysStr :: [(String,String)] -> Widget Name
 borderKeysStr = borderKeysStr' . map (second str)
@@ -201,7 +227,7 @@
 borderKeysStr' keydescs =
   hBox $
   intersperse sep $
-  [withAttr ("border" <> "key") (str keys) <+> str ":" <+> desc | (keys, desc) <- keydescs]
+  [withAttr (attrName "border" <> attrName "key") (str keys) <+> str ":" <+> desc | (keys, desc) <- keydescs]
   where
     -- sep = str " | "
     sep = str " "
@@ -209,7 +235,7 @@
 -- | Show both states of a toggle ("aaa/bbb"), highlighting the active one.
 renderToggle :: Bool -> String -> String -> Widget Name
 renderToggle isright l r =
-  let bold = withAttr ("border" <> "selected") in
+  let bold = withAttr (attrName "border" <> attrName "selected") in
   if isright
   then str (l++"/") <+> bold (str r)
   else bold (str l) <+> str ("/"++r)
@@ -217,7 +243,7 @@
 -- | Show a toggle's label, highlighted (bold) when the toggle is active.
 renderToggle1 :: Bool -> String -> Widget Name
 renderToggle1 isactive l =
-  let bold = withAttr ("border" <> "selected") in
+  let bold = withAttr (attrName "border" <> attrName "selected") in
   if isactive
   then bold (str l)
   else str l
@@ -262,11 +288,11 @@
           ""
           -- "  debug: "++show (_w,h')
     render $
-      hBorderWithLabel (withAttr "border" $ toplabel <+> str debugmsg)
+      hBorderWithLabel (withAttr (attrName "border") $ toplabel <+> str debugmsg)
       <=>
       body'
       <=>
-      hBorderWithLabel (withAttr "border" bottomlabel)
+      hBorderWithLabel (withAttr (attrName "border") bottomlabel)
 
 ---- XXX should be equivalent to the above, but isn't (page down goes offscreen)
 --_topBottomBorderWithLabel2 :: Widget Name -> Widget Name -> Widget Name
@@ -286,8 +312,8 @@
 -- 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
-    c <- getContext
-    let w' = vLimit (c^.availHeightL - v*2) $ hLimit (c^.availWidthL - h*2) w
+    ctx <- getContext
+    let w' = vLimit (ctx^.availHeightL - v*2) $ hLimit (ctx^.availWidthL - h*2) w
         attr = maybe currentAttr (\c -> c `on` c) mcolour
     render $
       withBorderAttr attr $
@@ -303,7 +329,7 @@
    -- applyN n border
 
 withBorderAttr :: Attr -> Widget Name -> Widget Name
-withBorderAttr attr = updateAttrMap (applyAttrMappings [("border", attr)])
+withBorderAttr attr = updateAttrMap (applyAttrMappings [(attrName "border", attr)])
 
 ---- | Like brick's continue, but first run some action to modify brick's state.
 ---- This action does not affect the app state, but might eg adjust a widget's scroll position.
@@ -319,7 +345,7 @@
 
 -- | Scroll a list's viewport so that the selected item is centered in the
 -- middle of the display area.
-scrollSelectionToMiddle :: List Name e -> EventM Name ()
+scrollSelectionToMiddle :: List Name item -> EventM Name UIState ()
 scrollSelectionToMiddle list = do
   case list^.listSelectedL of
     Nothing -> return ()
@@ -349,8 +375,8 @@
 -- 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]}
+reportSpecSetFutureAndForecast d fcast rspec =
+    rspec{_rsQuery=simplifyQuery $ And [_rsQuery rspec, periodq, excludeforecastq fcast]}
   where
     periodq = Date . periodAsDateSpan . period_ $ _rsReportOpts rspec
     -- Except in forecast mode, exclude future/forecast transactions.
@@ -364,9 +390,9 @@
 -- Vertically scroll the named list's viewport with the given number of non-empty items
 -- by the given positive or negative number of items (usually 1 or -1).
 -- The selection will be moved when necessary to keep it visible and allow the scroll.
-listScrollPushingSelection :: (Ord n, Foldable t, Splittable t) => 
-  n -> GenericList n t e -> Int -> Int -> EventM n (GenericList n t e)
-listScrollPushingSelection name list listheight scrollamt = do
+listScrollPushingSelection :: Name -> Int -> Int -> EventM Name (List Name item) (GenericList Name Vector item)
+listScrollPushingSelection name listheight scrollamt = do
+  list <- get
   viewportScroll name `vScrollBy` scrollamt
   mvp <- lookupViewport name
   case mvp of
@@ -381,3 +407,24 @@
               | otherwise = id
         _ -> return list
     _ -> return list
+
+-- | Log a string to ./debug.log before returning the second argument,
+-- if the global debug level is at or above a standard hledger-ui debug level.
+-- Uses unsafePerformIO.
+dlogUiTrace :: String -> a -> a
+dlogUiTrace = dlogTraceAt uiDebugLevel
+
+-- | Like dlogUiTrace, but within the hledger-ui brick event handler monad.
+dlogUiTraceM :: String -> EventM Name UIState ()
+dlogUiTraceM s = dlogUiTrace s $ return ()
+
+-- | Log hledger-ui events at this debug level.
+uiDebugLevel :: Int
+uiDebugLevel = 2
+
+-- | How many blank items to add to lists to fill the full window height.
+uiNumBlankItems :: Int
+uiNumBlankItems
+  -- | debugLevel >= uiDebugLevel = 0    -- suppress to improve debug output.
+  -- | otherwise 
+  = 100  -- 100 ought to be enough for anyone
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" "July 2022" "hledger-ui-1.26.1 " "hledger User Manuals"
+.TH "HLEDGER-UI" "1" "September 2022" "hledger-ui-1.27 " "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.26.1.
+This manual is for hledger-ui 1.27.
 .SH SYNOPSIS
 .PP
 \f[C]hledger-ui [OPTIONS] [QUERYARGS]\f[R]
@@ -557,9 +557,10 @@
 reboot.
 .PP
 On Windows computers, the default value is probably
-\f[C]C:\[rs]Users\[rs]MyUserName\[rs].hledger.journal\f[R].
+\f[C]C:\[rs]Users\[rs]YOURNAME\[rs].hledger.journal\f[R].
 You can change this by running a command like this in a powershell
-window:
+window (let us know if you need to be an Administrator, and if this
+persists across a reboot):
 .IP
 .nf
 \f[C]
@@ -567,8 +568,8 @@
 \f[R]
 .fi
 .PP
-(Let us know if you need to be an Administrator, and if this persists
-across a reboot.)
+Or, change it in settings: see
+https://www.java.com/en/download/help/path.html.
 .SH FILES
 .PP
 Reads data from one or more files in hledger journal, timeclock,
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.26.1
+version:        1.27
 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==9.0.1
+    GHC==8.10.7, GHC==9.0.2, GHC==9.2.4
 extra-source-files:
     CHANGES.md
     README.md
@@ -62,13 +62,14 @@
       Paths_hledger_ui
   hs-source-dirs:
       ./
-  ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-name-shadowing -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind
-  cpp-options: -DVERSION="1.26.1"
+  ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind
+  cpp-options: -DVERSION="1.27"
   build-depends:
       ansi-terminal >=0.9
     , async
-    , base >=4.11 && <4.17
-    , brick >=0.23
+    , base >=4.14 && <4.17
+    , breakpoint
+    , brick >=1.0
     , cmdargs >=0.8
     , containers >=0.5.9
     , data-default
@@ -77,8 +78,8 @@
     , extra >=1.6.3
     , filepath
     , fsnotify >=0.2.1.2 && <0.4
-    , hledger >=1.26.1 && <1.27
-    , hledger-lib >=1.26.1 && <1.27
+    , hledger ==1.27.*
+    , hledger-lib ==1.27.*
     , megaparsec >=7.0.0 && <9.3
     , 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.26.1.
+tool.  This manual is for hledger-ui 1.27.
 
    'hledger-ui [OPTIONS] [QUERYARGS]'
 'hledger ui -- [OPTIONS] [QUERYARGS]'
@@ -589,13 +589,14 @@
    For this to take effect you might need to 'killall Dock', or reboot.
 
    On Windows computers, the default value is probably
-'C:\Users\MyUserName\.hledger.journal'.  You can change this by running
-a command like this in a powershell window:
+'C:\Users\YOURNAME\.hledger.journal'.  You can change this by running a
+command like this in a powershell window (let us know if you need to be
+an Administrator, and if this persists across a reboot):
 
 > setx LEDGER_FILE "C:\Users\MyUserName\finance\2021.journal"
 
-   (Let us know if you need to be an Administrator, and if this persists
-across a reboot.)
+   Or, change it in settings: see
+https://www.java.com/en/download/help/path.html.
 
 
 File: hledger-ui.info,  Node: FILES,  Next: BUGS,  Prev: ENVIRONMENT,  Up: Top
@@ -639,34 +640,34 @@
 
 Tag Table:
 Node: Top221
-Node: OPTIONS1656
-Ref: #options1754
-Node: MOUSE6636
-Ref: #mouse6731
-Node: KEYS7013
-Ref: #keys7106
-Node: SCREENS11192
-Ref: #screens11290
-Node: Accounts screen11380
-Ref: #accounts-screen11508
-Node: Register screen13847
-Ref: #register-screen14002
-Node: Transaction screen15986
-Ref: #transaction-screen16144
-Node: Error screen17014
-Ref: #error-screen17136
-Node: TIPS17380
-Ref: #tips17479
-Node: Watch mode17531
-Ref: #watch-mode17648
-Node: Watch mode limitations18398
-Ref: #watch-mode-limitations18539
-Node: ENVIRONMENT19675
-Ref: #environment19786
-Node: FILES21094
-Ref: #files21193
-Node: BUGS21406
-Ref: #bugs21483
+Node: OPTIONS1654
+Ref: #options1752
+Node: MOUSE6634
+Ref: #mouse6729
+Node: KEYS7011
+Ref: #keys7104
+Node: SCREENS11190
+Ref: #screens11288
+Node: Accounts screen11378
+Ref: #accounts-screen11506
+Node: Register screen13845
+Ref: #register-screen14000
+Node: Transaction screen15984
+Ref: #transaction-screen16142
+Node: Error screen17012
+Ref: #error-screen17134
+Node: TIPS17378
+Ref: #tips17477
+Node: Watch mode17529
+Ref: #watch-mode17646
+Node: Watch mode limitations18396
+Ref: #watch-mode-limitations18537
+Node: ENVIRONMENT19673
+Ref: #environment19784
+Node: FILES21169
+Ref: #files21268
+Node: BUGS21481
+Ref: #bugs21558
 
 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.26.1.
+       tool.  This manual is for hledger-ui 1.27.
 
 SYNOPSIS
        hledger-ui [OPTIONS] [QUERYARGS]
@@ -489,23 +489,24 @@
 
        For this to take effect you might need to killall Dock, or reboot.
 
-       On Windows computers, the default value  is  probably  C:\Users\MyUser-
-       Name\.hledger.journal.   You  can change this by running a command like
-       this in a powershell window:
+       On Windows computers, the  default  value  is  probably  C:\Users\YOUR-
+       NAME\.hledger.journal.   You  can change this by running a command like
+       this in a powershell window (let us know if you need to be an  Adminis-
+       trator, and if this persists across a reboot):
 
               > setx LEDGER_FILE "C:\Users\MyUserName\finance\2021.journal"
 
-       (Let us know if you need to be an Administrator, and if  this  persists
-       across a reboot.)
+       Or,   change   it   in   settings:   see  https://www.java.com/en/down-
+       load/help/path.html.
 
 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).
@@ -513,13 +514,13 @@
        -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.
@@ -530,7 +531,7 @@
 
 
 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)
 
 
@@ -548,4 +549,4 @@
 
 
 
-hledger-ui-1.26.1                  July 2022                     HLEDGER-UI(1)
+hledger-ui-1.27                 September 2022                   HLEDGER-UI(1)
