diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -9,6 +9,60 @@
 User-visible changes in hledger-ui.
 See also the hledger changelog.
 
+# 1.28 2022-11-30
+
+Features
+
+- New "Balance sheet accounts" and "Income statement accounts" screens have been added,
+  along with a new top-level "Menu" screen for navigating between these and the
+  "All accounts" screen.
+
+- hledger-ui now starts in the "Balance sheet accounts" screen by default
+  (unless no asset/liability/equity accounts can be detected,
+  or command line account query arguments are provided).
+  This provides a more useful default view than the giant "All accounts" list.
+  Or, you can force a particular starting screen with the new --menu/--all/--bs/--is flags
+  (eg, `hledger-ui --all` to replicate the old behaviour).
+
+Improvements
+
+- The ENTER key is equivalent to RIGHT for navigation.
+
+- hledger-ui debug output is now always logged to ./hledger-ui.log rather than the console,
+  --debug with no argument is equivalent to --debug=1,
+  and debug output is much more informative.
+
+- Support GHC 9.4.
+
+- Support megaparsec 9.3 (Felix Yan)
+
+- Support (and require) brick 1.5, fsnotify 0.4.x.
+
+Fixes
+
+- Mouse-clicking in empty space below the last list item no longer navigates
+  back. It was too obtrusive, eg when you just want to focus the window. You can still navigate back with the mouse by clicking the left edge of the window.
+
+- A possible bug with detecting change of date while in --watch mode has been fixed.
+
+API
+
+- hledger-ui's internal types have been changed to allow fewer invalid states and make it easier  to develop and debug.
+  (#1889, #1919).
+
+- Debug logging helpers have been added and cleaned up in Hledger.Ui.UIUtils:
+  dbgui
+  dbguiIO
+  dbguiEv
+  dbguiScreensEv
+  mapScreens
+  screenId
+  screenRegisterDescriptions
+
+# 1.27.1 2022-09-18
+
+- Uses hledger-1.27.1
+
 # 1.27 2022-09-01
 
 Improvements
diff --git a/Hledger/UI/AccountsScreen.hs b/Hledger/UI/AccountsScreen.hs
--- a/Hledger/UI/AccountsScreen.hs
+++ b/Hledger/UI/AccountsScreen.hs
@@ -1,12 +1,19 @@
 -- The accounts screen, showing accounts and balances like the CLI balance command.
 
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE TupleSections #-}
 
 module Hledger.UI.AccountsScreen
- (accountsScreen
- ,asInit
+ (asNew
+ ,asUpdate
+ ,asDraw
+ ,asDrawHelper
+ ,asHandle
+ ,handleHelpMode
+ ,handleMinibufferMode
+ ,asHandleNormalMode
+ ,enterRegisterScreen
  ,asSetSelectedAccount
  )
 where
@@ -21,189 +28,134 @@
 import qualified Data.Text as T
 import Data.Time.Calendar (Day)
 import qualified Data.Vector as V
+import Data.Vector ((!?))
 import Graphics.Vty (Event(..),Key(..),Modifier(..), Button (BLeft, BScrollDown, BScrollUp))
 import Lens.Micro.Platform
-import Safe
 import System.Console.ANSI
 import System.FilePath (takeFileName)
 import Text.DocLayout (realLength)
 
 import Hledger
-import Hledger.Cli hiding (mode, progname, prognameandversion)
+import Hledger.Cli hiding (Mode, mode, progname, prognameandversion)
 import Hledger.UI.UIOptions
 import Hledger.UI.UITypes
 import Hledger.UI.UIState
 import Hledger.UI.UIUtils
+import Hledger.UI.UIScreens
 import Hledger.UI.Editor
-import Hledger.UI.RegisterScreen
-import Hledger.UI.ErrorScreen
-import Data.Vector ((!?))
-
-
-accountsScreen :: Screen
-accountsScreen = AccountsScreen{
-   sInit   = asInit
-  ,sDraw   = asDraw
-  ,sHandle = asHandle
-  ,_asList            = list AccountsList V.empty 1
-  ,_asSelectedAccount = ""
-  }
-
-asInit :: Day -> Bool -> UIState -> UIState
-asInit d reset ui@UIState{
-  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
-
-    -- decide which account is selected:
-    -- if reset is true, the first account;
-    -- otherwise, the previously selected account if possible;
-    -- otherwise, the first account with the same prefix (eg first leaf account when entering flat mode);
-    -- otherwise, the alphabetically preceding account.
-    newitems' = listMoveTo selidx newitems
-      where
-        selidx = case (reset, listSelectedElement $ _asList s) of
-                   (True, _)               -> 0
-                   (_, Nothing)            -> 0
-                   (_, Just (_,AccountsScreenItem{asItemAccountName=a})) ->
-                     headDef 0 $ catMaybes [
-                       elemIndex a as
-                      ,findIndex (a `isAccountNamePrefixOf`) as
-                      ,Just $ max 0 (length (filter (< a) as) - 1)
-                      ]
-                      where
-                        as = map asItemAccountName displayitems
-
-    rspec' =
-      -- Further restrict the query based on the current period and future/forecast mode.
-      (reportSpecSetFutureAndForecast d (forecast_ $ inputopts_ copts) rspec)
-      -- always show declared accounts even if unused
-        {_rsReportOpts=ropts{declared_=True}}
-
-    -- run the report
-    (items,_total) = balanceReport rspec' j
-
-    -- pre-render the list items
-    displayitem (fullacct, shortacct, indent, bal) =
-      AccountsScreenItem{asItemIndentLevel        = indent
-                        ,asItemAccountName        = fullacct
-                        ,asItemDisplayAccountName = replaceHiddenAccountsNameWith "All" $ if tree_ ropts then shortacct else fullacct
-                        ,asItemMixedAmount        = Just bal
-                        }
-    displayitems = map displayitem items
-    -- blanks added for scrolling control, cf RegisterScreen.
-    -- XXX Ugly. Changing to 0 helps when debugging.
-    blankitems = replicate uiNumBlankItems
-      AccountsScreenItem{asItemIndentLevel        = 0
-                        ,asItemAccountName        = ""
-                        ,asItemDisplayAccountName = ""
-                        ,asItemMixedAmount        = Nothing
-                        }
+import Hledger.UI.ErrorScreen (uiReloadJournal, uiCheckBalanceAssertions, uiReloadJournalIfChanged)
+import Hledger.UI.RegisterScreen (rsCenterSelection)
+import Data.Either (fromRight)
+import Control.Arrow ((>>>))
+import Safe (headDef)
 
 
-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]
-      _          -> [maincontent]
+asDraw ui = dbgui "asDraw" $ asDrawHelper ui ropts' scrname
   where
-    maincontent = Widget Greedy Greedy $ do
-      c <- getContext
-      let
-        availwidth =
-          -- ltrace "availwidth" $
-          c^.availWidthL
-          - 2 -- XXX due to margin ? shouldn't be necessary (cf UIUtils)
-        displayitems = s ^. asList . listElementsL
+    ropts' = _rsReportOpts $ reportspec_ $ uoCliOpts $ aopts ui
+    scrname = "account " ++ if ishistorical then "balances" else "changes"
+      where ishistorical = balanceaccum_ ropts' == Historical
 
-        acctwidths = V.map (\AccountsScreenItem{..} -> asItemIndentLevel + realLength asItemDisplayAccountName) displayitems
-        balwidths  = V.map (maybe 0 (wbWidth . showMixedAmountB oneLine) . asItemMixedAmount) displayitems
-        preferredacctwidth = V.maximum acctwidths
-        totalacctwidthseen = V.sum acctwidths
-        preferredbalwidth  = V.maximum balwidths
-        totalbalwidthseen  = V.sum balwidths
+-- | Help draw any accounts-like screen (all accounts, balance sheet, income statement..).
+-- The provided ReportOpts are used instead of the ones in the UIState.
+-- The other argument is the screen display name.
+asDrawHelper :: UIState -> ReportOpts -> String -> [Widget Name]
+asDrawHelper UIState{aScreen=scr, aopts=uopts, ajournal=j, aMode=mode} ropts scrname =
+  dbgui "asDrawHelper" $
+  case toAccountsLikeScreen scr of
+    Nothing          -> dbgui "asDrawHelper" $ errorWrongScreenType "draw helper"  -- PARTIAL:
+    Just (ALS _ ass) -> case mode of
+      Help -> [helpDialog, maincontent]
+      _    -> [maincontent]
+      where
+        UIOpts{uoCliOpts=copts} = uopts
+        maincontent = Widget Greedy Greedy $ do
+          c <- getContext
+          let
+            availwidth =
+              -- ltrace "availwidth" $
+              c^.availWidthL
+              - 2 -- XXX due to margin ? shouldn't be necessary (cf UIUtils)
+            displayitems = ass ^. assList . listElementsL
 
-        totalwidthseen = totalacctwidthseen + totalbalwidthseen
-        shortfall = preferredacctwidth + preferredbalwidth + 2 - availwidth
-        acctwidthproportion = fromIntegral totalacctwidthseen / fromIntegral totalwidthseen
-        adjustedacctwidth = min preferredacctwidth . max 15 . round $ acctwidthproportion * fromIntegral (availwidth - 2)  -- leave 2 whitespace for padding
-        adjustedbalwidth  = availwidth - 2 - adjustedacctwidth
+            acctwidths = V.map (\AccountsScreenItem{..} -> asItemIndentLevel + realLength asItemDisplayAccountName) displayitems
+            balwidths  = V.map (maybe 0 (wbWidth . showMixedAmountB oneLine) . asItemMixedAmount) displayitems
+            preferredacctwidth = V.maximum acctwidths
+            totalacctwidthseen = V.sum acctwidths
+            preferredbalwidth  = V.maximum balwidths
+            totalbalwidthseen  = V.sum balwidths
 
-        -- XXX how to minimise the balance column's jumping around as you change the depth limit ?
+            totalwidthseen = totalacctwidthseen + totalbalwidthseen
+            shortfall = preferredacctwidth + preferredbalwidth + 2 - availwidth
+            acctwidthproportion = fromIntegral totalacctwidthseen / fromIntegral totalwidthseen
+            adjustedacctwidth = min preferredacctwidth . max 15 . round $ acctwidthproportion * fromIntegral (availwidth - 2)  -- leave 2 whitespace for padding
+            adjustedbalwidth  = availwidth - 2 - adjustedacctwidth
 
-        colwidths | shortfall <= 0 = (preferredacctwidth, preferredbalwidth)
-                  | otherwise      = (adjustedacctwidth, adjustedbalwidth)
+            -- XXX how to minimise the balance column's jumping around as you change the depth limit ?
 
-      render $ defaultLayout toplabel bottomlabel $ renderList (asDrawItem colwidths) True (_asList s)
+            colwidths | shortfall <= 0 = (preferredacctwidth, preferredbalwidth)
+                      | otherwise      = (adjustedacctwidth, adjustedbalwidth)
 
-      where
-        ropts = _rsReportOpts rspec
-        ishistorical = balanceaccum_ ropts == Historical
+          render $ defaultLayout toplabel bottomlabel $ renderList (asDrawItem colwidths) True (ass ^. assList)
 
-        toplabel =
-              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)
-          <+> borderQueryStr (T.unpack . T.unwords . map textQuoteIfNeeded $ querystring_ ropts)
-          <+> borderDepthStr mdepth
-          <+> str (" ("++curidx++"/"++totidx++")")
-          <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts
-               then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions")
-               else str "")
           where
-            files = case journalFilePaths j of
-                           [] -> str ""
-                           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 (attrName "border" <> attrName "query") $ str $ unwords $ concat [
-               [""]
-              ,if empty_ ropts then [] else ["nonzero"]
-              ,uiShowStatus copts $ statuses_ ropts
-              ,if real_ ropts then ["real"] else []
-              ]
-            mdepth = depth_ ropts
-            curidx = case _asList s ^. listSelectedL of
-                       Nothing -> "-"
-                       Just i -> show (i + 1)
-            totidx = show $ V.length nonblanks
-              where
-                nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ s ^. asList . listElementsL
+            ishistorical = balanceaccum_ ropts == Historical
 
-        bottomlabel = case mode of
-                        Minibuffer label ed -> minibuffer label ed
-                        _                   -> quickhelp
-          where
-            quickhelp = borderKeysStr' [
-               ("?", str "help")
---              ,("RIGHT", str "register")
-              ,("t", renderToggle (tree_ ropts) "list" "tree")
-              -- ,("t", str "tree")
-              -- ,("l", str "list")
-              ,("-+", str "depth")
-              ,("H", renderToggle (not ishistorical) "end-bals" "changes")
-              ,("F", renderToggle1 (isJust . forecast_ $ inputopts_ copts) "forecast")
-              --,("/", "filter")
-              --,("DEL", "unfilter")
-              --,("ESC", "cancel/top")
-              ,("a", str "add")
---               ,("g", "reload")
-              ,("q", str "quit")
-              ]
+            toplabel =
+                  withAttr (attrName "border" <> attrName "filename") files
+              <+> toggles
+              <+> str (" " ++ scrname)
+              <+> borderPeriodStr (if ishistorical then "at end of" else "in") (period_ ropts)
+              <+> borderQueryStr (T.unpack . T.unwords . map textQuoteIfNeeded $ querystring_ ropts)
+              <+> borderDepthStr mdepth
+              <+> str (" ("++curidx++"/"++totidx++")")
+              <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts
+                  then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions")
+                  else str "")
+              where
+                files = case journalFilePaths j of
+                              [] -> str ""
+                              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 (attrName "border" <> attrName "query") $ str $ unwords $ concat [
+                  [""]
+                  ,if empty_ ropts then [] else ["nonzero"]
+                  ,uiShowStatus copts $ statuses_ ropts
+                  ,if real_ ropts then ["real"] else []
+                  ]
+                mdepth = depth_ ropts
+                curidx = case ass ^. assList . listSelectedL of
+                          Nothing -> "-"
+                          Just i -> show (i + 1)
+                totidx = show $ V.length nonblanks
+                  where
+                    nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ ass ^. assList . listElementsL
 
-asDraw _ =  dlogUiTrace "asDraw 2" $ errorWrongScreenType "draw function"  -- PARTIAL:
+            bottomlabel = case mode of
+                            Minibuffer label ed -> minibuffer label ed
+                            _                   -> quickhelp
+              where
+                quickhelp = borderKeysStr' [
+                  ("?", str "help")
+    --              ,("RIGHT", str "register")
+                  ,("t", renderToggle (tree_ ropts) "list" "tree")
+                  -- ,("t", str "tree")
+                  -- ,("l", str "list")
+                  ,("-+", str "depth")
+                  ,case scr of
+                    BS _ -> ("", str "")
+                    IS _ -> ("", str "")
+                    _    -> ("H", renderToggle (not ishistorical) "end-bals" "changes")
+                  ,("F", renderToggle1 (isJust . forecast_ $ inputopts_ copts) "forecast")
+                  --,("/", "filter")
+                  --,("DEL", "unfilter")
+                  --,("ESC", "cancel/top")
+                  ,("a", str "add")
+    --               ,("g", "reload")
+                  ,("q", str "quit")
+                  ]
 
 asDrawItem :: (Int,Int) -> Bool -> AccountsScreenItem -> Widget Name
 asDrawItem (acctwidth, balwidth) selected AccountsScreenItem{..} =
@@ -225,178 +177,228 @@
         sel | selected  = (<> attrName "selected")
             | otherwise = id
 
+-- | Handle events on any accounts-like screen (all accounts, balance sheet, income statement..).
 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
+  dbguiEv "asHandle"
+  ui0@UIState{aScreen=scr, aMode=mode} <- get'
+  case toAccountsLikeScreen scr of
+    Nothing -> dbgui "asHandle" $ errorWrongScreenType "event handler"  -- PARTIAL:
+    Just als@(ALS scons ass) -> do
+      -- save the currently selected account, in case we leave this screen and lose the selection
+      put' ui0{aScreen=scons ass{_assSelectedAccount=asSelectedAccount ass}}
+      case mode of
+        Normal          -> asHandleNormalMode als ev
+        Minibuffer _ ed -> handleMinibufferMode ed ev
+        Help            -> handleHelpMode ev
 
-      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
+-- | Handle events when in normal mode on any accounts-like screen.
+-- The provided AccountsLikeScreen should correspond to the ui state's current screen.
+asHandleNormalMode :: AccountsLikeScreen -> BrickEvent Name AppEvent -> EventM Name UIState ()
+asHandleNormalMode (ALS scons ass) ev = do
+  dbguiEv "asHandleNormalMode"
 
-      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 ()
+  ui@UIState{aopts=UIOpts{uoCliOpts=copts}, ajournal=j} <- get'
+  d <- liftIO getCurrentDay
+  let
+    l = _assList ass
+    selacct = asSelectedAccount ass
+    centerSelection = scrollSelectionToMiddle l
+    clickedAcctAt y =
+      case asItemAccountName <$> listElements l !? y of
+        Just t | not $ T.null t -> Just t
+        _ -> Nothing
+    nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ listElements l
+    lastnonblankidx = max 0 (length nonblanks - 1)
+    journalspan = journalDateSpan False j
 
-        Help ->
-          case ev of
-            -- VtyEvent (EvKey (KChar 'q') []) -> halt
-            VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
-            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
-            _ -> helpHandle ev
+  case ev of
 
-        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 (KChar 'q') []) -> halt                               -- q: quit
+    VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui                    -- C-z: suspend
+    VtyEvent (EvKey (KChar 'l') [MCtrl]) -> centerSelection >> redraw     -- C-l: redraw
+    VtyEvent (EvKey KEsc        []) -> modify' (resetScreens d)           -- ESC: reset
+    VtyEvent (EvKey (KChar c)   []) | c == '?' -> modify' (setMode Help)  -- ?: enter help mode
 
-            -- 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)
+    -- AppEvents come from the system, in --watch mode.
+    -- XXX currently they are handled only in Normal mode
+    -- XXX be sure we don't leave unconsumed app events piling up
+    -- A data file has changed (or the user has pressed g): reload.
+    e | e `elem` [AppEvent FileChange, VtyEvent (EvKey (KChar 'g') [])] ->
+      liftIO (uiReloadJournal copts d ui) >>= put'
 
-            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
+    -- The date has changed (and we are viewing a standard period which contained the old date):
+    -- adjust the viewed period and regenerate, just in case needed.
+    -- (Eg: when watching data for "today" and the time has just passed midnight.)
+    AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
+      modify' (setReportPeriod (DayPeriod d) >>> regenerateScreens j d)
+      where p = reportPeriod ui
 
-            -- 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
+    -- set or reset a filter:
+    VtyEvent (EvKey (KChar '/') []) -> modify' (showMinibuffer "filter" Nothing >>> regenerateScreens j d)
+    VtyEvent (EvKey k           []) | k `elem` [KBS, KDel] -> modify' (resetFilter >>> regenerateScreens j d)
 
-            -- 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
+    -- run external programs:
+    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
 
-            -- 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
+    -- adjust the period displayed:
+    VtyEvent (EvKey (KChar 'T') []) ->       modify' (setReportPeriod (DayPeriod d)    >>> regenerateScreens j d)
+    VtyEvent (EvKey (KDown)     [MShift]) -> modify' (shrinkReportPeriod d             >>> regenerateScreens j d)
+    VtyEvent (EvKey (KUp)       [MShift]) -> modify' (growReportPeriod d               >>> regenerateScreens j d)
+    VtyEvent (EvKey (KRight)    [MShift]) -> modify' (nextReportPeriod journalspan     >>> regenerateScreens j d)
+    VtyEvent (EvKey (KLeft)     [MShift]) -> modify' (previousReportPeriod journalspan >>> regenerateScreens j d)
 
-            -- 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'}}
+    -- various toggles and settings:
+    VtyEvent (EvKey (KChar 'I') []) -> modify' (toggleIgnoreBalanceAssertions >>> uiCheckBalanceAssertions d)
+    VtyEvent (EvKey (KChar 'F') []) -> modify' (toggleForecast d   >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar 'B') []) -> modify' (toggleConversionOp >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar 'V') []) -> modify' (toggleValue        >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '0') []) -> modify' (setDepth (Just 0)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '1') []) -> modify' (setDepth (Just 1)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '2') []) -> modify' (setDepth (Just 2)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '3') []) -> modify' (setDepth (Just 3)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '4') []) -> modify' (setDepth (Just 4)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '5') []) -> modify' (setDepth (Just 5)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '6') []) -> modify' (setDepth (Just 6)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '7') []) -> modify' (setDepth (Just 7)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '8') []) -> modify' (setDepth (Just 8)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar '9') []) -> modify' (setDepth (Just 9)  >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar c) []) | c `elem` ['-','_'] -> modify' (decDepth >>> regenerateScreens j d)
+    VtyEvent (EvKey (KChar c) []) | c `elem` ['+','='] -> modify' (incDepth >>> regenerateScreens j d)
+    -- toggles after which the selection should be recentered:
+    VtyEvent (EvKey (KChar 'H') []) -> modify' (toggleHistorical   >>> regenerateScreens j d) >> centerSelection  -- harmless on BS/IS screens
+    VtyEvent (EvKey (KChar 't') []) -> modify' (toggleTree         >>> regenerateScreens j d) >> centerSelection
+    VtyEvent (EvKey (KChar 'R') []) -> modify' (toggleReal         >>> regenerateScreens j d) >> centerSelection
+    VtyEvent (EvKey (KChar 'U') []) -> modify' (toggleUnmarked     >>> regenerateScreens j d) >> centerSelection
+    VtyEvent (EvKey (KChar 'P') []) -> modify' (togglePending      >>> regenerateScreens j d) >> centerSelection
+    VtyEvent (EvKey (KChar 'C') []) -> modify' (toggleCleared      >>> regenerateScreens j d) >> centerSelection
+    VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> modify' (toggleEmpty >>> regenerateScreens j d) >> centerSelection  -- back compat: accept Z as well as z
 
-            -- 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}}
+    -- LEFT key or a click in the app's left margin: exit to the parent screen.
+    VtyEvent e | e `elem` moveLeftEvents  -> modify' popScreen
+    VtyEvent (EvMouseUp 0 _ (Just BLeft)) -> modify' popScreen  -- this mouse click is a VtyEvent since not in a clickable widget
 
-            -- 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 }
+    -- RIGHT key or MouseUp on an account: enter the register screen for the selected account
+    VtyEvent e | e `elem` moveRightEvents, not $ isBlankItem $ listSelectedElement l -> enterRegisterScreen d selacct ui
+    MouseUp _n (Just BLeft) Location{loc=(_,y)} | Just clkacct <- clickedAcctAt y    -> enterRegisterScreen d clkacct ui
 
-            MouseDown{} -> return ()
-            MouseUp{}   -> return ()
-            AppEvent _  -> return ()
+    -- MouseDown: this is not debounced and can repeat (https://github.com/jtdaugherty/brick/issues/347)
+    -- so we only let it do something harmless: move the selection.
+    MouseDown _n BLeft _mods Location{loc=(_,y)} | not $ isBlankItem clickeditem ->
+      put' ui{aScreen=scons ass'}
+      where
+        clickeditem = (0,) <$> listElements l !? y
+        ass' = ass{_assList=listMoveTo y l}
 
-    _ -> dlogUiTraceM "asHandle 2" >> errorWrongScreenType "event handler"
+    -- Mouse scroll wheel: scroll up or down to the 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
+      l' <- nestEventM' l $ listScrollPushingSelection name (asListSize l) scrollamt
+      put' ui{aScreen=scons ass{_assList=l'}}
 
-asEnterRegister :: Day -> AccountName -> UIState -> EventM Name UIState ()
-asEnterRegister d selacct ui = do
-  dlogUiTraceM "asEnterRegister"
+    -- PGDOWN/END keys: handle with List's default handler, but restrict the selection to stop
+    -- (and center) at the last non-blank item.
+    VtyEvent e@(EvKey k []) | k `elem` [KPageDown, KEnd] -> do
+      l1 <- nestEventM' l $ handleListEvent e
+      if isBlankItem $ listSelectedElement l1
+      then do
+        let l2 = listMoveTo lastnonblankidx l1
+        scrollSelectionToMiddle l2
+        put' ui{aScreen=scons ass{_assList=l2}}
+      else
+        put' ui{aScreen=scons ass{_assList=l1}}
+
+    -- DOWN key when selection is at the last item: scroll instead of moving, until maximally scrolled
+    VtyEvent e | e `elem` moveDownEvents, isBlankItem mnextelement -> vScrollBy (viewportScroll $ l^.listNameL) 1
+      where mnextelement = listSelectedElement $ listMoveDown l
+
+    -- Any other vty event (UP, DOWN, PGUP etc): handle with List's default handler.
+    VtyEvent e -> do
+      l' <- nestEventM' l $ handleListEvent (normaliseMovementKeys e)
+      put' ui{aScreen=scons $ ass & assList .~ l' & assSelectedAccount .~ selacct}
+
+    -- Any other mouse/app event: ignore
+    MouseDown{} -> return ()
+    MouseUp{}   -> return ()
+    AppEvent _  -> return ()
+
+-- | Handle events when in minibuffer mode on any screen.
+handleMinibufferMode ed ev = do
+  ui@UIState{ajournal=j} <- get'
+  d <- liftIO getCurrentDay
+  case ev of
+    VtyEvent (EvKey KEsc   []) -> put' $ closeMinibuffer ui
+    VtyEvent (EvKey KEnter []) -> put' $ regenerateScreens j d ui'
+      where
+        ui' = setFilter s (closeMinibuffer ui)
+          & fromRight (showMinibuffer "Cannot compile regular expression" (Just s) 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 ()
+
+-- | Handle events when in help mode on any screen.
+handleHelpMode ev = do
+  ui <- get'
+  case ev of
+    -- VtyEvent (EvKey (KChar 'q') []) -> halt
+    VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
+    VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+    _ -> helpHandle ev
+
+enterRegisterScreen :: Day -> AccountName -> UIState -> EventM Name UIState ()
+enterRegisterScreen d acct ui@UIState{ajournal=j, aopts=uopts} = do
+  dbguiEv "enterRegisterScreen"
   let
-    regscr = rsSetAccount selacct isdepthclipped registerScreen
+    regscr = rsNew uopts d j acct isdepthclipped
       where
         isdepthclipped = case getDepth ui of
-                          Just de -> accountNameLevel selacct >= de
+                          Just de -> accountNameLevel acct >= de
                           Nothing -> False
-  rsCenterSelection (screenEnter d regscr ui) >>= put'
+    ui1 = pushScreen regscr ui
+  rsCenterSelection ui1 >>= put'
 
-asSetSelectedAccount a s@AccountsScreen{} = s & asSelectedAccount .~ a
-asSetSelectedAccount _ s = s
+-- | From any accounts screen's state, get the account name from the 
+-- currently selected list item, or otherwise the last known selected account name.
+asSelectedAccount :: AccountsScreenState -> AccountName
+asSelectedAccount ass =
+  case listSelectedElement $ _assList ass of
+    Just (_, AccountsScreenItem{..}) -> asItemAccountName
+    Nothing -> ass ^. assSelectedAccount
 
-isBlankElement mel = ((asItemAccountName . snd) <$> mel) == Just ""
+-- | Set the selected account on any of the accounts screens. Has no effect on other screens.
+-- Sets the high-level property _assSelectedAccount and also selects the corresponding or
+-- best alternative item in the list widget (_assList).
+asSetSelectedAccount :: AccountName -> Screen -> Screen
+asSetSelectedAccount acct scr =
+  case scr of
+    (AS ass) -> AS $ assSetSelectedAccount acct ass
+    (BS ass) -> BS $ assSetSelectedAccount acct ass
+    (IS ass) -> IS $ assSetSelectedAccount acct ass
+    _        -> scr
+    where
+      assSetSelectedAccount a ass@ASS{_assList=l} =
+        ass{_assSelectedAccount=a, _assList=listMoveTo selidx l}
+        where
+          -- which list item should be selected ?
+          selidx = headDef 0 $ catMaybes [
+            elemIndex a as                                -- the specified account, if it can be found
+            ,findIndex (a `isAccountNamePrefixOf`) as     -- or the first account found with the same prefix
+            ,Just $ max 0 (length (filter (< a) as) - 1)  -- otherwise, the alphabetically preceding account.
+            ]
+            where
+              as = map asItemAccountName $ V.toList $ listElements l
 
-asCenterAndContinue :: EventM Name UIState ()
-asCenterAndContinue = do
-  ui <- get'
-  scrollSelectionToMiddle (_asList $ aScreen ui)
+isBlankItem mitem = ((asItemAccountName . snd) <$> mitem) == Just ""
 
 asListSize = V.length . V.takeWhile ((/="").asItemAccountName) . listElements
+
+
 
diff --git a/Hledger/UI/BalancesheetScreen.hs b/Hledger/UI/BalancesheetScreen.hs
new file mode 100644
--- /dev/null
+++ b/Hledger/UI/BalancesheetScreen.hs
@@ -0,0 +1,29 @@
+-- The balance sheet screen, like the accounts screen but restricted to balance sheet accounts.
+
+module Hledger.UI.BalancesheetScreen
+ (bsNew
+ ,bsUpdate
+ ,bsDraw
+ ,bsHandle
+ )
+where
+
+import Brick hiding (bsDraw)
+
+import Hledger
+import Hledger.Cli hiding (mode, progname, prognameandversion)
+import Hledger.UI.UIOptions
+import Hledger.UI.UITypes
+import Hledger.UI.UIUtils
+import Hledger.UI.UIScreens
+import Hledger.UI.AccountsScreen (asHandle, asDrawHelper)
+
+
+bsDraw :: UIState -> [Widget Name]
+bsDraw ui = dbgui "bsDraw" $ asDrawHelper ui ropts' scrname
+  where
+    scrname = "balance sheet balances"
+    ropts' = (_rsReportOpts $ reportspec_ $ uoCliOpts $ aopts ui){balanceaccum_=Historical}
+
+bsHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+bsHandle = asHandle . dbgui "bsHandle"
diff --git a/Hledger/UI/ErrorScreen.hs b/Hledger/UI/ErrorScreen.hs
--- a/Hledger/UI/ErrorScreen.hs
+++ b/Hledger/UI/ErrorScreen.hs
@@ -6,7 +6,10 @@
 {-# OPTIONS_GHC -Wno-incomplete-record-updates #-}
 
 module Hledger.UI.ErrorScreen
- (errorScreen
+ (esNew
+ ,esUpdate
+ ,esDraw
+ ,esHandle
  ,uiCheckBalanceAssertions
  ,uiReloadJournal
  ,uiReloadJournalIfChanged
@@ -29,32 +32,19 @@
 import Hledger.UI.UITypes
 import Hledger.UI.UIState
 import Hledger.UI.UIUtils
+import Hledger.UI.UIScreens
 import Hledger.UI.Editor
 
-errorScreen :: Screen
-errorScreen = ErrorScreen{
-   sInit    = esInit
-  ,sDraw    = esDraw
-  ,sHandle  = esHandle
-  ,esError  = ""
-  }
-
-esInit :: Day -> Bool -> UIState -> UIState
-esInit _ _ ui@UIState{aScreen=ErrorScreen{}} = ui
-esInit _ _ _ = error "init function called with wrong screen type, should not happen"  -- PARTIAL:
-
 esDraw :: UIState -> [Widget Name]
-esDraw UIState{aopts=UIOpts{uoCliOpts=copts}
-              ,aScreen=ErrorScreen{..}
+esDraw UIState{aScreen=ES ESS{..}
               ,aMode=mode
               } =
   case mode of
-    Help       -> [helpDialog copts, maincontent]
-    -- Minibuffer e -> [minibuffer e, maincontent]
+    Help       -> [helpDialog, maincontent]
     _          -> [maincontent]
   where
     maincontent = Widget Greedy Greedy $ do
-      render $ defaultLayout toplabel bottomlabel $ withAttr (attrName "error") $ str $ esError
+      render $ defaultLayout toplabel bottomlabel $ withAttr (attrName "error") $ str $ _essError
       where
         toplabel =
               withAttr (attrName "border" <> attrName "bold") (str "Oops. Please fix this problem then press g to reload")
@@ -79,7 +69,7 @@
 esHandle ev = do
   ui0 <- get'
   case ui0 of
-    ui@UIState{aScreen=ErrorScreen{..}
+    ui@UIState{aScreen=ES ESS{..}
               ,aopts=UIOpts{uoCliOpts=copts}
               ,ajournal=j
               ,aMode=mode
@@ -93,14 +83,14 @@
             _                    -> helpHandle ev
 
         _ -> do
-          let d = copts^.rsDay
+          d <- liftIO getCurrentDay
           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
+                (pos,f) = case parsewithString hledgerparseerrorpositionp _essError of
                             Right (f',l,c) -> (Just (l, Just c),f')
                             Left  _       -> (endPosition, journalFilePath j)
             e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
@@ -159,12 +149,15 @@
   ej <-
     let copts' = enableForecastPreservingPeriod ui copts
     in runExceptT $ journalReload copts'
+  -- dbg1IO "uiReloadJournal before reload" (map tdescription $ jtxns $ ajournal ui)
   return $ case ej of
-    Right j  -> regenerateScreens j d ui
+    Right j  ->
+      -- dbg1 "uiReloadJournal after reload" (map tdescription $ jtxns j) $
+      regenerateScreens j d ui
     Left err ->
       case ui of
-        UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}
-        _                                -> screenEnter d errorScreen{esError=err} ui
+        UIState{aScreen=ES _} -> ui{aScreen=esNew err}
+        _                      -> pushScreen (esNew err) ui
       -- XXX GHC 9.2 warning:
       -- hledger-ui/Hledger/UI/ErrorScreen.hs:164:59: warning: [-Wincomplete-record-updates]
       --     Pattern match(es) are non-exhaustive
@@ -183,20 +176,20 @@
   ej <- runExceptT $ journalReloadIfChanged copts' d j
   return $ case ej of
     Right (j', _) -> regenerateScreens j' d ui
-    Left err -> case ui of
-        UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}
-        _                                -> screenEnter d errorScreen{esError=err} ui
+    Left err -> case aScreen ui of
+        ES _ -> ui{aScreen=esNew err}
+        _    -> pushScreen (esNew err) ui
 
 -- Re-check any balance assertions in the current journal, and if any
 -- fail, enter (or update) the error screen. Or if balance assertions
 -- are disabled, do nothing.
 uiCheckBalanceAssertions :: Day -> UIState -> UIState
-uiCheckBalanceAssertions d ui@UIState{ajournal=j}
+uiCheckBalanceAssertions _d ui@UIState{ajournal=j}
   | ui^.ignore_assertions = ui
   | otherwise =
     case journalCheckBalanceAssertions j of
       Nothing  -> ui
       Just err ->
         case ui of
-          UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}
-          _                                -> screenEnter d errorScreen{esError=err} ui
+          UIState{aScreen=ES sst} -> ui{aScreen=ES sst{_essError=err}}
+          _                        -> pushScreen (esNew err) ui
diff --git a/Hledger/UI/IncomestatementScreen.hs b/Hledger/UI/IncomestatementScreen.hs
new file mode 100644
--- /dev/null
+++ b/Hledger/UI/IncomestatementScreen.hs
@@ -0,0 +1,29 @@
+-- The income statement accounts screen, like the accounts screen but restricted to income statement accounts.
+
+module Hledger.UI.IncomestatementScreen
+ (isNew
+ ,isUpdate
+ ,isDraw
+ ,isHandle
+ )
+where
+
+import Brick
+
+import Hledger
+import Hledger.Cli hiding (mode, progname, prognameandversion)
+import Hledger.UI.UIOptions
+import Hledger.UI.UITypes
+import Hledger.UI.UIUtils
+import Hledger.UI.UIScreens
+import Hledger.UI.AccountsScreen (asHandle, asDrawHelper)
+
+
+isDraw :: UIState -> [Widget Name]
+isDraw ui = dbgui "isDraw" $ asDrawHelper ui ropts' scrname
+  where
+    scrname = "income statement changes"
+    ropts' = (_rsReportOpts $ reportspec_ $ uoCliOpts $ aopts ui){balanceaccum_=PerPeriod}
+
+isHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+isHandle = asHandle . dbgui "isHandle"
diff --git a/Hledger/UI/Main.hs b/Hledger/UI/Main.hs
--- a/Hledger/UI/Main.hs
+++ b/Hledger/UI/Main.hs
@@ -1,13 +1,12 @@
--- TODO: brick 1 support
--- https://hackage.haskell.org/package/brick-1.0/changelog
 {-|
-hledger-ui - a hledger add-on providing a curses-style interface.
+hledger-ui - a hledger add-on providing an efficient TUI.
 Copyright (c) 2007-2015 Simon Michael <simon@joyful.com>
 Released under GPL version 3 or later.
 -}
 {-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE MultiWayIf #-}
 
 module Hledger.UI.Main where
 
@@ -15,28 +14,38 @@
 import Control.Concurrent (threadDelay)
 import Control.Concurrent.Async (withAsync)
 import Control.Monad (forM_, void, when)
+import Data.Bifunctor (first)
+import Data.Function ((&))
 import Data.List (find)
 import Data.List.Extra (nubSort)
+import qualified Data.Map as M (elems)
 import Data.Maybe (fromMaybe)
 import qualified Data.Text as T
 import Graphics.Vty (mkVty, Mode (Mouse), Vty (outputIface), Output (setMode))
 import Lens.Micro ((^.))
 import System.Directory (canonicalizePath)
+import System.Environment (withProgName)
 import System.FilePath (takeDirectory)
-import System.FSNotify (Event(Modified), isPollingManager, watchDir, withManager)
-import Brick
-
+import System.FSNotify (Event(Modified), watchDir, withManager, EventIsDirectory (IsFile))
+import Brick hiding (bsDraw)
 import qualified Brick.BChan as BC
 
 import Hledger
 import Hledger.Cli hiding (progname,prognameandversion)
+import Hledger.UI.Theme
 import Hledger.UI.UIOptions
 import Hledger.UI.UITypes
-import Hledger.UI.Theme
+import Hledger.UI.UIState (uiState, getDepth)
+import Hledger.UI.UIUtils (dbguiEv, showScreenStack, showScreenSelection)
+import Hledger.UI.MenuScreen
 import Hledger.UI.AccountsScreen
+import Hledger.UI.BalancesheetScreen
+import Hledger.UI.IncomestatementScreen
 import Hledger.UI.RegisterScreen
-import Hledger.UI.UIUtils (dlogUiTrace)
+import Hledger.UI.TransactionScreen
+import Hledger.UI.ErrorScreen
 
+
 ----------------------------------------------------------------------
 
 newChan :: IO (BC.BChan a)
@@ -47,7 +56,11 @@
 
 
 main :: IO ()
-main = do
+main = withProgName "hledger-ui.log" $ do  -- force Hledger.Utils.Debug.* to log to hledger-ui.log
+  traceLogAtIO 1 "\n\n\n\n==== hledger-ui start"
+  dbg1IO "args" progArgs
+  dbg1IO "debugLevel" debugLevel
+
   opts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=iopts,rawopts_=rawopts}} <- getHledgerUIOpts
   -- when (debug_ $ cliopts_ opts) $ printf "%s\n" prognameandversion >> printf "opts: %s\n" (show opts)
 
@@ -63,8 +76,8 @@
     _                                         -> withJournalDo copts' (runBrickUi opts)
 
 runBrickUi :: UIOpts -> Journal -> IO ()
-runBrickUi uopts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} j =
-  dlogUiTrace "========= runBrickUi" $ do
+runBrickUi uopts0@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} j =
+  do
   let
     today = copts^.rsDay
 
@@ -105,15 +118,16 @@
     -- There is also a freeform text area for extra query terms (/ key).
     -- It's cleaner and less conflicting to keep the former out of the latter.
 
-    uopts' = uopts{
+    uopts = uopts0{
       uoCliOpts=copts{
          reportspec_=rspec{
             _rsQuery=filteredQuery $ _rsQuery rspec,  -- query with depth/date parts removed
             _rsReportOpts=ropts{
-               depth_ =queryDepth $ _rsQuery rspec,  -- query's depth part
-               period_=periodfromoptsandargs,       -- query's date part
-               no_elide_=True,  -- avoid squashing boring account names, for a more regular tree (unlike hledger)
-               empty_=not $ empty_ ropts  -- show zero items by default, hide them with -E (unlike hledger)
+               depth_    = queryDepth $ _rsQuery rspec,  -- query's depth part
+               period_   = periodfromoptsandargs,       -- query's date part
+               no_elide_ = True,  -- avoid squashing boring account names, for a more regular tree (unlike hledger)
+               empty_    = not $ empty_ ropts,  -- show zero items by default, hide them with -E (unlike hledger)
+               declared_ = True  -- always show declared accounts even if unused
                }
             }
          }
@@ -125,51 +139,76 @@
         filteredQuery q = simplifyQuery $ And [queryFromFlags ropts, filtered q]
           where filtered = filterQuery (\x -> not $ queryIsDepth x || queryIsDate x)
 
-    (scr, prevscrs) = case uoRegister uopts' of
-      Nothing   -> (accountsScreen, [])
-      -- with --register, start on the register screen, and also put
-      -- the accounts screen on the prev screens stack so you can exit
-      -- to that as usual.
-      Just apat -> (rsSetAccount acct False registerScreen, [ascr'])
-        where
-          acct = fromMaybe (error' $ "--register "++apat++" did not match any account")  -- PARTIAL:
-                 . firstMatch $ journalAccountNamesDeclaredOrImplied j
-          firstMatch = case toRegexCI $ T.pack apat of
-              Right re -> find (regexMatchText re)
-              Left  _  -> const Nothing
-          -- Initialising the accounts screen is awkward, requiring
-          -- another temporary UIState value..
-          ascr' = aScreen $
-                  asInit today True
-                    UIState{
-                     astartupopts=uopts'
-                    ,aopts=uopts'
-                    ,ajournal=j
-                    ,aScreen=asSetSelectedAccount acct accountsScreen
-                    ,aPrevScreens=[]
-                    ,aMode=Normal
-                    }
+    -- Choose the initial screen to display.
+    -- We like to show the balance sheet accounts screen by default,
+    -- but that can change eg if we can't detect any accounts for it,
+    -- or if an account query has been provided at startup,
+    -- or if a specific screen has been requested by command line flag.
+    -- Whichever is the initial screen, we also set up a stack of previous screens,
+    -- as if you had navigated down to it from the top.
+    -- (remember, the previous screens list is ordered nearest/lowest first)
+    rawopts = rawopts_ $ uoCliOpts $ uopts
+    (prevscrs, currscr) =
+      dbg1With (showScreenStack "initial" showScreenSelection . uncurry2 (uiState defuiopts nulljournal)) $
+      if
+        | boolopt "menu" rawopts -> ([], menuscr)
+        | boolopt "all"  rawopts -> ([msSetSelectedScreen 0 menuscr], allacctsscr)
+        | boolopt "bs"   rawopts -> ([menuscr], bsacctsscr)
+        | boolopt "is"   rawopts -> ([msSetSelectedScreen 2 menuscr], isacctsscr)
 
-    ui =
-      (sInit scr) today True $
-          UIState{
-           astartupopts=uopts'
-          ,aopts=uopts'
-          ,ajournal=j
-          ,aScreen=scr
-          ,aPrevScreens=prevscrs
-          ,aMode=Normal
-          }
+        -- With --register=ACCT, the initial screen stack is:
+        -- menu screen, with ACCTSSCR selected
+        --  ACCTSSCR (the accounts screen containing ACCT), with ACCT selected
+        --   register screen for ACCT
+        | Just apat <- uoRegister uopts ->
+          let
+            -- the account being requested
+            acct = fromMaybe (error' $ "--register "++apat++" did not match any account")  -- PARTIAL:
+              . firstMatch $ journalAccountNamesDeclaredOrImplied j
+              where
+                firstMatch = case toRegexCI $ T.pack apat of
+                    Right re -> find (regexMatchText re)
+                    Left  _  -> const Nothing
 
-    brickapp :: App UIState AppEvent Name
-    brickapp = App {
-        appStartEvent   = return ()
-      , appAttrMap      = const $ fromMaybe defaultTheme $ getTheme =<< uoTheme uopts'
-      , appChooseCursor = showFirstCursor
-      , appHandleEvent  = \ev -> do ui' <- get; sHandle (aScreen ui') ev
-      , appDraw         = \ui' -> sDraw (aScreen ui') ui'
-      }
+            -- the register screen for acct
+            regscr = 
+              rsSetAccount acct False $
+              rsNew uopts today j acct forceinclusive
+                where
+                  forceinclusive = case getDepth ui of
+                                    Just de -> accountNameLevel acct >= de
+                                    Nothing -> False
 
+            -- The accounts screen containing acct.
+            -- Keep these selidx values synced with the menu items in msNew.
+            (acctsscr, selidx) =
+              case journalAccountType j acct of
+                Just t | isBalanceSheetAccountType t    -> (bsacctsscr, 1)
+                Just t | isIncomeStatementAccountType t -> (isacctsscr, 2)
+                _                                       -> (allacctsscr,0)
+              & first (asSetSelectedAccount acct)
+
+            -- the menu screen
+            menuscr' = msSetSelectedScreen selidx menuscr
+          in ([acctsscr, menuscr'], regscr)
+
+        -- No balance sheet accounts detected, or an initial account query specified:
+        | not hasbsaccts || hasacctquery -> ([msSetSelectedScreen 0 menuscr], allacctsscr)
+
+        | otherwise -> ([menuscr], bsacctsscr)
+
+        where
+          hasbsaccts  = any (`elem` accttypes) [Asset, Liability, Equity]
+            where accttypes = M.elems $ jaccounttypes j
+          hasacctquery = matchesQuery queryIsAcct $ _rsQuery rspec
+          menuscr     = msNew
+          allacctsscr = asNew uopts today j Nothing
+          bsacctsscr  = bsNew uopts today j Nothing
+          isacctsscr  = isNew uopts today j Nothing
+
+    ui = uiState uopts j prevscrs currscr
+    app = brickApp (uoTheme uopts)
+
   -- print (length (show ui)) >> exitSuccess  -- show any debug output to this point & quit
 
   let 
@@ -179,10 +218,10 @@
       setMode (outputIface v) Mouse True
       return v
 
-  if not (uoWatch uopts')
+  if not (uoWatch uopts)
   then do
     vty <- makevty
-    void $ customMain vty makevty Nothing brickapp ui
+    void $ customMain vty makevty Nothing app ui
 
   else do
     -- a channel for sending misc. events to the app
@@ -214,7 +253,6 @@
       -- with Debounce at the default 1ms it clears transient errors itself
       -- but gets tied up for ages
       withManager $ \mgr -> do
-        dbg1IO "fsnotify using polling ?" $ isPollingManager mgr
         files <- mapM (canonicalizePath . fst) $ jfiles j
         let directories = nubSort $ map takeDirectory files
         dbg1IO "files" files
@@ -225,7 +263,7 @@
           d
           -- predicate: ignore changes not involving our files
           (\case
-            Modified f _ False -> f `elem` files
+            Modified f _ IsFile -> f `elem` files
             -- Added    f _ -> f `elem` files
             -- Removed  f _ -> f `elem` files
             -- we don't handle adding/removing journal files right now
@@ -242,4 +280,37 @@
 
         -- and start the app. Must be inside the withManager block. (XXX makevty too ?)
         vty <- makevty
-        void $ customMain vty makevty (Just eventChan) brickapp ui
+        void $ customMain vty makevty (Just eventChan) app ui
+
+brickApp :: Maybe String -> App UIState AppEvent Name
+brickApp mtheme = App {
+    appStartEvent   = return ()
+  , appAttrMap      = const $ fromMaybe defaultTheme $ getTheme =<< mtheme
+  , appChooseCursor = showFirstCursor
+  , appHandleEvent  = uiHandle
+  , appDraw         = uiDraw
+  }
+
+uiHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+uiHandle ev = do
+  dbguiEv $ "\n==== " ++ show ev
+  ui <- get
+  case aScreen ui of
+    MS _ -> msHandle ev
+    AS _ -> asHandle ev
+    BS _ -> bsHandle ev
+    IS _ -> isHandle ev
+    RS _ -> rsHandle ev
+    TS _ -> tsHandle ev
+    ES _ -> esHandle ev
+
+uiDraw :: UIState -> [Widget Name]
+uiDraw ui =
+  case aScreen ui of
+    MS _ -> msDraw ui
+    AS _ -> asDraw ui
+    BS _ -> bsDraw ui
+    IS _ -> isDraw ui
+    RS _ -> rsDraw ui
+    TS _ -> tsDraw ui
+    ES _ -> esDraw ui
diff --git a/Hledger/UI/MenuScreen.hs b/Hledger/UI/MenuScreen.hs
new file mode 100644
--- /dev/null
+++ b/Hledger/UI/MenuScreen.hs
@@ -0,0 +1,273 @@
+-- The menu screen, showing other screens available in hledger-ui.
+
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Hledger.UI.MenuScreen
+ (msNew
+ ,msUpdate
+ ,msDraw
+ ,msHandle
+ ,msSetSelectedScreen
+ )
+where
+
+import Brick
+import Brick.Widgets.List
+import Control.Monad
+import Control.Monad.IO.Class (liftIO)
+import Data.Maybe
+import qualified Data.Text as T
+import Data.Time.Calendar (Day)
+import qualified Data.Vector as V
+import Data.Vector ((!?))
+import Graphics.Vty (Event(..),Key(..),Modifier(..), Button (BLeft, BScrollDown, BScrollUp))
+import Lens.Micro.Platform
+import System.Console.ANSI
+import System.FilePath (takeFileName)
+
+import Hledger
+import Hledger.Cli hiding (mode, progname, prognameandversion)
+import Hledger.UI.UIOptions
+import Hledger.UI.UITypes
+import Hledger.UI.UIState
+import Hledger.UI.UIUtils
+import Hledger.UI.UIScreens
+import Hledger.UI.ErrorScreen (uiReloadJournal, uiCheckBalanceAssertions, uiReloadJournalIfChanged)
+import Hledger.UI.Editor (runIadd, runEditor, endPosition)
+import Brick.Widgets.Edit (getEditContents, handleEditorEvent)
+
+
+msDraw :: UIState -> [Widget Name]
+msDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=_rspec}}
+              ,ajournal=j
+              ,aScreen=MS sst
+              ,aMode=mode
+              } = dbgui "msDraw" $
+    case mode of
+      Help              -> [helpDialog, maincontent]
+      _                 -> [maincontent]
+  where
+    maincontent = Widget Greedy Greedy $ do
+      render $ defaultLayout toplabel bottomlabel $ renderList msDrawItem True (sst ^. mssList)
+      where
+        toplabel =
+              withAttr (attrName "border" <> attrName "filename") files
+          <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts
+               then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions")
+               else str "")
+          where
+            files = case journalFilePaths j of
+                           [] -> str ""
+                           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)")
+
+        bottomlabel = case mode of
+                        Minibuffer label ed -> minibuffer label ed
+                        _                   -> quickhelp
+          where
+            quickhelp = borderKeysStr' [
+               ("?", str "help")
+--              ,("RIGHT", str "register")
+              -- ,("t", renderToggle (tree_ ropts) "list" "tree")
+              -- ,("t", str "tree")
+              -- ,("l", str "list")
+              -- ,("-+", str "depth")
+              -- ,("H", renderToggle (not ishistorical) "end-bals" "changes")
+              -- ,("F", renderToggle1 (isJust . forecast_ $ inputopts_ copts) "forecast")
+              --,("/", "filter")
+              --,("DEL", "unfilter")
+              --,("ESC", "cancel/top")
+              ,("a", str "add")
+--               ,("g", "reload")
+              ,("q", str "quit")
+              ]
+
+msDraw _ =  dbgui "msDraw" $ errorWrongScreenType "draw function"  -- PARTIAL:
+
+-- msDrawItem :: (Int,Int) -> Bool -> MenuScreenItem -> Widget Name
+-- msDrawItem (_acctwidth, _balwidth) _selected MenuScreenItem{..} =
+msDrawItem :: Bool -> MenuScreenItem -> Widget Name
+msDrawItem _selected MenuScreenItem{..} =
+  Widget Greedy Fixed $ do
+    render $ txt msItemScreenName
+
+-- XXX clean up like asHandle
+msHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+msHandle ev = do
+  ui0 <- get'
+  dbguiEv "msHandle"
+  case ui0 of
+    ui@UIState{
+       aopts=UIOpts{uoCliOpts=copts}
+      ,ajournal=j
+      ,aMode=mode
+      ,aScreen=MS sst
+      } -> do
+      let
+        -- save the currently selected account, in case we leave this screen and lose the selection
+        mselscr = case listSelectedElement $ _mssList sst of
+                    Just (_, MenuScreenItem{..}) -> Just msItemScreen
+                    Nothing -> Nothing
+        nonblanks = V.takeWhile (not . T.null . msItemScreenName) $ listElements $ _mssList sst
+        lastnonblankidx = max 0 (length nonblanks - 1)
+      d <- liftIO getCurrentDay
+
+      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 ()
+
+        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
+            -- 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
+
+--             -- display mode/query toggles
+--             VtyEvent (EvKey (KChar 'H') []) -> modify' (regenerateScreens j d . toggleHistorical) >> msCenterAndContinue
+--             VtyEvent (EvKey (KChar 't') []) -> modify' (regenerateScreens j d . toggleTree) >> msCenterAndContinue
+--             VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> modify' (regenerateScreens j d . toggleEmpty) >> msCenterAndContinue
+--             VtyEvent (EvKey (KChar 'R') []) -> modify' (regenerateScreens j d . toggleReal) >> msCenterAndContinue
+--             VtyEvent (EvKey (KChar 'U') []) -> modify' (regenerateScreens j d . toggleUnmarked) >> msCenterAndContinue
+--             VtyEvent (EvKey (KChar 'P') []) -> modify' (regenerateScreens j d . togglePending) >> msCenterAndContinue
+--             VtyEvent (EvKey (KChar 'C') []) -> modify' (regenerateScreens j d . toggleCleared) >> msCenterAndContinue
+--             VtyEvent (EvKey (KChar 'F') []) -> modify' (regenerateScreens j d . toggleForecast d)
+
+            -- 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 (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle (_mssList sst) >> redraw
+            VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
+
+            -- RIGHT enters selected screen if there is one
+            VtyEvent e | e `elem` moveRightEvents
+                      , not $ isBlankElement $ listSelectedElement (_mssList sst) ->
+              msEnterScreen d (fromMaybe Accounts mselscr) 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 $ (=="") clickedname -> do
+              put' ui{aScreen=MS sst}  -- XXX does this do anything ?
+              where
+                item = listElements (_mssList sst) !? y
+                clickedname = maybe "" msItemScreenName item
+                -- mclickedscr  = msItemScreen <$> item
+            -- and on MouseUp, enter the subscreen
+            MouseUp _n (Just BLeft) Location{loc=(_x,y)} ->  -- | not $ (=="") clickedname ->
+              case mclickedscr of
+                Just scr -> msEnterScreen d scr ui
+                Nothing  -> return ()
+              where
+                item = listElements (_mssList sst) !? y
+                -- clickedname = maybe "" msItemScreenName item
+                mclickedscr  = msItemScreen <$> item
+
+            -- 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 $ (_mssList sst)^.listNameL) 1
+              where mnextelement = listSelectedElement $ listMoveDown (_mssList sst)
+
+            -- 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' (_mssList sst) $ listScrollPushingSelection name (msListSize (_mssList sst)) scrollamt
+              put' ui{aScreen=MS sst{_mssList=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' (_mssList sst) $ handleListEvent e
+              if isBlankElement $ listSelectedElement l
+              then do
+                let l' = listMoveTo lastnonblankidx l
+                scrollSelectionToMiddle l'
+                put' ui{aScreen=MS sst{_mssList=l'}}
+              else
+                put' ui{aScreen=MS sst{_mssList=l}}
+
+            -- fall through to the list's event handler (handles up/down)
+            VtyEvent e -> do
+              list' <- nestEventM' (_mssList sst) $ handleListEvent (normaliseMovementKeys e)
+              put' ui{aScreen=MS $ sst & mssList .~ list'}
+
+            MouseDown{} -> return ()
+            MouseUp{}   -> return ()
+            AppEvent _  -> return ()
+
+    _ -> dbguiEv "msHandle" >> errorWrongScreenType "event handler"
+
+msEnterScreen :: Day -> ScreenName -> UIState -> EventM Name UIState ()
+msEnterScreen d scrname ui@UIState{ajournal=j, aopts=uopts} = do
+  dbguiEv "msEnterScreen"
+  let
+    scr = case scrname of
+      Accounts        -> asNew uopts d j Nothing
+      Balancesheet    -> bsNew uopts d j Nothing
+      Incomestatement -> isNew uopts d j Nothing
+  put' $ pushScreen scr ui
+
+-- | Set the selected list item on the menu screen. Has no effect on other screens.
+msSetSelectedScreen :: Int -> Screen -> Screen
+msSetSelectedScreen selidx (MS mss@MSS{_mssList=l}) = MS mss{_mssList=listMoveTo selidx l}
+msSetSelectedScreen _ s = s
+
+isBlankElement mel = ((msItemScreenName . snd) <$> mel) == Just ""
+
+msListSize = V.length . V.takeWhile ((/="").msItemScreenName) . listElements
+
diff --git a/Hledger/UI/RegisterScreen.hs b/Hledger/UI/RegisterScreen.hs
--- a/Hledger/UI/RegisterScreen.hs
+++ b/Hledger/UI/RegisterScreen.hs
@@ -7,149 +7,50 @@
 {-# OPTIONS_GHC -Wno-incomplete-record-updates #-}
 
 module Hledger.UI.RegisterScreen
- (registerScreen
- ,rsHandle
- ,rsSetAccount
- ,rsCenterSelection
- )
+(rsNew
+,rsUpdate
+,rsDraw
+,rsHandle
+,rsSetAccount
+,rsCenterSelection
+)
 where
 
 import Control.Monad
 import Control.Monad.IO.Class (liftIO)
+import Data.Bifunctor (bimap, Bifunctor (second))
 import Data.List
 import Data.Maybe
 import qualified Data.Text as T
-import Data.Time.Calendar
 import qualified Data.Vector as V
+import Data.Vector ((!?))
 import Graphics.Vty (Event(..),Key(..),Modifier(..), Button (BLeft, BScrollDown, BScrollUp))
 import Brick
 import Brick.Widgets.List hiding (reverse)
 import Brick.Widgets.Edit
 import Lens.Micro.Platform
-import Safe
 import System.Console.ANSI
 
-
 import Hledger
 import Hledger.Cli hiding (mode, progname,prognameandversion)
 import Hledger.UI.UIOptions
--- import Hledger.UI.Theme
 import Hledger.UI.UITypes
 import Hledger.UI.UIState
 import Hledger.UI.UIUtils
+import Hledger.UI.UIScreens
 import Hledger.UI.Editor
-import Hledger.UI.TransactionScreen
-import Hledger.UI.ErrorScreen
-import Data.Vector ((!?))
-
-registerScreen :: Screen
-registerScreen = RegisterScreen{
-   sInit   = rsInit
-  ,sDraw   = rsDraw
-  ,sHandle = rsHandle
-  ,rsList    = list RegisterList V.empty 1
-  ,rsAccount = ""
-  ,rsForceInclusive = False
-  }
-
-rsSetAccount :: AccountName -> Bool -> Screen -> Screen
-rsSetAccount a forceinclusive scr@RegisterScreen{} =
-  scr{rsAccount=replaceHiddenAccountsNameWith "*" a, rsForceInclusive=forceinclusive}
-rsSetAccount _ _ scr = scr
-
-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
-    -- XXX temp
-    inclusive = tree_ ropts || rsForceInclusive
-    thisacctq = Acct $ (if inclusive then accountNameToAccountRegex else accountNameToAccountOnlyRegex) rsAccount
-
-    -- adjust the report options and regenerate the ReportSpec, carefully as usual to avoid screwups (#1523)
-    ropts' = ropts {
-        -- ignore any depth limit, as in postingsReport; allows register's total to match accounts screen
-        depth_=Nothing
-        -- do not strip prices so we can toggle costs within the ui
-      , show_costs_=True
-      -- XXX aregister also has this, needed ?
-        -- always show historical balance
-      -- , balanceaccum_= Historical
-      }
-    wd = whichDate ropts'
-    rspec' = reportSpecSetFutureAndForecast d (forecast_ $ inputopts_ copts) .
-      either (error "rsInit: adjusting the query for register, should not have failed") id $ -- PARTIAL:
-      updateReportSpec ropts' rspec{_rsDay=d}
-    items = accountTransactionsReport rspec' j thisacctq
-    items' = (if empty_ ropts then id else filter (not . mixedAmountLooksZero . fifth6)) $  -- without --empty, exclude no-change txns
-             reverse  -- most recent last
-             items
-
-    -- generate pre-rendered list items. This helps calculate column widths.
-    displayitems = map displayitem items'
-      where
-        displayitem (t, _, _issplit, otheracctsstr, change, bal) =
-          RegisterScreenItem{rsItemDate          = showDate $ transactionRegisterDate wd (_rsQuery rspec') thisacctq t
-                            ,rsItemStatus        = tstatus t
-                            ,rsItemDescription   = tdescription t
-                            ,rsItemOtherAccounts = otheracctsstr
-                                                     -- _   -> "<split>"  -- should do this if accounts field width < 30
-                            ,rsItemChangeAmount  = showamt change
-                            ,rsItemBalanceAmount = showamt bal
-                            ,rsItemTransaction   = t
-                            }
-            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 uiNumBlankItems
-          RegisterScreenItem{rsItemDate          = ""
-                            ,rsItemStatus        = Unmarked
-                            ,rsItemDescription   = ""
-                            ,rsItemOtherAccounts = ""
-                            ,rsItemChangeAmount  = mempty
-                            ,rsItemBalanceAmount = mempty
-                            ,rsItemTransaction   = nulltransaction
-                            }
-    -- build the List
-    newitems = list RegisterList (V.fromList $ displayitems ++ blankitems) 1
-
-    -- decide which transaction is selected:
-    -- if reset is true, the last (latest) transaction;
-    -- otherwise, the previously selected transaction if possible;
-    -- otherwise, the transaction nearest in date to it;
-    -- or if there's several with the same date, the nearest in journal order;
-    -- otherwise, the last (latest) transaction.
-    newitems' = listMoveTo newselidx newitems
-      where
-        newselidx =
-          case (reset, listSelectedElement rsList) of
-            (True, _)    -> endidx
-            (_, Nothing) -> endidx
-            (_, Just (_, RegisterScreenItem{rsItemTransaction=Transaction{tindex=prevselidx, tdate=prevseld}})) ->
-              headDef endidx $ catMaybes [
-                 findIndex ((==prevselidx) . tindex . rsItemTransaction) displayitems
-                ,findIndex ((==nearestidbydatethenid) . Just . tindex . rsItemTransaction) displayitems
-                ]
-              where
-                nearestidbydatethenid = third3 <$> (headMay $ sort
-                  [(abs $ diffDays (tdate t) prevseld, abs (tindex t - prevselidx), tindex t) | t <- ts])
-                ts = map rsItemTransaction displayitems
-        endidx = max 0 $ length displayitems - 1
-
-rsInit _ _ _ = dlogUiTrace "rsInit 2" $ errorWrongScreenType "init function"  -- PARTIAL:
+import Hledger.UI.ErrorScreen (uiReloadJournal, uiCheckBalanceAssertions, uiReloadJournalIfChanged)
 
 rsDraw :: UIState -> [Widget Name]
 rsDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}}
-              ,aScreen=RegisterScreen{..}
+              ,aScreen=RS RSS{..}
               ,aMode=mode
-              } = dlogUiTrace "rsDraw 1" $
+              } = dbgui "rsDraw 1" $
   case mode of
-    Help       -> [helpDialog copts, maincontent]
-    -- Minibuffer e -> [minibuffer e, maincontent]
+    Help       -> [helpDialog, maincontent]
     _          -> [maincontent]
   where
-    displayitems = V.toList $ rsList ^. listElementsL
+    displayitems = V.toList $ listElements $ _rssList
     maincontent = Widget Greedy Greedy $ do
       -- calculate column widths, based on current available width
       c <- getContext
@@ -191,7 +92,7 @@
         acctswidth = maxdescacctswidth - descwidth
         colwidths = (datewidth,descwidth,acctswidth,changewidth,balwidth)
 
-      render $ defaultLayout toplabel bottomlabel $ renderList (rsDrawItem colwidths) True rsList
+      render $ defaultLayout toplabel bottomlabel $ renderList (rsDrawItem colwidths) True _rssList
 
       where
         ropts = _rsReportOpts rspec
@@ -199,7 +100,7 @@
         -- inclusive = tree_ ropts || rsForceInclusive
 
         toplabel =
-              withAttr (attrName "border" <> attrName "bold") (str $ T.unpack $ replaceHiddenAccountsNameWith "All" rsAccount)
+              withAttr (attrName "border" <> attrName "bold") (str $ T.unpack $ replaceHiddenAccountsNameWith "All" _rssAccount)
 --           <+> withAttr ("border" <> "query") (str $ if inclusive then "" else " exclusive")
           <+> togglefilters
           <+> str " transactions"
@@ -222,11 +123,11 @@
                   ] of
                 [] -> str ""
                 fs -> withAttr (attrName "border" <> attrName "query") (str $ " " ++ intercalate ", " fs)
-            cur = str $ case rsList ^. listSelectedL of
+            cur = str $ case listSelected _rssList of
                          Nothing -> "-"
                          Just i -> show (i + 1)
             total = str $ show $ length nonblanks
-            nonblanks = V.takeWhile (not . T.null . rsItemDate) $ rsList^.listElementsL
+            nonblanks = V.takeWhile (not . T.null . rsItemDate) $ listElements $ _rssList
 
             -- query = query_ $ reportopts_ $ cliopts_ opts
 
@@ -251,7 +152,7 @@
 --               ,("q", "quit")
               ]
 
-rsDraw _ = dlogUiTrace "rsDraw 2" $ errorWrongScreenType "draw function"  -- PARTIAL:
+rsDraw _ = dbgui "rsDraw 2" $ errorWrongScreenType "draw function"  -- PARTIAL:
 
 rsDrawItem :: (Int,Int,Int,Int,Int) -> Bool -> RegisterScreenItem -> Widget Name
 rsDrawItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected RegisterScreenItem{..} =
@@ -278,23 +179,30 @@
     sel | selected  = (<> attrName "selected")
         | otherwise = id
 
+-- XXX clean up like asHandle
 rsHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
 rsHandle ev = do
   ui0 <- get'
-  dlogUiTraceM "rsHandle 1"
+  dbguiEv "rsHandle 1"
   case ui0 of
     ui@UIState{
-      aScreen=scr@RegisterScreen{..}
+      aScreen=RS sst@RSS{..}
       ,aopts=UIOpts{uoCliOpts=copts}
       ,ajournal=j
       ,aMode=mode
       } -> do
+      d <- liftIO getCurrentDay
       let
-        d = copts^.rsDay
         journalspan = journalDateSpan False j
-        nonblanks = V.takeWhile (not . T.null . rsItemDate) $ rsList^.listElementsL
+        nonblanks = V.takeWhile (not . T.null . rsItemDate) $ listElements $ _rssList
         lastnonblankidx = max 0 (length nonblanks - 1)
-
+        numberedtxns = zipWith (curry (second rsItemTransaction)) [(1::Integer)..] (V.toList nonblanks)
+        -- the transactions being shown and the currently selected or last transaction, if any:
+        mtxns :: Maybe ([NumberedTransaction], NumberedTransaction)
+        mtxns = case numberedtxns of
+          []        -> Nothing
+          nts@(_:_) -> Just (nts, maybe (last nts) (bimap ((+1).fromIntegral) rsItemTransaction) $
+                              listSelectedElement _rssList)  -- PARTIAL: last won't fail
       case mode of
         Minibuffer _ ed ->
           case ev of
@@ -326,19 +234,23 @@
             VtyEvent (EvKey (KChar 'q') []) -> halt
             VtyEvent (EvKey KEsc        []) -> put' $ resetScreens d ui
             VtyEvent (EvKey (KChar c)   []) | c == '?' -> put' $ setMode Help ui
+
+            -- AppEvents arrive in --watch mode, see AccountsScreen
             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] ->
+
+            e | e `elem` [AppEvent FileChange, VtyEvent (EvKey (KChar 'g') [])] ->
               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
+                (pos,f) = case listSelectedElement _rssList of
                             Nothing -> (endPosition, journalFilePath j)
                             Just (_, RegisterScreenItem{
                               rsItemTransaction=Transaction{tsourcepos=(SourcePos f' l c,_)}}) -> (Just (unPos l, Just $ unPos c),f')
@@ -361,73 +273,85 @@
             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 'l') [MCtrl]) -> scrollSelectionToMiddle _rssList >> redraw
             VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
 
             -- 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 _  -> put' $ screenEnter d transactionScreen{tsAccount=rsAccount} ui
-                Nothing -> put' ui
+              case mtxns of Nothing -> return (); Just (nts, nt) -> rsEnterTransactionScreen _rssAccount nts nt 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
+              put' $ ui{aScreen=RS sst{_rssList=listMoveTo y _rssList}}
+              where clickeddate = maybe "" rsItemDate $ listElements _rssList !? 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
+              case mtxns of Nothing -> return (); Just (nts, nt) -> rsEnterTransactionScreen _rssAccount nts nt ui
+              where clickeddate = maybe "" rsItemDate $ listElements _rssList !? 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
-              where mnextelement = listSelectedElement $ listMoveDown rsList
+              vScrollBy (viewportScroll $ listName $ _rssList) 1
+              where mnextelement = listSelectedElement $ listMoveDown _rssList
 
             -- 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'}}
+              list' <- nestEventM' _rssList $ listScrollPushingSelection name (rsListSize _rssList) scrollamt
+              put' ui{aScreen=RS sst{_rssList=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
+              l <- nestEventM' _rssList $ handleListEvent e
               if isBlankElement $ listSelectedElement l
               then do
                 let l' = listMoveTo lastnonblankidx l
                 scrollSelectionToMiddle l'
-                put' ui{aScreen=scr{rsList=l'}}
+                put' ui{aScreen=RS sst{_rssList=l'}}
               else
-                put' ui{aScreen=scr{rsList=l}}
+                put' ui{aScreen=RS sst{_rssList=l}}
 
             -- 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}}
+              newitems <- nestEventM' _rssList $ handleListEvent e'
+              put' ui{aScreen=RS sst{_rssList=newitems}}
 
             MouseDown{}       -> return ()
             MouseUp{}         -> return ()
             AppEvent _        -> return ()
 
-    _ -> dlogUiTrace "rsHandle 2" $ errorWrongScreenType "event handler"
+    _ -> dbgui "rsHandle 2" $ errorWrongScreenType "event handler"
 
 isBlankElement mel = ((rsItemDate . snd) <$> mel) == Just ""
 
+rsListSize = V.length . V.takeWhile ((/="").rsItemDate) . listElements
+
+rsSetAccount :: AccountName -> Bool -> Screen -> Screen
+rsSetAccount a forceinclusive (RS st@RSS{}) =
+  RS st{_rssAccount=replaceHiddenAccountsNameWith "*" a, _rssForceInclusive=forceinclusive}
+rsSetAccount _ _ st = st
+
+-- | Scroll the selected item to the middle of the screen, when on the register screen.
+-- No effect on other screens.
 rsCenterSelection :: UIState -> EventM Name UIState UIState
-rsCenterSelection ui = do
-  scrollSelectionToMiddle $ rsList $ aScreen ui
+rsCenterSelection ui@UIState{aScreen=RS sst} = do
+  scrollSelectionToMiddle $ _rssList sst
   return ui  -- ui is unchanged, but this makes the function more chainable
+rsCenterSelection ui = return ui
 
-rsListSize = V.length . V.takeWhile ((/="").rsItemDate) . listElements
+rsEnterTransactionScreen :: AccountName -> [NumberedTransaction] -> NumberedTransaction -> UIState -> EventM Name UIState ()
+rsEnterTransactionScreen acct nts nt ui = do
+  dbguiEv "rsEnterTransactionScreen"
+  put' $
+    pushScreen (tsNew acct nts nt)
+    ui
+
+
diff --git a/Hledger/UI/TransactionScreen.hs b/Hledger/UI/TransactionScreen.hs
--- a/Hledger/UI/TransactionScreen.hs
+++ b/Hledger/UI/TransactionScreen.hs
@@ -6,7 +6,10 @@
 {-# OPTIONS_GHC -Wno-incomplete-record-updates #-}
 
 module Hledger.UI.TransactionScreen
-( transactionScreen
+(tsNew
+,tsUpdate
+,tsDraw
+,tsHandle
 ) where
 
 import Control.Monad
@@ -14,79 +17,32 @@
 import Data.List
 import Data.Maybe
 import qualified Data.Text as T
-import Data.Time.Calendar (Day)
-import qualified Data.Vector as V
 import Graphics.Vty (Event(..),Key(..),Modifier(..), Button (BLeft))
-import Lens.Micro ((^.))
 import Brick
-import Brick.Widgets.List (listElementsL, listMoveTo, listSelectedElement)
+import Brick.Widgets.List (listMoveTo)
 
 import Hledger
 import Hledger.Cli hiding (mode, prices, progname,prognameandversion)
 import Hledger.UI.UIOptions
--- import Hledger.UI.Theme
 import Hledger.UI.UITypes
 import Hledger.UI.UIState
 import Hledger.UI.UIUtils
+import Hledger.UI.UIScreens
 import Hledger.UI.Editor
-import Hledger.UI.ErrorScreen
 import Brick.Widgets.Edit (editorText, renderEditor)
-
-transactionScreen :: Screen
-transactionScreen = TransactionScreen{
-   sInit   = tsInit
-  ,sDraw   = tsDraw
-  ,sHandle = tsHandle
-  ,tsTransaction  = (1,nulltransaction)
-  ,tsTransactions = [(1,nulltransaction)]
-  ,tsAccount      = ""
-  }
-
-tsInit :: Day -> Bool -> UIState -> UIState
-tsInit _d _reset ui@UIState{aopts=UIOpts{}
-                           ,ajournal=_j
-                           ,aScreen=s@TransactionScreen{tsTransaction=(_,t),tsTransactions=nts}
-                           ,aPrevScreens=prevscreens
-                           } =
-    ui{aScreen=s{tsTransaction=(i',t'),tsTransactions=nts'}}
-  where
-    i' = maybe 0 (toInteger . (+1)) . elemIndex t' $ map snd nts'
-    -- If the previous screen was RegisterScreen, use the listed and selected items as
-    -- the transactions. Otherwise, use the provided transaction and list.
-    (t',nts') = case prevscreens of
-        RegisterScreen{rsList=xs}:_ -> (seltxn, zip [1..] $ map rsItemTransaction nonblanks)
-          where
-            seltxn = maybe nulltransaction (rsItemTransaction . snd) $ listSelectedElement xs
-            nonblanks = V.toList . V.takeWhile (not . T.null . rsItemDate) $ xs ^. listElementsL
-        _                           -> (t, nts)
-tsInit _ _ _ = errorWrongScreenType "init function"  -- PARTIAL:
-
--- Render a transaction suitably for the transaction screen.
-showTxn :: ReportOpts -> ReportSpec -> Journal -> Transaction -> T.Text
-showTxn ropts rspec j t =
-      showTransactionOneLineAmounts
-    $ maybe id (transactionApplyValuation prices styles periodlast (_rsDay rspec)) (value_ ropts)
-    $ maybe id (transactionToCost styles) (conversionop_ ropts) t
-    -- (if real_ ropts then filterTransactionPostings (Real True) else id) -- filter postings by --real
-  where
-    prices = journalPriceOracle (infer_prices_ ropts) j
-    styles = journalCommodityStyles j
-    periodlast =
-      fromMaybe (error' "TransactionScreen: expected a non-empty journal") $  -- PARTIAL: shouldn't happen
-      reportPeriodOrJournalLastDay rspec j
+import Hledger.UI.ErrorScreen (uiReloadJournalIfChanged, uiCheckBalanceAssertions)
 
 tsDraw :: UIState -> [Widget Name]
 tsDraw UIState{aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}
               ,ajournal=j
-              ,aScreen=TransactionScreen{tsTransaction=(i,t')
-                                        ,tsTransactions=nts
-                                        ,tsAccount=acct
-                                        }
+              ,aScreen=TS TSS{_tssTransaction=(i,t')
+                              ,_tssTransactions=nts
+                              ,_tssAccount=acct
+                              }
               ,aMode=mode
               } =
   case mode of
-    Help       -> [helpDialog copts, maincontent]
-    -- Minibuffer e -> [minibuffer e, maincontent]
+    Help       -> [helpDialog, maincontent]
     _          -> [maincontent]
   where
     maincontent = Widget Greedy Greedy $ render $ defaultLayout toplabel bottomlabel txneditor
@@ -141,15 +97,29 @@
 
 tsDraw _ = errorWrongScreenType "draw function"  -- PARTIAL:
 
+-- Render a transaction suitably for the transaction screen.
+showTxn :: ReportOpts -> ReportSpec -> Journal -> Transaction -> T.Text
+showTxn ropts rspec j t =
+      showTransactionOneLineAmounts
+    $ maybe id (transactionApplyValuation prices styles periodlast (_rsDay rspec)) (value_ ropts)
+    $ maybe id (transactionToCost styles) (conversionop_ ropts) t
+    -- (if real_ ropts then filterTransactionPostings (Real True) else id) -- filter postings by --real
+  where
+    prices = journalPriceOracle (infer_prices_ ropts) j
+    styles = journalCommodityStyles j
+    periodlast =
+      fromMaybe (error' "TransactionScreen: expected a non-empty journal") $  -- PARTIAL: shouldn't happen
+      reportPeriodOrJournalLastDay rspec j
+
 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
-                    } ->
+    ui@UIState{aScreen=TS TSS{_tssTransaction=(i,t), _tssTransactions=nts}
+              ,aopts=UIOpts{uoCliOpts=copts}
+              ,ajournal=j
+              ,aMode=mode
+              } ->
       case mode of
         Help ->
           case ev of
@@ -159,8 +129,8 @@
             _ -> helpHandle ev
 
         _ -> do
+          d <- liftIO getCurrentDay
           let
-            d = copts^.rsDay
             (iprev,tprev) = maybe (i,t) ((i-1),) $ lookup (i-1) nts
             (inext,tnext) = maybe (i,t) ((i+1),) $ lookup (i+1) nts
           case ev of
@@ -179,7 +149,7 @@
               -- 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
+                Left err -> put' $ pushScreen (esNew err) ui
                 Right j' -> put' $ regenerateScreens j' d ui
             VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
 
@@ -198,9 +168,6 @@
             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
             VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
@@ -209,12 +176,14 @@
     _ -> 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
+tsSelect :: Integer -> Transaction -> UIState -> UIState
+tsSelect i t ui@UIState{aScreen=TS sst} = case aPrevScreens ui of
     x:xs -> ui'{aPrevScreens=rsSelect i x : xs}
     []   -> ui'
-  where ui' = ui{aScreen=s{tsTransaction=(i,t)}}
+  where ui' = ui{aScreen=TS sst{_tssTransaction=(i,t)}}
 tsSelect _ _ ui = ui
 
 -- | Select the nth item on the register screen.
-rsSelect i scr@RegisterScreen{..} = scr{rsList=listMoveTo (fromInteger $ i-1) rsList}
+rsSelect :: Integer -> Screen -> Screen
+rsSelect i (RS sst@RSS{..}) = RS sst{_rssList=listMoveTo (fromInteger $ i-1) _rssList}
 rsSelect _ scr = scr
diff --git a/Hledger/UI/UIOptions.hs b/Hledger/UI/UIOptions.hs
--- a/Hledger/UI/UIOptions.hs
+++ b/Hledger/UI/UIOptions.hs
@@ -36,7 +36,11 @@
   -- flagNone ["debug-ui"] (setboolopt "rules-file") "run with no terminal output, showing console"
    flagNone ["watch","w"] (setboolopt "watch") "watch for data and date changes and reload automatically"
   ,flagReq  ["theme"] (\s opts -> Right $ setopt "theme" s opts) "THEME" ("use this custom display theme ("++intercalate ", " themeNames++")")
-  ,flagReq  ["register"] (\s opts -> Right $ setopt "register" s opts) "ACCTREGEX" "start in the (first) matched account's register"
+  ,flagNone ["menu"] (setboolopt "menu") "start in the menu screen"
+  ,flagNone ["all"] (setboolopt "all") "start in the all accounts screen"
+  ,flagNone ["bs"] (setboolopt "bs") "start in the balance sheet accounts screen"
+  ,flagNone ["is"] (setboolopt "is") "start in the income statement accounts screen"
+  ,flagReq  ["register"] (\s opts -> Right $ setopt "register" s opts) "ACCTREGEX" "start in the (first matched) account's register"
   ,flagNone ["change"] (setboolopt "change")
     "show period balances (changes) at startup instead of historical balances"
   -- ,flagNone ["cumulative"] (setboolopt "cumulative")
@@ -52,8 +56,8 @@
 
 --uimode :: Mode RawOpts
 uimode =  (mode "hledger-ui" (setopt "command" "ui" def)
-            "browse accounts, postings and entries in a full-window curses interface"
-            (argsFlag "[PATTERNS]") []){
+            "browse accounts, postings and entries in a full-window TUI"
+            (argsFlag "[--menu|--all|--bs|--is|--register=ACCT] [QUERY]") []){
               modeGroupFlags = Group {
                                 groupUnnamed = uiflags
                                ,groupHidden = hiddenflags
@@ -61,7 +65,7 @@
                                ,groupNamed = [(generalflagsgroup1)]
                                }
              ,modeHelpSuffix=[
-                  -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui."
+                  -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window TUI."
                  ]
            }
 
@@ -101,7 +105,7 @@
 --getHledgerUIOpts = processArgs uimode >>= return >>= rawOptsToUIOpts
 getHledgerUIOpts = do
   args <- getArgs >>= expandArgsAt
-  let args' = replaceNumericFlags args
+  let args' = replaceNumericFlags $ ensureDebugHasArg args
   let cmdargopts = either usageError id $ process uimode args'
   rawOptsToUIOpts cmdargopts
 
diff --git a/Hledger/UI/UIScreens.hs b/Hledger/UI/UIScreens.hs
new file mode 100644
--- /dev/null
+++ b/Hledger/UI/UIScreens.hs
@@ -0,0 +1,329 @@
+-- | Constructors and updaters for all hledger-ui screens.
+--
+-- Constructors (*New) create and initialise a new screen with valid state,
+-- based on the provided options, reporting date, journal, and screen-specific parameters.
+--
+-- Updaters (*Update) recalculate an existing screen's state, 
+-- based on new options, reporting date, journal, and the old screen state.
+--
+-- These are gathered in this low-level module so that any screen's handler 
+-- can create or regenerate all other screens.
+-- Drawing and event-handling code is elsewhere, in per-screen modules.
+
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NamedFieldPuns #-}
+
+module Hledger.UI.UIScreens
+(screenUpdate
+,esNew
+,esUpdate
+,msNew
+,msUpdate
+,asNew
+,asUpdate
+,bsNew
+,bsUpdate
+,isNew
+,isUpdate
+,rsNew
+,rsUpdate
+,tsNew
+,tsUpdate
+)
+where
+
+import Brick.Widgets.List (listMoveTo, listSelectedElement, list)
+import Data.List
+import Data.Maybe
+import Data.Time.Calendar (Day, diffDays)
+import Safe
+import qualified Data.Vector as V
+
+import Hledger.Cli hiding (mode, progname,prognameandversion)
+import Hledger.UI.UIOptions
+import Hledger.UI.UITypes
+import Hledger.UI.UIUtils
+import Data.Function ((&))
+
+
+-- | Regenerate the content of any screen from new options, reporting date and journal.
+screenUpdate :: UIOpts -> Day -> Journal -> Screen -> Screen
+screenUpdate opts d j = \case
+  MS sst -> MS $ msUpdate sst  -- opts d j ass
+  AS sst -> AS $ asUpdate opts d j sst
+  BS sst -> BS $ bsUpdate opts d j sst
+  IS sst -> IS $ isUpdate opts d j sst
+  RS sst -> RS $ rsUpdate opts d j sst
+  TS sst -> TS $ tsUpdate sst
+  ES sst -> ES $ esUpdate sst
+
+-- | Construct an error screen.
+-- Screen-specific arguments: the error message to show.
+esNew :: String -> Screen
+esNew msg =
+  dbgui "esNew" $
+  ES ESS {
+    _essError = msg
+    ,_essUnused = ()
+    }
+
+-- | Update an error screen. Currently a no-op since error screen
+-- depends only on its screen-specific state.
+esUpdate :: ErrorScreenState -> ErrorScreenState
+esUpdate = dbgui "esUpdate`"
+
+-- | Construct a menu screen.
+-- Screen-specific arguments: none.
+msNew :: Screen
+msNew =
+  dbgui "msNew" $
+  MS MSS {
+     _mssList            = list MenuList (V.fromList [
+      -- keep initial screen stack setup in UI.Main synced with these
+       MenuScreenItem "All accounts" Accounts
+      ,MenuScreenItem "Balance sheet accounts" Balancesheet
+      ,MenuScreenItem "Income statement accounts" Incomestatement
+      ]) 1
+      & listMoveTo 1  -- select balance sheet accounts screen at startup (currently this screen is constructed only then)
+    ,_mssUnused = ()
+    }
+
+-- | Update a menu screen. Currently a no-op since menu screen
+-- has unchanging content.
+msUpdate :: MenuScreenState -> MenuScreenState
+msUpdate = dbgui "msUpdate"
+
+nullass macct = ASS {
+   _assSelectedAccount = fromMaybe "" macct
+  ,_assList            = list AccountsList (V.fromList []) 1
+  }
+
+-- | Construct an accounts screen listing the appropriate set of accounts,
+-- with the appropriate one selected.
+-- Screen-specific arguments: the account to select if any.
+asNew :: UIOpts -> Day -> Journal -> Maybe AccountName -> Screen
+asNew uopts d j macct = dbgui "asNew" $ AS $ asUpdate uopts d j $ nullass macct
+
+-- | Update an accounts screen's state from these options, reporting date, and journal.
+asUpdate :: UIOpts -> Day -> Journal -> AccountsScreenState -> AccountsScreenState
+asUpdate uopts d = dbgui "asUpdate" .
+  asUpdateHelper rspec d copts roptsmod extraquery
+  where
+    UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}} = uopts
+    roptsmod       = id
+    extraquery     = Any
+
+-- | Update an accounts-like screen's state from this report spec, reporting date,
+-- cli options, report options modifier, extra query, and journal.
+asUpdateHelper :: ReportSpec -> Day -> CliOpts -> (ReportOpts -> ReportOpts) -> Query -> Journal -> AccountsScreenState -> AccountsScreenState
+asUpdateHelper rspec0 d copts roptsModify extraquery j ass = dbgui "asUpdateHelper"
+  ass{_assList=l}
+  where
+    ropts = roptsModify $ _rsReportOpts rspec0
+    rspec =
+      updateReportSpec
+        ropts
+        rspec0{_rsDay=d}  -- update to the current date, might have changed since program start
+      & either (error "asUpdateHelper: adjusting the query, should not have failed") id -- PARTIAL:
+      & reportSpecSetFutureAndForecast (forecast_ $ inputopts_ copts)  -- include/exclude future & forecast transactions
+      & reportSpecAddQuery extraquery  -- add any extra restrictions
+
+    l = listMoveTo selidx $ list AccountsList (V.fromList $ displayitems ++ blankitems) 1
+      where
+        -- which account should be selected ?
+        selidx = headDef 0 $ catMaybes [
+           elemIndex a as                               -- the one previously selected, if it can be found
+          ,findIndex (a `isAccountNamePrefixOf`) as     -- or the first account found with the same prefix
+          ,Just $ max 0 (length (filter (< a) as) - 1)  -- otherwise, the alphabetically preceding account.
+          ]
+          where
+            a = _assSelectedAccount ass
+            as = map asItemAccountName displayitems
+
+        displayitems = map displayitem items
+          where
+            -- run the report
+            (items, _) = balanceReport rspec j
+
+            -- pre-render a list item
+            displayitem (fullacct, shortacct, indent, bal) =
+              AccountsScreenItem{asItemIndentLevel        = indent
+                                ,asItemAccountName        = fullacct
+                                ,asItemDisplayAccountName = replaceHiddenAccountsNameWith "All" $ if tree_ ropts then shortacct else fullacct
+                                ,asItemMixedAmount        = Just bal
+                                }
+
+        -- blanks added for scrolling control, cf RegisterScreen.
+        blankitems = replicate uiNumBlankItems  -- XXX ugly hard-coded value. When debugging, changing to 0 reduces noise.
+          AccountsScreenItem{asItemIndentLevel        = 0
+                            ,asItemAccountName        = ""
+                            ,asItemDisplayAccountName = ""
+                            ,asItemMixedAmount        = Nothing
+                            }
+
+-- | Construct a balance sheet screen listing the appropriate set of accounts,
+-- with the appropriate one selected.
+-- Screen-specific arguments: the account to select if any.
+bsNew :: UIOpts -> Day -> Journal -> Maybe AccountName -> Screen
+bsNew uopts d j macct = dbgui "bsNew" $ BS $ bsUpdate uopts d j $ nullass macct
+
+-- | Update a balance sheet screen's state from these options, reporting date, and journal.
+bsUpdate :: UIOpts -> Day -> Journal -> AccountsScreenState -> AccountsScreenState
+bsUpdate uopts d = dbgui "bsUpdate" .
+  asUpdateHelper rspec d copts roptsmod extraquery
+  where
+    UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}} = uopts
+    roptsmod ropts = ropts{balanceaccum_=Historical}  -- always show historical end balances
+    extraquery     = Type [Asset,Liability,Equity]    -- restrict to balance sheet accounts
+
+-- | Construct an income statement screen listing the appropriate set of accounts,
+-- with the appropriate one selected.
+-- Screen-specific arguments: the account to select if any.
+isNew :: UIOpts -> Day -> Journal -> Maybe AccountName -> Screen
+isNew uopts d j macct = dbgui "isNew" $ IS $ isUpdate uopts d j $ nullass macct
+
+-- | Update an income statement screen's state from these options, reporting date, and journal.
+isUpdate :: UIOpts -> Day -> Journal -> AccountsScreenState -> AccountsScreenState
+isUpdate uopts d = dbgui "isUpdate" .
+  asUpdateHelper rspec d copts roptsmod extraquery
+  where
+    UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}} = uopts
+    roptsmod ropts = ropts{balanceaccum_=PerPeriod}  -- always show historical end balances
+    extraquery     = Type [Revenue, Expense]         -- restrict to income statement accounts
+
+-- | Construct a register screen listing the appropriate set of transactions,
+-- with the appropriate one selected.
+-- Screen-specific arguments: the account whose register this is,
+-- whether to force inclusive balances.
+rsNew :: UIOpts -> Day -> Journal -> AccountName -> Bool -> Screen
+rsNew uopts d j acct forceinclusive =  -- XXX forcedefaultselection - whether to force selecting the last transaction.
+  dbgui "rsNew" $
+  RS $
+  rsUpdate uopts d j $
+  RSS {
+     _rssAccount        = replaceHiddenAccountsNameWith "*" acct
+    ,_rssForceInclusive = forceinclusive
+    ,_rssList           = list RegisterList (V.fromList []) 1
+    }
+
+-- | Update a register screen from these options, reporting date, and journal.
+rsUpdate :: UIOpts -> Day -> Journal -> RegisterScreenState -> RegisterScreenState
+rsUpdate uopts d j rss@RSS{_rssAccount, _rssForceInclusive, _rssList=oldlist} =
+  dbgui "rsUpdate"
+  rss{_rssList=l'}
+  where
+    UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} = uopts
+    -- gather arguments and queries
+    -- XXX temp
+    inclusive = tree_ ropts || _rssForceInclusive
+    thisacctq = Acct $ mkregex _rssAccount
+      where
+        mkregex = if inclusive then accountNameToAccountRegex else accountNameToAccountOnlyRegex
+
+    -- adjust the report options and report spec, carefully as usual to avoid screwups (#1523)
+    ropts' = ropts {
+        -- ignore any depth limit, as in postingsReport; allows register's total to match accounts screen
+        depth_=Nothing
+        -- do not strip prices so we can toggle costs within the ui
+      , show_costs_=True
+      -- XXX aregister also has this, needed ?
+        -- always show historical balance
+      -- , balanceaccum_= Historical
+      }
+    rspec' =
+      updateReportSpec ropts' rspec{_rsDay=d}
+      & either (error "rsUpdate: adjusting the query for register, should not have failed") id -- PARTIAL:
+      & reportSpecSetFutureAndForecast (forecast_ $ inputopts_ copts)
+
+    -- gather transactions to display
+    items = accountTransactionsReport rspec' j thisacctq
+    items' =
+      (if empty_ ropts then id else filter (not . mixedAmountLooksZero . fifth6)) $  -- without --empty, exclude no-change txns
+      reverse  -- most recent last
+      items
+
+    -- pre-render the list items, helps calculate column widths
+    displayitems = map displayitem items'
+      where
+        displayitem (t, _, _issplit, otheracctsstr, change, bal) =
+          RegisterScreenItem{rsItemDate          = showDate $ transactionRegisterDate wd (_rsQuery rspec') thisacctq t
+                            ,rsItemStatus        = tstatus t
+                            ,rsItemDescription   = tdescription t
+                            ,rsItemOtherAccounts = otheracctsstr
+                                                    -- _   -> "<split>"  -- should do this if accounts field width < 30
+                            ,rsItemChangeAmount  = showamt change
+                            ,rsItemBalanceAmount = showamt bal
+                            ,rsItemTransaction   = t
+                            }
+            where
+              showamt = showMixedAmountB oneLine{displayMaxWidth=Just 3}
+              wd = whichDate ropts'
+
+    -- 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 uiNumBlankItems
+          RegisterScreenItem{rsItemDate          = ""
+                            ,rsItemStatus        = Unmarked
+                            ,rsItemDescription   = ""
+                            ,rsItemOtherAccounts = ""
+                            ,rsItemChangeAmount  = mempty
+                            ,rsItemBalanceAmount = mempty
+                            ,rsItemTransaction   = nulltransaction
+                            }
+
+    -- build the new list widget
+    l = list RegisterList (V.fromList $ displayitems ++ blankitems) 1
+
+    -- ensure the appropriate list item is selected:
+    -- if forcedefaultselection is true, the last (latest) transaction;  XXX still needed ?
+    -- otherwise, the previously selected transaction if possible;
+    -- otherwise, the transaction nearest in date to it;
+    -- or if there's several with the same date, the nearest in journal order;
+    -- otherwise, the last (latest) transaction.
+    l' = listMoveTo newselidx l
+      where
+        endidx = max 0 $ length displayitems - 1
+        newselidx =
+          -- case (forcedefaultselection, listSelectedElement _rssList) of
+          --   (True, _)    -> endidx
+          --   (_, Nothing) -> endidx
+          --   (_, Just (_, RegisterScreenItem{rsItemTransaction=Transaction{tindex=prevselidx, tdate=prevseld}})) ->
+          --     headDef endidx $ catMaybes [
+          --       findIndex ((==prevselidx) . tindex . rsItemTransaction) displayitems
+          --       ,findIndex ((==nearestidbydatethenid) . Just . tindex . rsItemTransaction) displayitems
+          --       ]
+          --     where
+          --       nearestidbydatethenid = third3 <$> (headMay $ sort
+          --         [(abs $ diffDays (tdate t) prevseld, abs (tindex t - prevselidx), tindex t) | t <- ts])
+          --       ts = map rsItemTransaction displayitems
+          case listSelectedElement oldlist of
+            Nothing -> endidx
+            Just (_, RegisterScreenItem{rsItemTransaction=Transaction{tindex=prevselidx, tdate=prevseld}}) ->
+              headDef endidx $ catMaybes [
+                findIndex ((==prevselidx) . tindex . rsItemTransaction) displayitems
+                ,findIndex ((==nearestidbydatethenid) . Just . tindex . rsItemTransaction) displayitems
+                ]
+              where
+                nearestidbydatethenid = third3 <$> (headMay $ sort
+                  [(abs $ diffDays (tdate t) prevseld, abs (tindex t - prevselidx), tindex t) | t <- ts])
+                ts = map rsItemTransaction displayitems
+
+-- | Construct a transaction screen showing one of a given list of transactions,
+-- with the ability to step back and forth through the list.
+-- Screen-specific arguments: the account whose transactions are being shown,
+-- the list of showable transactions, the currently shown transaction.
+tsNew :: AccountName -> [NumberedTransaction] -> NumberedTransaction -> Screen
+tsNew acct nts nt =
+  dbgui "tsNew" $
+  TS TSS{
+     _tssAccount      = acct
+    ,_tssTransactions = nts
+    ,_tssTransaction  = nt
+    }
+
+-- | Update a transaction screen. Currently a no-op since transaction screen
+-- depends only on its screen-specific state.
+tsUpdate :: TransactionScreenState -> TransactionScreenState
+tsUpdate = dbgui "tsUpdate"
+
diff --git a/Hledger/UI/UIState.hs b/Hledger/UI/UIState.hs
--- a/Hledger/UI/UIState.hs
+++ b/Hledger/UI/UIState.hs
@@ -1,27 +1,78 @@
 {- | UIState operations. -}
 
-{-# LANGUAGE NamedFieldPuns    #-}
-{-# LANGUAGE OverloadedStrings #-}
-
 module Hledger.UI.UIState
+(uiState
+,uiShowStatus
+,setFilter
+,setMode
+,setReportPeriod
+,showMinibuffer
+,closeMinibuffer
+,toggleCleared
+,toggleConversionOp
+,toggleIgnoreBalanceAssertions
+,toggleEmpty
+,toggleForecast
+,toggleHistorical
+,togglePending
+,toggleUnmarked
+,toggleReal
+,toggleTree
+,setTree
+,setList
+,toggleValue
+,reportPeriod
+,shrinkReportPeriod
+,growReportPeriod
+,nextReportPeriod
+,previousReportPeriod
+,resetReportPeriod
+,moveReportPeriodToDate
+,getDepth
+,setDepth
+,decDepth
+,incDepth
+,resetDepth
+,popScreen
+,pushScreen
+,enableForecastPreservingPeriod
+,resetFilter
+,resetScreens
+,regenerateScreens
+)
 where
 
 import Brick.Widgets.Edit
 import Data.Bifunctor (first)
 import Data.Foldable (asum)
 import Data.Either (fromRight)
-import Data.List ((\\), foldl', sort)
+import Data.List ((\\), sort)
 import Data.Maybe (fromMaybe)
 import Data.Semigroup (Max(..))
 import qualified Data.Text as T
 import Data.Text.Zipper (gotoEOL)
 import Data.Time.Calendar (Day)
 import Lens.Micro ((^.), over, set)
+import Safe
 
 import Hledger
 import Hledger.Cli.CliOptions
 import Hledger.UI.UITypes
+import Hledger.UI.UIOptions (UIOpts)
+import Hledger.UI.UIScreens (screenUpdate)
 
+-- | Make an initial UI state with the given options, journal,
+-- parent screen stack if any, and starting screen.
+uiState :: UIOpts -> Journal -> [Screen] -> Screen -> UIState
+uiState uopts j prevscrs scr = UIState {
+   astartupopts  = uopts
+  ,aopts         = uopts
+  ,ajournal      = j
+  ,aMode         = Normal
+  ,aScreen      = scr
+  ,aPrevScreens = prevscrs
+  }
+
 -- | Toggle between showing only unmarked items or all items.
 toggleUnmarked :: UIState -> UIState
 toggleUnmarked = over statuses (toggleStatus1 Unmarked)
@@ -66,7 +117,7 @@
 -- pressing Y after first or second step starts new cycle:
 -- [u] P [p]
 -- [pc] P [p]
--- toggleStatus2 s ss
+-- toggleStatus s ss
 --   | ss == [s]            = complement [s]
 --   | ss == complement [s] = []
 --   | otherwise            = [s]  -- XXX assume only three values
@@ -218,10 +269,10 @@
             . set empty__ True  -- set period PeriodAll
             . set rsQuery Any . set rsQueryOpts []
 
--- | Reset all options state to exactly what it was at startup
--- (preserving any command-line options/arguments).
-resetOpts :: UIState -> UIState
-resetOpts ui@UIState{astartupopts} = ui{aopts=astartupopts}
+-- -- | Reset all options state to exactly what it was at startup
+-- -- (preserving any command-line options/arguments).
+-- resetOpts :: UIState -> UIState
+-- resetOpts ui@UIState{astartupopts} = ui{aopts=astartupopts}
 
 resetDepth :: UIState -> UIState
 resetDepth = updateReportDepth (const Nothing)
@@ -278,22 +329,6 @@
 setMode :: Mode -> UIState -> UIState
 setMode m ui = ui{aMode=m}
 
--- | Regenerate the content for the current and previous screens, from a new journal and current date.
-regenerateScreens :: Journal -> Day -> UIState -> UIState
-regenerateScreens j d ui@UIState{aScreen=s,aPrevScreens=ss} =
-  -- XXX clumsy due to entanglement of UIState and Screen.
-  -- sInit operates only on an appstate's current screen, so
-  -- remove all the screens from the appstate and then add them back
-  -- one at a time, regenerating as we go.
-  let
-    frst:rest = reverse $ s:ss :: [Screen]
-    ui0 = ui{ajournal=j, aScreen=frst, aPrevScreens=[]} :: UIState
-
-    ui1 = (sInit frst) d False ui0 :: UIState
-    ui2 = foldl' (\ui' s' -> (sInit s') d False $ pushScreen s' ui') ui1 rest :: UIState
-  in
-    ui2
-
 pushScreen :: Screen -> UIState -> UIState
 pushScreen scr ui = ui{aPrevScreens=(aScreen ui:aPrevScreens ui)
                       ,aScreen=scr
@@ -303,18 +338,20 @@
 popScreen ui@UIState{aPrevScreens=s:ss} = ui{aScreen=s, aPrevScreens=ss}
 popScreen ui = ui
 
+-- | Reset options to their startup values, discard screen navigation history,
+-- and return to the top screen, regenerating it with the startup options 
+-- and the provided reporting date.
 resetScreens :: Day -> UIState -> UIState
-resetScreens d ui@UIState{aScreen=s,aPrevScreens=ss} =
-  (sInit topscreen) d True $
-  resetOpts $
-  closeMinibuffer ui{aScreen=topscreen, aPrevScreens=[]}
+resetScreens d ui@UIState{astartupopts=origopts, ajournal=j, aScreen=s,aPrevScreens=ss} =
+  ui{aopts=origopts, aPrevScreens=[], aScreen=topscreen', aMode=Normal}
   where
-    topscreen = case ss of _:_ -> last ss
-                           []  -> s
+    topscreen' = screenUpdate origopts d j $ lastDef s ss
 
--- | Enter a new screen, saving the old screen & state in the
--- navigation history and initialising the new screen's state.
-screenEnter :: Day -> Screen -> UIState -> UIState
-screenEnter d scr ui = (sInit scr) d True $
-                       pushScreen scr
-                       ui
+-- | Given a new journal and reporting date, save the new journal in the ui state,
+-- then regenerate the content of all screens in the stack
+-- (using the ui state's current options), preserving the screen navigation history.
+-- Note, does not save the reporting date.
+regenerateScreens :: Journal -> Day -> UIState -> UIState
+regenerateScreens j d ui@UIState{aopts=opts, aScreen=s,aPrevScreens=ss} =
+  ui{ajournal=j, aScreen=screenUpdate opts d j s, aPrevScreens=map (screenUpdate opts d j) ss}
+
diff --git a/Hledger/UI/UITypes.hs b/Hledger/UI/UITypes.hs
--- a/Hledger/UI/UITypes.hs
+++ b/Hledger/UI/UITypes.hs
@@ -36,6 +36,7 @@
 {-# LANGUAGE FlexibleInstances  #-}
 {-# LANGUAGE OverloadedStrings  #-}
 {-# LANGUAGE TemplateHaskell    #-}
+{-# LANGUAGE EmptyDataDeriving #-}
 
 module Hledger.UI.UITypes where
 
@@ -43,10 +44,9 @@
 -- import GHC.IO (unsafePerformIO)
 import Data.Text (Text)
 import Data.Time.Calendar (Day)
-import Brick
 import Brick.Widgets.List (List)
 import Brick.Widgets.Edit (Editor)
-import Lens.Micro.Platform
+import Lens.Micro.Platform (makeLenses)
 import Text.Show.Functions ()
   -- import the Show instance for functions. Warning, this also re-exports it
 
@@ -54,21 +54,29 @@
 import Hledger.Cli (HasCliOpts(..))
 import Hledger.UI.UIOptions
 
+data AppEvent =
+    FileChange          -- one of the Journal's files has been added/modified/removed
+  | DateChange Day Day  -- the current date has changed since last checked (with the old and new values)
+  deriving (Eq, Show)
+
 -- | hledger-ui's application state. This holds one or more stateful screens.
 -- As you navigate through screens, the old ones are saved in a stack.
 -- The app can be in one of several modes: normal screen operation,
 -- showing a help dialog, entering data in the minibuffer etc.
 data UIState = UIState {
-   astartupopts :: UIOpts    -- ^ the command-line options and query arguments specified at startup
-  ,aopts        :: UIOpts    -- ^ the command-line options and query arguments currently in effect
-  ,ajournal     :: Journal   -- ^ the journal being viewed
-  ,aPrevScreens :: [Screen]  -- ^ previously visited screens, most recent first
-  ,aScreen      :: Screen    -- ^ the currently active screen
-  ,aMode        :: Mode      -- ^ the currently active mode
+    -- unchanging:
+   astartupopts  :: UIOpts    -- ^ the command-line options and query arguments specified at program start
+    -- can change while program runs:
+  ,aopts         :: UIOpts    -- ^ the command-line options and query arguments currently in effect
+  ,ajournal      :: Journal   -- ^ the journal being viewed (can change with --watch)
+  ,aPrevScreens :: [Screen] -- ^ previously visited screens, most recent first (XXX silly, reverse these)
+  ,aScreen      :: Screen   -- ^ the currently active screen
+  ,aMode         :: Mode      -- ^ the currently active mode on the current screen
   } deriving (Show)
 
--- | The mode modifies the screen's rendering and event handling.
--- It resets to Normal when entering a new screen.
+-- | Any screen can be in one of several modes, which modifies 
+-- its rendering and event handling.
+-- The mode resets to Normal when entering a new screen.
 data Mode =
     Normal
   | Help
@@ -78,10 +86,11 @@
 -- Ignore the editor when comparing Modes.
 instance Eq (Editor l n) where _ == _ = True
 
--- Unique names required for widgets, viewports, cursor locations etc.
+-- Unique names required for brick widgets, viewports, cursor locations etc.
 data Name =
     HelpDialog
   | MinibufferEditor
+  | MenuList
   | AccountsViewport
   | AccountsList
   | RegisterViewport
@@ -89,61 +98,150 @@
   | TransactionEditor
   deriving (Ord, Show, Eq)
 
-data AppEvent =
-    FileChange          -- one of the Journal's files has been added/modified/removed
-  | DateChange Day Day  -- the current date has changed since last checked (with the old and new values)
-  deriving (Eq, Show)
+-- Unique names for screens the user can navigate to from the menu.
+data ScreenName =
+    Accounts
+  | Balancesheet
+  | Incomestatement
+  deriving (Ord, Show, Eq)
 
--- | hledger-ui screen types & instances.
+----------------------------------------------------------------------------------------------------
+-- | hledger-ui screen types, v1, "one screen = one module"
+-- These types aimed for maximum decoupling of modules and ease of adding more screens.
+-- A new screen requires
+-- 1. a new constructor in the Screen type, 
+-- 2. a new module implementing init/draw/handle functions, 
+-- 3. a call from any other screen which enters it.
 -- Each screen type has generically named initialisation, draw, and event handling functions,
 -- and zero or more uniquely named screen state fields, which hold the data for a particular
 -- instance of this screen. Note the latter create partial functions, which means that some invalid
 -- cases need to be handled, and also that their lenses are traversals, not single-value getters.
+-- data Screen =
+--     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 :: 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 "")
+--     }
+--   | RegisterScreen {
+--        sInit   :: Day -> Bool -> UIState -> UIState
+--       ,sDraw   :: UIState -> [Widget Name]
+--       ,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
+--       ,rsForceInclusive :: Bool                       -- ^ should this register always include subaccount transactions,
+--                                                       --   even when in flat mode ? (ie because entered from a
+--                                                       --   depth-clipped accounts screen item)
+--     }
+--   | TransactionScreen {
+--        sInit   :: Day -> Bool -> UIState -> UIState
+--       ,sDraw   :: UIState -> [Widget Name]
+--       ,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
+--       ,tsAccount      :: AccountName                  -- ^ the account whose register we entered this screen from
+--     }
+--   | ErrorScreen {
+--        sInit   :: Day -> Bool -> UIState -> UIState
+--       ,sDraw   :: UIState -> [Widget Name]
+--       ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
+--       --
+--       ,esError :: String                              -- ^ error message to show
+--     }
+--   deriving (Show)
+
+----------------------------------------------------------------------------------------------------
+-- | hledger-ui screen types, v2, "more parts, but simpler parts"
+-- These types aim to be more restrictive, allowing fewer invalid states, and easier to inspect
+-- and debug. The screen types store only state, not behaviour (functions), and there is no longer
+-- a circular dependency between UIState and Screen.
+-- A new screen requires
+-- 1. a new constructor in the Screen type
+-- 2. a new screen state type if needed
+-- 3. a new case in toAccountsLikeScreen if needed
+-- 4. new cases in the uiDraw and uiHandle functions
+-- 5. new constructor and updater functions in UIScreens, and a new case in screenUpdate
+-- 6. a new module implementing draw and event-handling functions
+-- 7. a call from any other screen which enters it (eg the menu screen, a new case in msEnterScreen)
+-- 8. if it appears on the main menu: a new menu item in msNew
+
+-- cf https://github.com/jtdaugherty/brick/issues/379#issuecomment-1192000374
+-- | The various screens which a user can navigate to in hledger-ui,
+-- along with any screen-specific parameters or data influencing what they display.
+-- (The separate state types add code noise but seem to reduce partial code/invalid data a bit.)
 data Screen =
-    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 :: 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 "")
-    }
-  | RegisterScreen {
-       sInit   :: Day -> Bool -> UIState -> UIState
-      ,sDraw   :: UIState -> [Widget Name]
-      ,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
-      ,rsForceInclusive :: Bool                       -- ^ should this register always include subaccount transactions,
-                                                      --   even when in flat mode ? (ie because entered from a
-                                                      --   depth-clipped accounts screen item)
-    }
-  | TransactionScreen {
-       sInit   :: Day -> Bool -> UIState -> UIState
-      ,sDraw   :: UIState -> [Widget Name]
-      ,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
-      ,tsAccount      :: AccountName                  -- ^ the account whose register we entered this screen from
-    }
-  | ErrorScreen {
-       sInit   :: Day -> Bool -> UIState -> UIState
-      ,sDraw   :: UIState -> [Widget Name]
-      ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
-      --
-      ,esError :: String                              -- ^ error message to show
-    }
+    MS MenuScreenState
+  | AS AccountsScreenState
+  | BS AccountsScreenState
+  | IS AccountsScreenState
+  | RS RegisterScreenState
+  | TS TransactionScreenState
+  | ES ErrorScreenState
   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"])
+-- | A subset of the screens which reuse the account screen's state and logic.
+-- Such Screens can be converted to and from this more restrictive type
+-- for cleaner code.
+data AccountsLikeScreen = ALS (AccountsScreenState -> Screen) AccountsScreenState
+  deriving (Show)
 
+toAccountsLikeScreen :: Screen -> Maybe AccountsLikeScreen
+toAccountsLikeScreen scr = case scr of
+  AS ass -> Just $ ALS AS ass
+  BS ass -> Just $ ALS BS ass
+  IS ass -> Just $ ALS IS ass
+  _      -> Nothing
+
+fromAccountsLikeScreen :: AccountsLikeScreen -> Screen
+fromAccountsLikeScreen (ALS scons ass) = scons ass
+
+data MenuScreenState = MSS {
+    -- view data:
+   _mssList            :: List Name MenuScreenItem  -- ^ list widget showing screen names
+  ,_mssUnused          :: ()                        -- ^ dummy field to silence warning
+} deriving (Show)
+
+-- Used for the accounts screen and similar screens.
+data AccountsScreenState = ASS {
+    -- screen parameters:
+   _assSelectedAccount :: AccountName                   -- ^ a copy of the account name from the list's selected item (or "")
+    -- view data derived from options, reporting date, journal, and screen parameters:
+  ,_assList            :: List Name AccountsScreenItem  -- ^ list widget showing account names & balances
+} deriving (Show)
+
+data RegisterScreenState = RSS {
+    -- screen parameters:
+   _rssAccount        :: AccountName                    -- ^ the account this register is for
+  ,_rssForceInclusive :: Bool                           -- ^ should this register always include subaccount transactions,
+                                                        --   even when in flat mode ? (ie because entered from a
+                                                        --   depth-clipped accounts screen item)
+    -- view data derived from options, reporting date, journal, and screen parameters:
+  ,_rssList           :: List Name RegisterScreenItem   -- ^ list widget showing transactions affecting this account
+} deriving (Show)
+
+data TransactionScreenState = TSS {
+    -- screen parameters:
+   _tssAccount      :: AccountName                  -- ^ the account whose register we entered this screen from
+  ,_tssTransactions :: [NumberedTransaction]        -- ^ the transactions in that register, which we can step through
+  ,_tssTransaction  :: NumberedTransaction          -- ^ the currently displayed transaction, and its position in the list
+} deriving (Show)
+
+data ErrorScreenState = ESS {
+    -- screen parameters:
+   _essError :: String                              -- ^ error message to show
+  ,_essUnused :: ()                                 -- ^ dummy field to silence warning
+} deriving (Show)
+
+-- | An item in the menu screen's list of screens.
+data MenuScreenItem = MenuScreenItem {
+   msItemScreenName :: Text                         -- ^ screen display name
+  ,msItemScreen     :: ScreenName                   -- ^ an internal name we can use to find the corresponding screen
+  } deriving (Show)
+
 -- | An item in the accounts screen's list of accounts and balances.
 data AccountsScreenItem = AccountsScreenItem {
    asItemIndentLevel        :: Int                -- ^ indent level
@@ -166,13 +264,28 @@
 
 type NumberedTransaction = (Integer, Transaction)
 
+-- These TH calls must come after most of the types above.
+-- Fields named _foo produce lenses named foo.
+-- XXX foo fields producing fooL lenses would be preferable
+makeLenses ''MenuScreenState
+makeLenses ''AccountsScreenState
+makeLenses ''RegisterScreenState
+makeLenses ''TransactionScreenState
+makeLenses ''ErrorScreenState
+
+----------------------------------------------------------------------------------------------------
+
+-- | 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"])
+
 -- dummy monoid instance needed make lenses work with List fields not common across constructors
 --instance Monoid (List n a)
 --  where
 --    mempty        = list "" V.empty 1  -- XXX problem in 0.7, every list requires a unique Name
---    mappend l1 l2 = l1 & listElementsL .~ (l1^.listElementsL <> l2^.listElementsL)
-
-makeLenses ''Screen
+--    mappend l1 l = l1 & listElementsL .~ (l1^.listElementsL <> l^.listElementsL)
 
 uioptslens f ui = (\x -> ui{aopts=x}) <$> f (aopts ui)
 
@@ -193,3 +306,4 @@
 
 instance HasReportOpts UIState where
     reportOpts = uioptslens.reportOpts
+
diff --git a/Hledger/UI/UIUtils.hs b/Hledger/UI/UIUtils.hs
--- a/Hledger/UI/UIUtils.hs
+++ b/Hledger/UI/UIUtils.hs
@@ -3,6 +3,7 @@
 {-# LANGUAGE CPP               #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE LambdaCase #-}
 
 module Hledger.UI.UIUtils (
    borderDepthStr
@@ -28,12 +29,19 @@
   ,modify'
   ,suspend
   ,redraw
+  ,reportSpecAddQuery
   ,reportSpecSetFutureAndForecast
   ,listScrollPushingSelection
-  ,dlogUiTrace
-  ,dlogUiTraceM
-  ,uiDebugLevel
+  ,dbgui
+  ,dbguiIO
+  ,dbguiEv
+  ,dbguiScreensEv
+  ,showScreenId
+  ,showScreenRegisterDescriptions
+  ,showScreenSelection
+  ,mapScreens
   ,uiNumBlankItems
+  ,showScreenStack
   )
 where
 
@@ -42,12 +50,12 @@
 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)
+import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL, listSelected, listMoveDown, listMoveUp, GenericList, listElements)
 import Control.Monad.IO.Class
 import Data.Bifunctor (second)
 import Data.List
 import qualified Data.Text as T
-import Data.Time (Day, addDays)
+import Data.Time (addDays)
 import Graphics.Vty
   (Event(..),Key(..),Modifier(..),Vty(..),Color,Attr,currentAttr,refresh, displayBounds
   -- ,Output(displayBounds,mkDisplayContext),DisplayContext(..)
@@ -55,38 +63,63 @@
 import Lens.Micro.Platform
 
 import Hledger
-import Hledger.Cli (CliOpts)
+-- import Hledger.Cli.CliOptions (CliOpts(reportspec_))
 import Hledger.Cli.DocFiles
+-- import Hledger.UI.UIOptions (UIOpts(uoCliOpts))
 import Hledger.UI.UITypes
-import Hledger.UI.UIState
 
+import Data.Vector (Vector)
+import qualified Data.Vector as V
+
 -- | On posix platforms, send the system STOP signal to suspend the
 -- current program. On windows, does nothing.
+-- (Though, currently hledger-ui is not built on windows.)
 #ifdef mingw32_HOST_OS
 suspendSignal :: IO ()
 suspendSignal = return ()
 #else
 import System.Posix.Signals
-import Data.Vector (Vector)
 suspendSignal :: IO ()
 suspendSignal = raiseSignal sigSTOP
 #endif
 
 -- Debug logging for UI state changes.
+-- A good place to log things of interest while debugging, see commented examples below.
 
 get' = do
-  x <- get
-  dlogUiTraceM $ "getting state: " ++ (head $ lines $ pshow $ aScreen x)
-  return x
+  ui <- get
+  dbguiEv $ "getting state: " ++ 
+    showScreenStack "" showScreenSelection ui
+    -- (head $ lines $ pshow $ aScreen x)
+    -- ++ " " ++ (show $ map tdescription $ jtxns $ ajournal x)
+  -- dbguiEv $ ("query: "++) $ pshow' $ x  & aopts & uoCliOpts & reportspec_ & _rsQuery
+  -- dbguiScreensEv "getting" showScreenId x
+  -- dbguiScreensEv "getting, with register descriptions" showScreenRegisterDescriptions x
+  return ui
 
-put' x = do
-  dlogUiTraceM $ "putting state: " ++ (head $ lines $ pshow $ aScreen x)
-  put x
+put' ui = do
+  dbguiEv $ "putting state: " ++
+    showScreenStack "" showScreenSelection ui
+    -- (head $ lines $ pshow $ aScreen x)
+    -- ++ " " ++ (show $ map tdescription $ jtxns $ ajournal x)
+  -- dbguiEv $ ("query: "++) $ pshow' $ x  & aopts & uoCliOpts & reportspec_ & _rsQuery
+  -- dbguiScreensEv "putting" showScreenId x
+  -- dbguiScreensEv "putting, with register descriptions" showScreenRegisterDescriptions x
+  put ui
 
 modify' f = do
-  x <- get
-  let x' = f x
-  dlogUiTraceM $ "modifying state: " ++ (head $ lines $ pshow $ aScreen x')
+  ui <- get
+  let ui' = f ui
+  dbguiEv $ "getting state: " ++ (showScreenStack "" showScreenSelection ui)
+  dbguiEv $ "putting state: " ++ (showScreenStack "" showScreenSelection ui')
+    -- (head $ lines $ pshow $ aScreen x')
+    -- ++ " " ++ (show $ map tdescription $ jtxns $ ajournal x')
+  -- dbguiEv $ ("from: "++) $ pshow' $ x  & aopts & uoCliOpts & reportspec_ & _rsQuery
+  -- dbguiEv $ ("to:   "++) $ pshow' $ x' & aopts & uoCliOpts & reportspec_ & _rsQuery
+  -- dbguiScreensEv "getting" showScreenId x
+  -- dbguiScreensEv "putting" showScreenId x'
+  -- dbguiScreensEv "getting, with register descriptions" showScreenRegisterDescriptions x
+  -- dbguiScreensEv "putting, with register descriptions" showScreenRegisterDescriptions x'
   modify f
 
 -- | On posix platforms, suspend the program using the STOP signal,
@@ -105,18 +138,18 @@
 defaultLayout toplabel bottomlabel =
   topBottomBorderWithLabels (str " "<+>toplabel<+>str " ") (str " "<+>bottomlabel<+>str " ") .
   margin 1 0 Nothing
-  -- topBottomBorderWithLabel2 label .
+  -- topBottomBorderWithLabel label .
   -- padLeftRight 1 -- XXX should reduce inner widget's width by 2, but doesn't
                     -- "the layout adjusts... if you use the core combinators"
 
 -- | Draw the help dialog, called when help mode is active.
-helpDialog :: CliOpts -> Widget Name
-helpDialog _copts =
+helpDialog :: Widget Name
+helpDialog =
   Widget Fixed Fixed $ do
     c <- getContext
     render $
       withDefAttr (attrName "help") $
-      renderDialog (dialog (Just "Help (LEFT/ESC/?/q to close help)") Nothing (c^.availWidthL)) $ -- (Just (0,[("ok",())]))
+      renderDialog (dialog (Just $ str "Help (LEFT/ESC/?/q to close help)") Nothing (c^.availWidthL)) $ -- (Just (0,[("ok",())]))
       padTop (Pad 0) $ padLeft (Pad 1) $ padRight (Pad 1) $
         vBox [
            hBox [
@@ -192,7 +225,7 @@
 helpHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()
 helpHandle ev = do
   ui <- get
-  let ui' = setMode Normal ui
+  let ui' = ui{aMode=Normal}
   case ev of
     VtyEvent e | e `elem` closeHelpEvents -> put' ui'
     VtyEvent (EvKey (KChar 'p') []) -> suspendAndResume (runPagerForTopic "hledger-ui" Nothing >> return ui')
@@ -250,7 +283,8 @@
 
 -- temporary shenanigans:
 
--- | Convert the special account name "*" (from balance report with depth limit 0) to something clearer.
+-- | Replace the special account names "*" and "..." (from balance reports with depth limit 0)
+-- to something clearer.
 replaceHiddenAccountsNameWith :: AccountName -> AccountName -> AccountName
 replaceHiddenAccountsNameWith anew a | a == hiddenAccountsName = anew
                                      | a == "*"                = anew
@@ -295,8 +329,8 @@
       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
---_topBottomBorderWithLabel2 label = \wrapped ->
+--_topBottomBorderWithLabel :: Widget Name -> Widget Name -> Widget Name
+--_topBottomBorderWithLabel label = \wrapped ->
 -- let debugmsg = ""
 -- in hBorderWithLabel (label <+> str debugmsg)
 --    <=>
@@ -309,7 +343,7 @@
 -- thickness, using the current background colour or the specified
 -- colour.
 -- XXX May disrupt border style of inner widgets.
--- XXX Should reduce the available size visible to inner widget, but doesn't seem to (cf rsDraw2).
+-- XXX Should reduce the available size visible to inner widget, but doesn't seem to (cf rsDraw).
 margin :: Int -> Int -> Maybe Color -> Widget Name -> Widget Name
 margin h v mcolour w = Widget Greedy Greedy $ do
     ctx <- getContext
@@ -358,11 +392,11 @@
         toprow       = dbg4 "toprow" $ max 0 (selectedrow - (itemsperpage `div` 2)) -- assuming ViewportScroll's row offset is measured in list items not screen rows
       setTop (viewportScroll $ list^.listNameL) toprow
 
---                 arrow keys       vi keys               emacs keys
+--                 arrow keys       vi keys               emacs keys                 enter key
 moveUpEvents    = [EvKey KUp []   , EvKey (KChar 'k') [], EvKey (KChar 'p') [MCtrl]]
 moveDownEvents  = [EvKey KDown [] , EvKey (KChar 'j') [], EvKey (KChar 'n') [MCtrl]]
 moveLeftEvents  = [EvKey KLeft [] , EvKey (KChar 'h') [], EvKey (KChar 'b') [MCtrl]]
-moveRightEvents = [EvKey KRight [], EvKey (KChar 'l') [], EvKey (KChar 'f') [MCtrl]]
+moveRightEvents = [EvKey KRight [], EvKey (KChar 'l') [], EvKey (KChar 'f') [MCtrl], EvKey KEnter []]
 
 normaliseMovementKeys ev
   | ev `elem` moveUpEvents    = EvKey KUp []
@@ -371,11 +405,16 @@
   | ev `elem` moveRightEvents = EvKey KRight []
   | otherwise = ev
 
--- | Update the ReportSpec's query to exclude future transactions (later than the given day)
+-- | Restrict the ReportSpec's query by adding the given additional query.
+reportSpecAddQuery :: Query -> ReportSpec -> ReportSpec
+reportSpecAddQuery q rspec =
+    rspec{_rsQuery=simplifyQuery $ And [_rsQuery rspec, q]}
+
+-- | Update the ReportSpec's query to exclude future transactions (later than its "today" date)
 -- 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 fcast rspec =
+reportSpecSetFutureAndForecast :: Maybe DateSpan -> ReportSpec -> ReportSpec
+reportSpecSetFutureAndForecast fcast rspec =
     rspec{_rsQuery=simplifyQuery $ And [_rsQuery rspec, periodq, excludeforecastq fcast]}
   where
     periodq = Date . periodAsDateSpan . period_ $ _rsReportOpts rspec
@@ -383,7 +422,7 @@
     excludeforecastq (Just _) = Any
     excludeforecastq Nothing  =  -- not:date:tomorrow- not:tag:generated-transaction
       And [
-         Not (Date $ DateSpan (Just $ addDays 1 d) Nothing)
+         Not (Date $ DateSpan (Just $ addDays 1 $ _rsDay rspec) Nothing)
         ,Not generatedTransactionTag
       ]
 
@@ -408,19 +447,80 @@
         _ -> 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.
+-- | A debug logging helper for hledger-ui code: at any debug level >= 1,
+-- logs the string to hledger-ui.log before returning the second argument.
 -- Uses unsafePerformIO.
-dlogUiTrace :: String -> a -> a
-dlogUiTrace = dlogTraceAt uiDebugLevel
+dbgui :: String -> a -> a
+dbgui = traceLogAt 1
 
--- | Like dlogUiTrace, but within the hledger-ui brick event handler monad.
-dlogUiTraceM :: String -> EventM Name UIState ()
-dlogUiTraceM s = dlogUiTrace s $ return ()
+-- | Like dbgui, but convenient to use in IO.
+dbguiIO :: String -> IO ()
+dbguiIO = traceLogAtIO 1
 
--- | Log hledger-ui events at this debug level.
-uiDebugLevel :: Int
-uiDebugLevel = 2
+-- | Like dbgui, but convenient to use in EventM handlers.
+dbguiEv :: String -> EventM Name s ()
+dbguiEv s = dbgui s $ return ()
+
+-- | Like dbguiEv, but log a compact view of the current screen stack.
+-- See showScreenStack.
+-- To just log the stack: @dbguiScreensEv "" showScreenId ui@
+dbguiScreensEv :: String -> (Screen -> String) -> UIState -> EventM Name UIState ()
+dbguiScreensEv postfix showscr ui = dbguiEv $ showScreenStack postfix showscr ui
+
+-- Render a compact labelled view of the current screen stack,
+-- adding the given postfix to the label (can be empty),
+-- from the topmost screen to the currently-viewed screen,
+-- with each screen rendered by the given rendering function.
+-- Useful for inspecting states across the whole screen stack.
+-- Some screen rendering functions are 
+-- @showScreenId@, @showScreenSelection@, @showScreenRegisterDescriptions@.
+--
+-- Eg to just show the stack: @showScreenStack "" showScreenId ui@
+--
+-- To to show the stack plus selected item indexes: @showScreenStack "" showScreenSelection ui@
+--
+showScreenStack :: String -> (Screen -> String) -> UIState -> String
+showScreenStack postfix showscr ui = concat [
+    "screen stack"
+  ,if null postfix then "" else ", " ++ postfix
+  ,": "
+  ,unwords $ mapScreens showscr ui
+  ]
+
+-- | Run a function on each screen in a UIState's screen "stack",
+-- from topmost screen down to currently-viewed screen.
+mapScreens :: (Screen -> a) -> UIState -> [a]
+mapScreens f UIState{aPrevScreens, aScreen} = map f $ reverse $ aScreen : aPrevScreens
+
+-- Show a screen's compact id (first letter of its constructor).
+showScreenId :: Screen -> String
+showScreenId = \case
+  MS _ -> "M"  -- menu
+  AS _ -> "A"  -- all accounts
+  BS _ -> "B"  -- bs accounts
+  IS _ -> "I"  -- is accounts
+  RS _ -> "R"  -- menu
+  TS _ -> "T"  -- transaction
+  ES _ -> "E"  -- error
+
+-- Show a screen's compact id, plus for register screens, the transaction descriptions.
+showScreenRegisterDescriptions :: Screen -> String
+showScreenRegisterDescriptions scr = case scr of
+  RS sst -> ((showScreenId scr ++ ":") ++) $ -- menu
+    intercalate "," $ map (T.unpack . rsItemDescription) $
+    takeWhile (not . T.null . rsItemDate) $ V.toList $ listElements $ _rssList sst
+  _ -> showScreenId scr
+
+-- Show a screen's compact id, plus index of its selected list item if any.
+showScreenSelection :: Screen -> String
+showScreenSelection = \case
+  MS MSS{_mssList} -> "M" ++ (maybe "" show $ listSelected _mssList)  -- menu
+  AS ASS{_assList} -> "A" ++ (maybe "" show $ listSelected _assList)  -- all accounts
+  BS ASS{_assList} -> "B" ++ (maybe "" show $ listSelected _assList)  -- bs accounts
+  IS ASS{_assList} -> "I" ++ (maybe "" show $ listSelected _assList)  -- is accounts
+  RS RSS{_rssList} -> "R" ++ (maybe "" show $ listSelected _rssList)  -- menu
+  TS _ -> "T"  -- transaction
+  ES _ -> "E"  -- error
 
 -- | How many blank items to add to lists to fill the full window height.
 uiNumBlankItems :: Int
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" "September 2022" "hledger-ui-1.27 " "hledger User Manuals"
+.TH "HLEDGER-UI" "1" "December 2022" "hledger-ui-1.28 " "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.27.
+This manual is for hledger-ui 1.28.
 .SH SYNOPSIS
 .PP
 \f[C]hledger-ui [OPTIONS] [QUERYARGS]\f[R]
@@ -53,6 +53,18 @@
 \f[B]\f[CB]--theme=default|terminal|greenterm\f[B]\f[R]
 use this custom display theme
 .TP
+\f[B]\f[CB]--menu\f[B]\f[R]
+start in the menu screen
+.TP
+\f[B]\f[CB]--all\f[B]\f[R]
+start in the all accounts screen
+.TP
+\f[B]\f[CB]--bs\f[B]\f[R]
+start in the balance sheet accounts screen
+.TP
+\f[B]\f[CB]--is\f[B]\f[R]
+start in the income statement accounts screen
+.TP
 \f[B]\f[CB]--register=ACCTREGEX\f[B]\f[R]
 start in the (first) matched account\[aq]s register screen
 .TP
@@ -228,8 +240,7 @@
 .IP \[bu] 2
 Click on list items to go deeper
 .IP \[bu] 2
-Click on the left margin (column 0), or the blank area at bottom of
-screen, to go back.
+Click on the left margin (column 0) to go back.
 .SH KEYS
 .PP
 Keyboard gives more control.
@@ -240,14 +251,14 @@
 \f[C]LEFT\f[R], or \f[C]q\f[R]) to close it.
 The following keys work on most screens:
 .PP
-The cursor keys navigate: \f[C]RIGHT\f[R] goes deeper, \f[C]LEFT\f[R]
-returns to the previous screen,
+The cursor keys navigate: \f[C]RIGHT\f[R] or \f[C]ENTER\f[R] goes
+deeper, \f[C]LEFT\f[R] returns to the previous screen,
 \f[C]UP\f[R]/\f[C]DOWN\f[R]/\f[C]PGUP\f[R]/\f[C]PGDN\f[R]/\f[C]HOME\f[R]/\f[C]END\f[R]
 move up and down through lists.
 Emacs-style
 (\f[C]CTRL-p\f[R]/\f[C]CTRL-n\f[R]/\f[C]CTRL-f\f[R]/\f[C]CTRL-b\f[R])
-movement keys are also supported (but not vi-style keys, since
-hledger-1.19, sorry!).
+and VI-style (\f[C]k\f[R],\f[C]j\f[R],\f[C]l\f[R],\f[C]h\f[R]) movement
+keys are also supported.
 A tip: movement speed is limited by your keyboard repeat rate, to move
 faster you may want to adjust it.
 (If you\[aq]re on a mac, the karabiner app is one way to do that.)
@@ -335,18 +346,57 @@
 .PP
 Additional screen-specific keys are described below.
 .SH SCREENS
-.SS Accounts screen
 .PP
-This is normally the first screen displayed.
-It lists accounts and their balances, like hledger\[aq]s balance
-command.
-By default, it shows all accounts and their latest ending balances
-(including the balances of subaccounts).
-Accounts which have been declared with an account directive are also
-listed, even if not yet used (except for empty parent accounts).
-If you specify a query on the command line, it shows just the matched
-accounts and the balances from matched transactions.
+hledger-ui shows several different screens, described below.
+It shows the \[dq]Balance sheet accounts\[dq] screen to start with,
+except in the following situations:
+.IP \[bu] 2
+If no asset/liability/equity accounts can be detected, or if an account
+query has been given on the command line, it starts in the \[dq]All
+accounts\[dq] screen.
+.IP \[bu] 2
+If a starting screen is specified with --menu/--all/--bs/--is/--register
+on the command line, it starts in that screen.
 .PP
+From any screen you can press \f[C]LEFT\f[R] or \f[C]ESC\f[R] to
+navigate back to the top level \[dq]Menu\[dq] screen.
+.SS Menu
+.PP
+The top-most screen.
+From here you can navigate to three accounts screens:
+.SS All accounts
+.PP
+This screen shows all accounts (possibly filtered by a query), and their
+end balances on the date shown in the title bar (or their balance
+changes in the period shown in the title bar, toggleable with
+\f[C]H\f[R]).
+It is like the \f[C]hledger balance\f[R] command.
+.SS Balance sheet accounts
+.PP
+This screen shows asset, liability and equity accounts, if these can be
+detected (see account types).
+It always shows end balances.
+It is like the \f[C]hledger balancesheetequity\f[R] command.
+.SS Income statement accounts
+.PP
+This screen shows revenue and expense accounts.
+It always shows balance changes.
+It is like the \f[C]hledger incomestatement\f[R] command.
+.PP
+All of these accounts screens work in much the same way:
+.PP
+They show accounts which have been posted to by transactions, as well as
+accounts which have been declared with an account directive (except for
+empty parent accounts).
+.PP
+If you specify a query on the command line or with \f[C]/\f[R] in the
+app, they show just the matched accounts, and the balances from matched
+transactions.
+.PP
+hledger-ui shows accounts with zero balances by default (unlike
+command-line hledger).
+To hide these, press \f[C]z\f[R] to toggle nonzero mode.
+.PP
 Account names are shown as a flat list by default; press \f[C]t\f[R] to
 toggle tree mode.
 In list mode, account balances are exclusive of subaccounts, except
@@ -363,7 +413,7 @@
 or press \f[C]ESCAPE\f[R].
 .PP
 \f[C]H\f[R] toggles between showing historical balances or period
-balances.
+balances (on the \[dq]All accounts\[dq] screen).
 Historical balances (the default) are ending balances at the end of the
 report period, taking into account all transactions before that date
 (filtered by the filter query if any), including transactions before the
@@ -384,12 +434,9 @@
 .PP
 \f[C]R\f[R] toggles real mode, in which virtual postings are ignored.
 .PP
-\f[C]z\f[R] toggles nonzero mode, in which only accounts with nonzero
-balances are shown (hledger-ui shows zero items by default, unlike
-command-line hledger).
-.PP
-Press \f[C]RIGHT\f[R] to view an account\[aq]s transactions register.
-.SS Register screen
+Press \f[C]RIGHT\f[R] to view an account\[aq]s register screen, Or,
+\f[C]LEFT\f[R] to see the menu screen.
+.SS Register
 .PP
 This screen shows the transactions affecting a particular account, like
 a check register.
@@ -435,7 +482,7 @@
 command-line hledger).
 .PP
 Press \f[C]RIGHT\f[R] to view the selected transaction in detail.
-.SS Transaction screen
+.SS Transaction
 .PP
 This screen shows a single transaction, as a general journal entry,
 similar to hledger\[aq]s print command and journal format
@@ -455,7 +502,7 @@
 The #N number preceding them is the transaction\[aq]s position within
 the complete unfiltered journal, which is a more stable id (at least
 until the next reload).
-.SS Error screen
+.SS Error
 .PP
 This screen will appear if there is a problem, such as a parse error,
 when you press g to reload.
@@ -486,34 +533,31 @@
 This leaves more mental bandwidth for your accounting.
 Of course you can still interact with hledger-ui when needed, eg to
 toggle cleared mode, or to explore the history.
-.SS Watch mode limitations
 .PP
-There are situations in which it won\[aq]t work, ie the display will not
-update when you save a change (because the underlying \f[C]inotify\f[R]
-library does not support it).
-Here are some that we know of:
-.IP \[bu] 2
-Certain editors: saving with \f[C]gedit\f[R], and perhaps any Gnome
-application, won\[aq]t be detected (#1617).
-Jetbrains IDEs, such as IDEA, also may not work (#911).
-.IP \[bu] 2
-Certain unusual filesystems might not be supported.
-(All the usual ones on unix, mac and windows are supported.)
+Here are some current limitations to be aware of:
 .PP
-In such cases, the workaround is to switch to the hledger-ui window and
-press \f[C]g\f[R] each time you want it to reload.
-(Actually, see #1617 for another workaround, and let us know if it works
-for you.)
+Changes might not be detected with certain editors, possibly including
+Jetbrains IDEs, \f[C]gedit\f[R], other Gnome applications; or on certain
+unusual filesystems.
+(#1617, #911).
+To work around, reload manually by pressing \f[C]g\f[R] in the
+hledger-ui window.
+(Or see #1617 for another workaround, and let us know if it works for
+you.)
 .PP
-If you leave \f[C]hledger-ui --watch\f[R] running for days, on certain
-platforms (?), perhaps with many transactions in your journal (?),
-perhaps with large numbers of other files present (?), you may see it
-gradually using more and more memory and CPU over time, as seen in
-\f[C]top\f[R] or Activity Monitor or Task Manager.
+CPU and memory usage can sometimes gradually increase, if
+\f[C]hledger-ui --watch\f[R] is left running for days.
+(Possibly correlated with certain platforms, many transactions, and/or
+large numbers of other files present).
+To work around, \f[C]q\f[R]uit and restart it, or (where supported)
+suspend (\f[C]CTRL-z\f[R]) and restart it (\f[C]fg\f[R]).
+.SS Debug output
 .PP
-A workaround is to \f[C]q\f[R]uit and restart it, or to suspend it
-(\f[C]CTRL-z\f[R]) and restart it (\f[C]fg\f[R]) if your shell supports
-that.
+You can add \f[C]--debug[=N]\f[R] to the command line to log debug
+output.
+This will be logged to the file \f[C]hledger-ui.log\f[R] in the current
+directory.
+N ranges from 1 (least output, the default) to 9 (maximum output).
 .SH ENVIRONMENT
 .PP
 \f[B]COLUMNS\f[R] The screen width to use.
diff --git a/hledger-ui.cabal b/hledger-ui.cabal
--- a/hledger-ui.cabal
+++ b/hledger-ui.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.34.4.
+-- This file has been generated from package.yaml by hpack version 0.35.0.
 --
 -- see: https://github.com/sol/hpack
 
 name:           hledger-ui
-version:        1.27.1
+version:        1.28
 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.
@@ -49,13 +49,17 @@
   other-modules:
       Hledger.UI
       Hledger.UI.AccountsScreen
+      Hledger.UI.BalancesheetScreen
       Hledger.UI.Editor
       Hledger.UI.ErrorScreen
+      Hledger.UI.IncomestatementScreen
       Hledger.UI.Main
+      Hledger.UI.MenuScreen
       Hledger.UI.RegisterScreen
       Hledger.UI.Theme
       Hledger.UI.TransactionScreen
       Hledger.UI.UIOptions
+      Hledger.UI.UIScreens
       Hledger.UI.UIState
       Hledger.UI.UITypes
       Hledger.UI.UIUtils
@@ -63,13 +67,12 @@
   hs-source-dirs:
       ./
   ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind
-  cpp-options: -DVERSION="1.27.1"
+  cpp-options: -DVERSION="1.28"
   build-depends:
       ansi-terminal >=0.9
     , async
-    , base >=4.14 && <4.17
-    , breakpoint
-    , brick >=1.0
+    , base >=4.14 && <4.18
+    , brick >=1.5
     , cmdargs >=0.8
     , containers >=0.5.9
     , data-default
@@ -77,10 +80,10 @@
     , doclayout >=0.3 && <0.5
     , extra >=1.6.3
     , filepath
-    , fsnotify >=0.2.1.2 && <0.4
-    , hledger >=1.27.1 && <1.28
-    , hledger-lib >=1.27.1 && <1.28
-    , megaparsec >=7.0.0 && <9.3
+    , fsnotify ==0.4.*
+    , hledger ==1.28.*
+    , hledger-lib ==1.28.*
+    , megaparsec >=7.0.0 && <9.4
     , microlens >=0.4
     , microlens-platform >=0.2.3.1
     , mtl >=2.2.1
@@ -94,10 +97,10 @@
     , unix
     , vector
     , vty >=5.15
+  default-language: Haskell2010
   if os(windows)
     buildable: False
   else
     buildable: True
   if flag(threaded)
     ghc-options: -threaded
-  default-language: Haskell2010
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.27.
+tool.  This manual is for hledger-ui 1.28.
 
    'hledger-ui [OPTIONS] [QUERYARGS]'
 'hledger ui -- [OPTIONS] [QUERYARGS]'
@@ -68,6 +68,18 @@
 '--theme=default|terminal|greenterm'
 
      use this custom display theme
+'--menu'
+
+     start in the menu screen
+'--all'
+
+     start in the all accounts screen
+'--bs'
+
+     start in the balance sheet accounts screen
+'--is'
+
+     start in the income statement accounts screen
 '--register=ACCTREGEX'
 
      start in the (first) matched account's register screen
@@ -250,8 +262,7 @@
 
    * Use mouse wheel or trackpad to scroll up and down
    * Click on list items to go deeper
-   * Click on the left margin (column 0), or the blank area at bottom of
-     screen, to go back.
+   * Click on the left margin (column 0) to go back.
 
 
 File: hledger-ui.info,  Node: KEYS,  Next: SCREENS,  Prev: MOUSE,  Up: Top
@@ -266,13 +277,13 @@
 'ESCAPE', or 'LEFT', or 'q') to close it.  The following keys work on
 most screens:
 
-   The cursor keys navigate: 'RIGHT' goes deeper, 'LEFT' returns to the
-previous screen, 'UP'/'DOWN'/'PGUP'/'PGDN'/'HOME'/'END' move up and down
-through lists.  Emacs-style ('CTRL-p'/'CTRL-n'/'CTRL-f'/'CTRL-b')
-movement keys are also supported (but not vi-style keys, since
-hledger-1.19, sorry!).  A tip: movement speed is limited by your
-keyboard repeat rate, to move faster you may want to adjust it.  (If
-you're on a mac, the karabiner app is one way to do that.)
+   The cursor keys navigate: 'RIGHT' or 'ENTER' goes deeper, 'LEFT'
+returns to the previous screen, 'UP'/'DOWN'/'PGUP'/'PGDN'/'HOME'/'END'
+move up and down through lists.  Emacs-style
+('CTRL-p'/'CTRL-n'/'CTRL-f'/'CTRL-b') and VI-style ('k','j','l','h')
+movement keys are also supported.  A tip: movement speed is limited by
+your keyboard repeat rate, to move faster you may want to adjust it.
+(If you're on a mac, the karabiner app is one way to do that.)
 
    With shift pressed, the cursor keys adjust the report period,
 limiting the transactions to be shown (by default, all are shown).
@@ -352,27 +363,82 @@
 4 SCREENS
 *********
 
+hledger-ui shows several different screens, described below.  It shows
+the "Balance sheet accounts" screen to start with, except in the
+following situations:
+
+   * If no asset/liability/equity accounts can be detected, or if an
+     account query has been given on the command line, it starts in the
+     "All accounts" screen.
+
+   * If a starting screen is specified with -menu/-all/-bs/-is/-register
+     on the command line, it starts in that screen.
+
+   From any screen you can press 'LEFT' or 'ESC' to navigate back to the
+top level "Menu" screen.
+
 * Menu:
 
-* Accounts screen::
-* Register screen::
-* Transaction screen::
-* Error screen::
+* Menu::
+* All accounts::
+* Balance sheet accounts::
+* Income statement accounts::
+* Register::
+* Transaction::
+* Error::
 
 
-File: hledger-ui.info,  Node: Accounts screen,  Next: Register screen,  Up: SCREENS
+File: hledger-ui.info,  Node: Menu,  Next: All accounts,  Up: SCREENS
 
-4.1 Accounts screen
-===================
+4.1 Menu
+========
 
-This is normally the first screen displayed.  It lists accounts and
-their balances, like hledger's balance command.  By default, it shows
-all accounts and their latest ending balances (including the balances of
-subaccounts).  Accounts which have been declared with an account
-directive are also listed, even if not yet used (except for empty parent
-accounts).  If you specify a query on the command line, it shows just
-the matched accounts and the balances from matched transactions.
+The top-most screen.  From here you can navigate to three accounts
+screens:
 
+
+File: hledger-ui.info,  Node: All accounts,  Next: Balance sheet accounts,  Prev: Menu,  Up: SCREENS
+
+4.2 All accounts
+================
+
+This screen shows all accounts (possibly filtered by a query), and their
+end balances on the date shown in the title bar (or their balance
+changes in the period shown in the title bar, toggleable with 'H').  It
+is like the 'hledger balance' command.
+
+
+File: hledger-ui.info,  Node: Balance sheet accounts,  Next: Income statement accounts,  Prev: All accounts,  Up: SCREENS
+
+4.3 Balance sheet accounts
+==========================
+
+This screen shows asset, liability and equity accounts, if these can be
+detected (see account types).  It always shows end balances.  It is like
+the 'hledger balancesheetequity' command.
+
+
+File: hledger-ui.info,  Node: Income statement accounts,  Next: Register,  Prev: Balance sheet accounts,  Up: SCREENS
+
+4.4 Income statement accounts
+=============================
+
+This screen shows revenue and expense accounts.  It always shows balance
+changes.  It is like the 'hledger incomestatement' command.
+
+   All of these accounts screens work in much the same way:
+
+   They show accounts which have been posted to by transactions, as well
+as accounts which have been declared with an account directive (except
+for empty parent accounts).
+
+   If you specify a query on the command line or with '/' in the app,
+they show just the matched accounts, and the balances from matched
+transactions.
+
+   hledger-ui shows accounts with zero balances by default (unlike
+command-line hledger).  To hide these, press 'z' to toggle nonzero mode.
+
    Account names are shown as a flat list by default; press 't' to
 toggle tree mode.  In list mode, account balances are exclusive of
 subaccounts, except where subaccounts are hidden by a depth limit (see
@@ -385,15 +451,16 @@
 To remove the depth limit, set it higher than the maximum account depth,
 or press 'ESCAPE'.
 
-   'H' toggles between showing historical balances or period balances.
-Historical balances (the default) are ending balances at the end of the
-report period, taking into account all transactions before that date
-(filtered by the filter query if any), including transactions before the
-start of the report period.  In other words, historical balances are
-what you would see on a bank statement for that account (unless
-disturbed by a filter query).  Period balances ignore transactions
-before the report start date, so they show the change in balance during
-the report period.  They are more useful eg when viewing a time log.
+   'H' toggles between showing historical balances or period balances
+(on the "All accounts" screen).  Historical balances (the default) are
+ending balances at the end of the report period, taking into account all
+transactions before that date (filtered by the filter query if any),
+including transactions before the start of the report period.  In other
+words, historical balances are what you would see on a bank statement
+for that account (unless disturbed by a filter query).  Period balances
+ignore transactions before the report start date, so they show the
+change in balance during the report period.  They are more useful eg
+when viewing a time log.
 
    'U' toggles filtering by unmarked status, including or excluding
 unmarked postings in the balances.  Similarly, 'P' toggles pending
@@ -404,17 +471,14 @@
 
    'R' toggles real mode, in which virtual postings are ignored.
 
-   'z' toggles nonzero mode, in which only accounts with nonzero
-balances are shown (hledger-ui shows zero items by default, unlike
-command-line hledger).
-
-   Press 'RIGHT' to view an account's transactions register.
+   Press 'RIGHT' to view an account's register screen, Or, 'LEFT' to see
+the menu screen.
 
 
-File: hledger-ui.info,  Node: Register screen,  Next: Transaction screen,  Prev: Accounts screen,  Up: SCREENS
+File: hledger-ui.info,  Node: Register,  Next: Transaction,  Prev: Income statement accounts,  Up: SCREENS
 
-4.2 Register screen
-===================
+4.5 Register
+============
 
 This screen shows the transactions affecting a particular account, like
 a check register.  Each line represents one transaction and shows:
@@ -457,10 +521,10 @@
    Press 'RIGHT' to view the selected transaction in detail.
 
 
-File: hledger-ui.info,  Node: Transaction screen,  Next: Error screen,  Prev: Register screen,  Up: SCREENS
+File: hledger-ui.info,  Node: Transaction,  Next: Error,  Prev: Register,  Up: SCREENS
 
-4.3 Transaction screen
-======================
+4.6 Transaction
+===============
 
 This screen shows a single transaction, as a general journal entry,
 similar to hledger's print command and journal format
@@ -481,10 +545,10 @@
 reload).
 
 
-File: hledger-ui.info,  Node: Error screen,  Prev: Transaction screen,  Up: SCREENS
+File: hledger-ui.info,  Node: Error,  Prev: Transaction,  Up: SCREENS
 
-4.4 Error screen
-================
+4.7 Error
+=========
 
 This screen will appear if there is a problem, such as a parse error,
 when you press g to reload.  Once you have fixed the problem, press g
@@ -500,10 +564,10 @@
 * Menu:
 
 * Watch mode::
-* Watch mode limitations::
+* Debug output::
 
 
-File: hledger-ui.info,  Node: Watch mode,  Next: Watch mode limitations,  Up: TIPS
+File: hledger-ui.info,  Node: Watch mode,  Next: Debug output,  Up: TIPS
 
 5.1 Watch mode
 ==============
@@ -525,35 +589,29 @@
 hledger-ui when needed, eg to toggle cleared mode, or to explore the
 history.
 
-
-File: hledger-ui.info,  Node: Watch mode limitations,  Prev: Watch mode,  Up: TIPS
-
-5.2 Watch mode limitations
-==========================
-
-There are situations in which it won't work, ie the display will not
-update when you save a change (because the underlying 'inotify' library
-does not support it).  Here are some that we know of:
+   Here are some current limitations to be aware of:
 
-   * Certain editors: saving with 'gedit', and perhaps any Gnome
-     application, won't be detected (#1617).  Jetbrains IDEs, such as
-     IDEA, also may not work (#911).
+   Changes might not be detected with certain editors, possibly
+including Jetbrains IDEs, 'gedit', other Gnome applications; or on
+certain unusual filesystems.  (#1617, #911).  To work around, reload
+manually by pressing 'g' in the hledger-ui window.  (Or see #1617 for
+another workaround, and let us know if it works for you.)
 
-   * Certain unusual filesystems might not be supported.  (All the usual
-     ones on unix, mac and windows are supported.)
+   CPU and memory usage can sometimes gradually increase, if 'hledger-ui
+--watch' is left running for days.  (Possibly correlated with certain
+platforms, many transactions, and/or large numbers of other files
+present).  To work around, 'q'uit and restart it, or (where supported)
+suspend ('CTRL-z') and restart it ('fg').
 
-   In such cases, the workaround is to switch to the hledger-ui window
-and press 'g' each time you want it to reload.  (Actually, see #1617 for
-another workaround, and let us know if it works for you.)
+
+File: hledger-ui.info,  Node: Debug output,  Prev: Watch mode,  Up: TIPS
 
-   If you leave 'hledger-ui --watch' running for days, on certain
-platforms (?), perhaps with many transactions in your journal (?),
-perhaps with large numbers of other files present (?), you may see it
-gradually using more and more memory and CPU over time, as seen in 'top'
-or Activity Monitor or Task Manager.
+5.2 Debug output
+================
 
-   A workaround is to 'q'uit and restart it, or to suspend it ('CTRL-z')
-and restart it ('fg') if your shell supports that.
+You can add '--debug[=N]' to the command line to log debug output.  This
+will be logged to the file 'hledger-ui.log' in the current directory.  N
+ranges from 1 (least output, the default) to 9 (maximum output).
 
 
 File: hledger-ui.info,  Node: ENVIRONMENT,  Next: FILES,  Prev: TIPS,  Up: Top
@@ -642,32 +700,38 @@
 Node: Top221
 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
+Node: MOUSE6836
+Ref: #mouse6931
+Node: KEYS7168
+Ref: #keys7261
+Node: SCREENS11336
+Ref: #screens11434
+Node: Menu12120
+Ref: #menu12212
+Node: All accounts12289
+Ref: #all-accounts12428
+Node: Balance sheet accounts12679
+Ref: #balance-sheet-accounts12859
+Node: Income statement accounts13047
+Ref: #income-statement-accounts13229
+Node: Register15649
+Ref: #register15786
+Node: Transaction17770
+Ref: #transaction17893
+Node: Error18763
+Ref: #error18857
+Node: TIPS19101
+Ref: #tips19200
+Node: Watch mode19242
+Ref: #watch-mode19349
+Node: Debug output20805
+Ref: #debug-output20916
+Node: ENVIRONMENT21128
+Ref: #environment21239
+Node: FILES22624
+Ref: #files22723
+Node: BUGS22936
+Ref: #bugs23013
 
 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.27.
+       tool.  This manual is for hledger-ui 1.28.
 
 SYNOPSIS
        hledger-ui [OPTIONS] [QUERYARGS]
@@ -47,6 +47,14 @@
        --theme=default|terminal|greenterm
               use this custom display theme
 
+       --menu start in the menu screen
+
+       --all  start in the all accounts screen
+
+       --bs   start in the balance sheet accounts screen
+
+       --is   start in the income statement accounts screen
+
        --register=ACCTREGEX
               start in the (first) matched account's register screen
 
@@ -219,88 +227,87 @@
 
        o Click on list items to go deeper
 
-       o Click  on  the left margin (column 0), or the blank area at bottom of
-         screen, to go back.
+       o Click on the left margin (column 0) to go back.
 
 KEYS
        Keyboard gives more control.
 
-       ? shows a help dialog listing all keys.  (Some of these also appear  in
+       ?  shows a help dialog listing all keys.  (Some of these also appear in
        the quick help at the bottom of each screen.) Press ? again (or ESCAPE,
        or LEFT, or q) to close it.  The following keys work on most screens:
 
-       The cursor keys navigate: RIGHT goes deeper, LEFT returns to the previ-
-       ous  screen, UP/DOWN/PGUP/PGDN/HOME/END move up and down through lists.
-       Emacs-style (CTRL-p/CTRL-n/CTRL-f/CTRL-b) movement keys are  also  sup-
-       ported  (but  not  vi-style  keys, since hledger-1.19, sorry!).  A tip:
-       movement speed is limited by your keyboard repeat rate, to move  faster
-       you  may  want to adjust it.  (If you're on a mac, the karabiner app is
-       one way to do that.)
+       The  cursor  keys navigate: RIGHT or ENTER goes deeper, LEFT returns to
+       the  previous  screen,  UP/DOWN/PGUP/PGDN/HOME/END  move  up  and  down
+       through  lists.  Emacs-style (CTRL-p/CTRL-n/CTRL-f/CTRL-b) and VI-style
+       (k,j,l,h) movement keys are also supported.  A tip: movement  speed  is
+       limited  by  your  keyboard repeat rate, to move faster you may want to
+       adjust it.  (If you're on a mac, the karabiner app is  one  way  to  do
+       that.)
 
-       With shift pressed, the cursor keys adjust the report period,  limiting
-       the  transactions  to  be  shown  (by  default, all are shown).  SHIFT-
-       DOWN/UP steps downward and upward through these standard report  period
-       durations:  year,  quarter,  month,  week, day.  Then, SHIFT-LEFT/RIGHT
-       moves to the previous/next period.  T sets the report period to  today.
-       With  the  -w/--watch option, when viewing a "current" period (the cur-
+       With  shift pressed, the cursor keys adjust the report period, limiting
+       the transactions to be shown  (by  default,  all  are  shown).   SHIFT-
+       DOWN/UP  steps downward and upward through these standard report period
+       durations: year, quarter, month,  week,  day.   Then,  SHIFT-LEFT/RIGHT
+       moves  to the previous/next period.  T sets the report period to today.
+       With the -w/--watch option, when viewing a "current" period  (the  cur-
        rent day, week, month, quarter, or year), the period will move automat-
-       ically  to  track  the current date.  To set a non-standard period, you
+       ically to track the current date.  To set a  non-standard  period,  you
        can use / and a date: query.
 
-       / lets you set a general filter query limiting the  data  shown,  using
-       the  same query terms as in hledger and hledger-web.  While editing the
-       query, you can use CTRL-a/e/d/k, BS, cursor keys; press  ENTER  to  set
+       /  lets  you  set a general filter query limiting the data shown, using
+       the same query terms as in hledger and hledger-web.  While editing  the
+       query,  you  can  use CTRL-a/e/d/k, BS, cursor keys; press ENTER to set
        it, or ESCAPEto cancel.  There are also keys for quickly adjusting some
-       common filters like account depth and transaction status  (see  below).
+       common  filters  like account depth and transaction status (see below).
        BACKSPACE or DELETE removes all filters, showing all transactions.
 
-       As  mentioned  above, by default hledger-ui hides future transactions -
+       As mentioned above, by default hledger-ui hides future  transactions  -
        both ordinary transactions recorded in the journal, and periodic trans-
-       actions   generated  by  rule.   F  toggles  forecast  mode,  in  which
+       actions  generated  by  rule.   F  toggles  forecast  mode,  in   which
        future/forecasted transactions are shown.
 
-       ESCAPE resets the UI state and jumps back to the top screen,  restoring
-       the  app's  initial  state  at startup.  Or, it cancels minibuffer data
+       ESCAPE  resets the UI state and jumps back to the top screen, restoring
+       the app's initial state at startup.  Or,  it  cancels  minibuffer  data
        entry or the help dialog.
 
        CTRL-l redraws the screen and centers the selection if possible (selec-
-       tions  near  the top won't be centered, since we don't scroll above the
+       tions near the top won't be centered, since we don't scroll  above  the
        top).
 
-       g reloads from the data file(s) and updates the current screen and  any
-       previous  screens.   (With  large  files, this could cause a noticeable
+       g  reloads from the data file(s) and updates the current screen and any
+       previous screens.  (With large files, this  could  cause  a  noticeable
        pause.)
 
-       I toggles balance assertion  checking.   Disabling  balance  assertions
+       I  toggles  balance  assertion  checking.  Disabling balance assertions
        temporarily can be useful for troubleshooting.
 
-       a  runs  command-line  hledger's  add  command, and reloads the updated
+       a runs command-line hledger's add  command,  and  reloads  the  updated
        file.  This allows some basic data entry.
 
-       A is like a, but runs the hledger-iadd tool, which provides a  terminal
-       interface.   This key will be available if hledger-iadd is installed in
+       A  is like a, but runs the hledger-iadd tool, which provides a terminal
+       interface.  This key will be available if hledger-iadd is installed  in
        $path.
 
-       E runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default (emacsclient -a  ""
-       -nw)  on  the  journal file.  With some editors (emacs, vi), the cursor
-       will be positioned at the current transaction  when  invoked  from  the
-       register  and transaction screens, and at the error location (if possi-
+       E  runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default (emacsclient -a ""
+       -nw) on the journal file.  With some editors (emacs,  vi),  the  cursor
+       will  be  positioned  at  the current transaction when invoked from the
+       register and transaction screens, and at the error location (if  possi-
        ble) when invoked from the error screen.
 
-       B toggles cost mode, showing amounts in their transaction price's  com-
+       B  toggles cost mode, showing amounts in their transaction price's com-
        modity (like toggling the -B/--cost flag).
 
-       V  toggles  value  mode, showing amounts' current market value in their
-       default valuation  commodity  (like  toggling  the  -V/--market  flag).
-       Note,  "current market value" means the value on the report end date if
-       specified, otherwise today.  To see the value on another date, you  can
-       temporarily  set that as the report end date.  Eg: to see a transaction
-       as it was valued on july 30, go to the  accounts  or  register  screen,
+       V toggles value mode, showing amounts' current market  value  in  their
+       default  valuation  commodity  (like  toggling  the  -V/--market flag).
+       Note, "current market value" means the value on the report end date  if
+       specified,  otherwise today.  To see the value on another date, you can
+       temporarily set that as the report end date.  Eg: to see a  transaction
+       as  it  was  valued  on july 30, go to the accounts or register screen,
        press /, and add date:-7/30 to the query.
 
        At most one of cost or value mode can be active at once.
 
-       There's  not yet any visual reminder when cost or value mode is active;
+       There's not yet any visual reminder when cost or value mode is  active;
        for now pressing b b v should reliably reset to normal mode.
 
        q quits the application.
@@ -308,36 +315,74 @@
        Additional screen-specific keys are described below.
 
 SCREENS
-   Accounts screen
-       This is normally the first screen displayed.   It  lists  accounts  and
-       their  balances,  like hledger's balance command.  By default, it shows
-       all accounts and their latest ending balances (including  the  balances
-       of  subaccounts).   Accounts  which  have been declared with an account
-       directive are also listed, even if not yet used (except for empty  par-
-       ent  accounts).   If  you specify a query on the command line, it shows
-       just the matched accounts and the balances from matched transactions.
+       hledger-ui  shows several different screens, described below.  It shows
+       the "Balance sheet accounts" screen to start with, except in  the  fol-
+       lowing situations:
 
-       Account names are shown as a flat list by default; press  t  to  toggle
-       tree  mode.   In  list  mode,  account balances are exclusive of subac-
-       counts, except where subaccounts are  hidden  by  a  depth  limit  (see
-       below).   In  tree  mode,  all account balances are inclusive of subac-
+       o If  no  asset/liability/equity  accounts  can  be  detected, or if an
+         account query has been given on the command line, it  starts  in  the
+         "All accounts" screen.
+
+       o If  a starting screen is specified with --menu/--all/--bs/--is/--reg-
+         ister on the command line, it starts in that screen.
+
+       From any screen you can press LEFT or ESC to navigate back to  the  top
+       level "Menu" screen.
+
+   Menu
+       The  top-most  screen.   From  here  you can navigate to three accounts
+       screens:
+
+   All accounts
+       This screen shows all accounts (possibly  filtered  by  a  query),  and
+       their end balances on the date shown in the title bar (or their balance
+       changes in the period shown in the title bar, toggleable with  H).   It
+       is like the hledger balance command.
+
+   Balance sheet accounts
+       This screen shows asset, liability and equity accounts, if these can be
+       detected (see account types).  It always shows  end  balances.   It  is
+       like the hledger balancesheetequity command.
+
+   Income statement accounts
+       This  screen  shows revenue and expense accounts.  It always shows bal-
+       ance changes.  It is like the hledger incomestatement command.
+
+       All of these accounts screens work in much the same way:
+
+       They show accounts which have been posted to by transactions,  as  well
+       as  accounts which have been declared with an account directive (except
+       for empty parent accounts).
+
+       If you specify a query on the command line or with / in the  app,  they
+       show  just the matched accounts, and the balances from matched transac-
+       tions.
+
+       hledger-ui shows accounts with zero balances by  default  (unlike  com-
+       mand-line hledger).  To hide these, press z to toggle nonzero mode.
+
+       Account  names  are  shown as a flat list by default; press t to toggle
+       tree mode.  In list mode, account  balances  are  exclusive  of  subac-
+       counts,  except  where  subaccounts  are  hidden  by a depth limit (see
+       below).  In tree mode, all account balances  are  inclusive  of  subac-
        counts.
 
-       To see less detail, press a number key, 1 to 9, to set a  depth  limit.
+       To  see  less detail, press a number key, 1 to 9, to set a depth limit.
        Or use - to decrease and +/= to increase the depth limit.  0 shows even
-       less detail, collapsing all accounts to a single total.  To remove  the
-       depth  limit,  set  it  higher than the maximum account depth, or press
+       less  detail, collapsing all accounts to a single total.  To remove the
+       depth limit, set it higher than the maximum  account  depth,  or  press
        ESCAPE.
 
-       H toggles between showing historical balances or period balances.  His-
-       torical  balances  (the  default) are ending balances at the end of the
-       report period, taking into account all transactions  before  that  date
-       (filtered  by  the  filter query if any), including transactions before
-       the start of the report period.  In other  words,  historical  balances
-       are  what  you  would  see on a bank statement for that account (unless
-       disturbed by a filter  query).   Period  balances  ignore  transactions
-       before the report start date, so they show the change in balance during
-       the report period.  They are more useful eg when viewing a time log.
+       H  toggles  between  showing historical balances or period balances (on
+       the "All accounts" screen).  Historical balances (the default) are end-
+       ing  balances  at the end of the report period, taking into account all
+       transactions before that date (filtered by the filter  query  if  any),
+       including transactions before the start of the report period.  In other
+       words, historical balances are what you would see on a  bank  statement
+       for that account (unless disturbed by a filter query).  Period balances
+       ignore transactions before the report start  date,  so  they  show  the
+       change  in  balance  during the report period.  They are more useful eg
+       when viewing a time log.
 
        U toggles filtering by unmarked status, including or excluding unmarked
        postings in the balances.  Similarly, P toggles pending postings, and C
@@ -347,119 +392,110 @@
 
        R toggles real mode, in which virtual postings are ignored.
 
-       z toggles nonzero mode, in which only accounts  with  nonzero  balances
-       are  shown (hledger-ui shows zero items by default, unlike command-line
-       hledger).
-
-       Press RIGHT to view an account's transactions register.
+       Press RIGHT to view an account's register screen, Or, LEFT to  see  the
+       menu screen.
 
-   Register screen
+   Register
        This screen shows the transactions affecting a particular account, like
        a check register.  Each line represents one transaction and shows:
 
-       o the  other  account(s)  involved, in abbreviated form.  (If there are
-         both real and virtual postings, it shows only the  accounts  affected
+       o the other account(s) involved, in abbreviated form.   (If  there  are
+         both  real  and virtual postings, it shows only the accounts affected
          by real postings.)
 
-       o the  overall change to the current account's balance; positive for an
+       o the overall change to the current account's balance; positive for  an
          inflow to this account, negative for an outflow.
 
        o the running historical total or period total for the current account,
-         after  the  transaction.  This can be toggled with H.  Similar to the
-         accounts screen, the historical total  is  affected  by  transactions
-         (filtered  by  the  filter query) before the report start date, while
+         after the transaction.  This can be toggled with H.  Similar  to  the
+         accounts  screen,  the  historical  total is affected by transactions
+         (filtered by the filter query) before the report  start  date,  while
          the period total is not.  If the historical total is not disturbed by
-         a  filter  query, it will be the running historical balance you would
+         a filter query, it will be the running historical balance  you  would
          see on a bank register for the current account.
 
-       Transactions affecting this account's subaccounts will be  included  in
+       Transactions  affecting  this account's subaccounts will be included in
        the register if the accounts screen is in tree mode, or if it's in list
-       mode but this account has subaccounts which are  not  shown  due  to  a
-       depth  limit.   In  other words, the register always shows the transac-
-       tions contributing to the balance shown on the accounts  screen.   Tree
+       mode  but  this  account  has  subaccounts which are not shown due to a
+       depth limit.  In other words, the register always  shows  the  transac-
+       tions  contributing  to the balance shown on the accounts screen.  Tree
        mode/list mode can be toggled with t here also.
 
-       U  toggles  filtering  by  unmarked  status, showing or hiding unmarked
+       U toggles filtering by unmarked  status,  showing  or  hiding  unmarked
        transactions.  Similarly, P toggles pending transactions, and C toggles
-       cleared  transactions.  (By default, transactions with all statuses are
-       shown; if you activate one or two status filters, only  those  transac-
+       cleared transactions.  (By default, transactions with all statuses  are
+       shown;  if  you activate one or two status filters, only those transac-
        tions are shown; and if you activate all three, the filter is removed.)
 
        R toggles real mode, in which virtual postings are ignored.
 
-       z toggles nonzero mode, in which only transactions  posting  a  nonzero
-       change  are  shown (hledger-ui shows zero items by default, unlike com-
+       z  toggles  nonzero  mode, in which only transactions posting a nonzero
+       change are shown (hledger-ui shows zero items by default,  unlike  com-
        mand-line hledger).
 
        Press RIGHT to view the selected transaction in detail.
 
-   Transaction screen
-       This screen shows a single transaction, as  a  general  journal  entry,
-       similar  to  hledger's  print command and journal format (hledger_jour-
+   Transaction
+       This  screen  shows  a  single transaction, as a general journal entry,
+       similar to hledger's print command and  journal  format  (hledger_jour-
        nal(5)).
 
-       The transaction's date(s)  and  any  cleared  flag,  transaction  code,
-       description,  comments,  along  with  all  of  its account postings are
-       shown.  Simple transactions have two postings, but there  can  be  more
+       The  transaction's  date(s)  and  any  cleared  flag, transaction code,
+       description, comments, along with  all  of  its  account  postings  are
+       shown.   Simple  transactions  have two postings, but there can be more
        (or in certain cases, fewer).
 
-       UP  and  DOWN will step through all transactions listed in the previous
-       account register screen.  In the title bar, the numbers in  parentheses
-       show  your  position  within  that  account  register.   They will vary
+       UP and DOWN will step through all transactions listed in  the  previous
+       account  register screen.  In the title bar, the numbers in parentheses
+       show your position  within  that  account  register.   They  will  vary
        depending on which account register you came from (remember most trans-
        actions appear in multiple account registers).  The #N number preceding
        them is the transaction's position within the complete unfiltered jour-
        nal, which is a more stable id (at least until the next reload).
 
-   Error screen
-       This  screen  will appear if there is a problem, such as a parse error,
-       when you press g to reload.  Once you have fixed the problem,  press  g
+   Error
+       This screen will appear if there is a problem, such as a  parse  error,
+       when  you  press g to reload.  Once you have fixed the problem, press g
        again to reload and resume normal operation.  (Or, you can press escape
        to cancel the reload attempt.)
 
 TIPS
    Watch mode
-       One of hledger-ui's best  features  is  the  auto-reloading  -w/--watch
-       mode.   With  this flag, it will update the display automatically when-
+       One  of  hledger-ui's  best  features  is the auto-reloading -w/--watch
+       mode.  With this flag, it will update the display  automatically  when-
        ever changes are saved to the data files.
 
-       This is very useful when reconciling.  A good workflow is to have  your
-       bank's  online  register  open  in a browser window, for reference; the
-       journal file open in an editor window; and hledger-ui in watch mode  in
+       This  is very useful when reconciling.  A good workflow is to have your
+       bank's online register open in a browser  window,  for  reference;  the
+       journal  file open in an editor window; and hledger-ui in watch mode in
        a terminal window, eg:
 
               $ hledger-ui --watch --register checking -C
 
-       As  you mark things cleared in the editor, you can see the effect imme-
-       diately without having to context  switch.   This  leaves  more  mental
-       bandwidth  for  your accounting.  Of course you can still interact with
-       hledger-ui when needed, eg to toggle cleared mode, or  to  explore  the
+       As you mark things cleared in the editor, you can see the effect  imme-
+       diately  without  having  to  context  switch.  This leaves more mental
+       bandwidth for your accounting.  Of course you can still  interact  with
+       hledger-ui  when  needed,  eg to toggle cleared mode, or to explore the
        history.
 
-   Watch mode limitations
-       There  are  situations  in which it won't work, ie the display will not
-       update when you save a change (because the underlying  inotify  library
-       does not support it).  Here are some that we know of:
-
-       o Certain  editors:  saving  with gedit, and perhaps any Gnome applica-
-         tion, won't be detected (#1617).  Jetbrains IDEs, such as IDEA,  also
-         may not work (#911).
-
-       o Certain  unusual  filesystems might not be supported.  (All the usual
-         ones on unix, mac and windows are supported.)
+       Here are some current limitations to be aware of:
 
-       In such cases, the workaround is to switch to the hledger-ui window and
-       press  g  each  time  you  want it to reload.  (Actually, see #1617 for
-       another workaround, and let us know if it works for you.)
+       Changes might not be detected with certain editors, possibly  including
+       Jetbrains  IDEs, gedit, other Gnome applications; or on certain unusual
+       filesystems.  (#1617, #911).  To work around, reload manually by press-
+       ing  g in the hledger-ui window.  (Or see #1617 for another workaround,
+       and let us know if it works for you.)
 
-       If you leave hledger-ui --watch running for days, on certain  platforms
-       (?),  perhaps  with many transactions in your journal (?), perhaps with
-       large numbers of other files present (?),  you  may  see  it  gradually
-       using  more and more memory and CPU over time, as seen in top or Activ-
-       ity Monitor or Task Manager.
+       CPU and memory usage can sometimes gradually  increase,  if  hledger-ui
+       --watch  is  left  running for days.  (Possibly correlated with certain
+       platforms, many transactions,  and/or  large  numbers  of  other  files
+       present).   To  work  around, quit and restart it, or (where supported)
+       suspend (CTRL-z) and restart it (fg).
 
-       A workaround is to quit and restart it, or to suspend it  (CTRL-z)  and
-       restart it (fg) if your shell supports that.
+   Debug output
+       You can add --debug[=N] to the command line to log debug output.   This
+       will  be logged to the file hledger-ui.log in the current directory.  N
+       ranges from 1 (least output, the default) to 9 (maximum output).
 
 ENVIRONMENT
        COLUMNS The screen width to use.  Default: the full terminal width.
@@ -468,17 +504,17 @@
 
        On unix computers, the default value is: ~/.hledger.journal.
 
-       A  more  typical  value is something like ~/finance/YYYY.journal, where
-       ~/finance is a version-controlled finance directory  and  YYYY  is  the
-       current  year.  Or, ~/finance/current.journal, where current.journal is
+       A more typical value is something  like  ~/finance/YYYY.journal,  where
+       ~/finance  is  a  version-controlled  finance directory and YYYY is the
+       current year.  Or, ~/finance/current.journal, where current.journal  is
        a symbolic link to YYYY.journal.
 
-       The usual way to set this permanently is to add a  command  to  one  of
+       The  usual  way  to  set this permanently is to add a command to one of
        your shell's startup files (eg ~/.profile):
 
               export LEDGER_FILE=~/finance/current.journal`
 
-       On  some Mac computers, there is a more thorough way to set environment
+       On some Mac computers, there is a more thorough way to set  environment
        variables, that will also affect applications started from the GUI (eg,
        Emacs started from a dock icon): In ~/.MacOSX/environment.plist, add an
        entry like:
@@ -489,24 +525,24 @@
 
        For this to take effect you might need to killall Dock, or reboot.
 
-       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-
+       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"
 
-       Or,   change   it   in   settings:   see  https://www.java.com/en/down-
+       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).
@@ -514,13 +550,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.
@@ -531,7 +567,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)
 
 
@@ -549,4 +585,4 @@
 
 
 
-hledger-ui-1.27                 September 2022                   HLEDGER-UI(1)
+hledger-ui-1.28                  December 2022                   HLEDGER-UI(1)
