Repository: zulip/zulip-terminal
Pull request #1515 (2024-06-07 01:19:48)

Add new help categories to remove parentheses (context) from help texts
### What does this PR do, and why?
Adds the following new help categories to remove the corresponding context-suffixes (in parentheses) from help texts:
- Stream information ("from stream information" suffixes)
- Message information ("from message information" suffixes)
- User list actions ("from user list" suffixes)

That removes parantheses from all the help texts.

Each of the categories are added in their own commit, for readability.
A new command entry `NARROW_TO_USER_PM` is also added to the user list actions.

### Outstanding aspect(s)
This PR is about removing the contexts mentioned in parentheses as suffixes. And it removes all of them.
But, it also introduces new parentheses for the help category names itself.

I went with this because
1. There is not much repetition of the content in the parentheses
2. Length constraints apply to help texts, they don't wrap. But, help category names do wrap.
3. They add some needed clarity, on when/where the help category's hotkeys will work.

Feedback welcome!

### External discussion & connections
- [x] Discussed in **#zulip-terminal** in [`re-categorize`](https://chat.zulip.org/#narrow/stream/206-zulip-terminal/topic/Re-Categorization.20of.20Hotkeys/near/1793697)
- [ ] Fully fixes #
- [ ] Partially fixes issue #
- [ ] Builds upon previous unmerged work in PR #
- [ ] Is a follow-up to work in PR #
- [ ] Requires merge of PR #
- [ ] Merge will enable work on #

### How did you test this?
- [ ] Manually - Behavioral changes
- [x] Manually - Visual changes
- [ ] Adapting existing automated tests
- [ ] Adding automated tests for new behavior (or missing tests)
- [x] Existing automated tests should already cover this (*only a refactor of tested code*)

### Self-review checklist for each commit
- [x] It is a [minimal coherent idea](https://github.com/zulip/zulip-terminal#structuring-commits---speeding-up-reviews-merging--development)
- [x] It has a commit summary following the [documented style](https://github.com/zulip/zulip-terminal#structuring-commits---speeding-up-reviews-merging--development) (title & body)
- [x] It has a commit summary describing the  motivation and reasoning for the change
- [x] It individually passes linting and tests
- [ ] It contains test additions for any new behavior
- [x] It flows clearly from a previous branch commit, and/or prepares for the next commit

### Visual changes    
The information popup categories:
![image - info popups](https://github.com/zulip/zulip-terminal/assets/20315308/9fd542f6-8eea-4611-9ef4-0870836ee7f6)
The user list actions category:
![image - user list actions](https://github.com/zulip/zulip-terminal/assets/20315308/ee401b59-f010-41d7-bc90-f0d5f08aa5ed)


Niloth-p: Rebased onto main, added a comment as per feedback.
Niloth-p: Rebased and pushed.

We just have 2 keys for the 2 categories 'Stream message info actions' and 'User list actions'. As discussed, those make it tiny categories. So,
- we could either discuss the alternative options that I previously suggested in the CZO topic,
- or leave this PR until more hotkeys are added to these categories,
- or we could push these changes but put these tiny categories at the bottom of the help menu.

Patch:
diff --git a/.mailmap b/.mailmap
index 3304f0a..d495ac8 100644
--- a/.mailmap
+++ b/.mailmap
@@ -16,3 +16,5 @@ Zeeshan Equbal <equbalzeeshan@gmail.com>
 Mounil K. Shah <mounilshah271@gmail.com> <mkshah_b18@ce.vjti.ac.in>
 Mounil K. Shah <mounilshah271@gmail.com>
 Vishwesh Pillai <vishwesh103@gmail.com>
+Sashank Ravipati <sashankr04@gmail.com>
+Niloth P <20315308+Niloth-p@users.noreply.github.com>
diff --git a/README.md b/README.md
index 9fd63f4..c0395a2 100644
--- a/README.md
+++ b/README.md
@@ -246,6 +246,15 @@ notify=disabled
 
 ## Color-depth: set to one of 1 (for monochrome), 16, 256, or 24bit
 color-depth=256
+
+## Transparency: set to 'enabled' to allow background transparency
+## This is highly dependent on a suitable terminal emulator, and support in the selected theme
+## Terminal emulators without this feature may show an arbitrary solid background color
+transparency=disabled
+
+## Editor: set external editor command, to edit message content
+## If not set, this falls back to the $ZULIP_EDITOR_COMMAND then $EDITOR environment variables
+# editor: nano
 ```
 
 > **NOTE:** Most of these configuration settings may be specified on the
diff --git a/docs/FAQ.md b/docs/FAQ.md
index b147444..04a5d16 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -13,6 +13,7 @@
   - [When are messages marked as having been read?](#when-are-messages-marked-as-having-been-read)
   - [How do I access multiple servers?](#how-do-i-access-multiple-servers)
   - [What is autocomplete? Why is it useful?](#what-is-autocomplete-why-is-it-useful)
+  - [Can I compose messages in another editor?](#can-i-compose-messages-in-another-editor)
 - Something is not working!
   - [Colors appear mismatched, don't change with theme, or look strange](#colors-appear-mismatched-dont-change-with-theme-or-look-strange)
   - [Symbols look different to in the provided screenshots, or just look incorrect](#symbols-look-different-to-in-the-provided-screenshots-or-just-look-incorrect)
@@ -373,6 +374,55 @@ through autocomplete depend upon the context automatically.
 **NOTE:** If a direct message recipient's name contains comma(s) (`,`), they
 are currently treated as comma-separated recipients.
 
+## Can I compose messages in another editor?
+
+In the `main` branch of zulip-terminal, you can now use an external editor of
+your choice to compose your messages using the `Ctrl o` hotkey.
+
+The editor command is looked for in the `ZULIP_EDITOR_COMMAND` and `EDITOR`
+environment variables (in that order), or the `editor` entry of the zuliprc
+file (which overrides both of those).
+
+It should work directly for most terminal editors with only the program name,
+eg. `vim`, `nano`, `helix`, `kakoune`, `nvim`, etc.
+
+It can also be used with a desktop editor with some constraints: the program
+must not fork or detach from the running terminal and should open in a new
+window. For this reason it may be useful to use `ZULIP_EDITOR_COMMAND` (or the
+zuliprc setting) to avoid conflict with the more widely used `EDITOR`
+environment variable. Some examples include:
+
+- [lapce](https://github.com/lapce/lapce) with `lapce -n -w`
+- [sublime-text](https://www.sublimetext.com/) with `subl -n -w`
+- [marker](https://github.com/fabiocolacio/Marker) with `marker`
+- [vim](https://github.com/vim/vim) with `vim -g -f` or `gvim -f`
+- [vscode](https://github.com/microsoft/vscode) with `code -n -w`
+
+**NOTE:** Backslashing white space (`\ `) is needed when using an executable
+containing them, for example Sublime Text on macOS can be configured with
+`/Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl`.
+
+This feature works by sending the message content back and forth using
+temporary files:
+- A temporary file is created, filled with any existing message content;
+- The editor command is run, with the filename appended (standard practice for editors);
+- You edit your text in the editor of your choice;
+- When the external editor process ends (closing the window, or quitting the terminal
+editor), the compose box will be updated with the new message content from
+the temporary file.
+
+> NOTE 1: If using a terminal-based editor, it will temporarily take over the
+> entire terminal and replace the application. However, upon exiting that
+> editor, the application should return to the terminal.
+
+> NOTE 2: Whichever type of editor you use (terminal or desktop/graphical), the
+> application will temporarily pause while you are editing. The application
+> should resume as normal once you exit the external editor and the text you
+> entered appears in the compose area.
+
+**If you have success with other editors, these examples do not work, or have
+other problems, please let us know!**
+
 ## Colors appear mismatched, don't change with theme, or look strange
 
 Some terminal emulators support specifying custom colors, or custom color
diff --git a/docs/hotkeys.md b/docs/hotkeys.md
index e984694..e0ecf04 100644
--- a/docs/hotkeys.md
+++ b/docs/hotkeys.md
@@ -11,7 +11,6 @@
 |Copy information from About Menu to clipboard|<kbd>c</kbd>|
 |Redraw screen|<kbd>Ctrl</kbd> + <kbd>l</kbd>|
 |Quit|<kbd>Ctrl</kbd> + <kbd>c</kbd>|
-|Show/hide user information (from users list)|<kbd>i</kbd>|
 |New footer hotkey hint|<kbd>Tab</kbd>|
 
 ## Navigation
@@ -26,10 +25,18 @@
 |Scroll down|<kbd>PgDn</kbd> / <kbd>J</kbd>|
 |Go to bottom / Last message|<kbd>End</kbd> / <kbd>G</kbd>|
 |Trigger the selected entry|<kbd>Enter</kbd> / <kbd>Space</kbd>|
-|Narrow to all messages|<kbd>a</kbd> / <kbd>Esc</kbd>|
-|Narrow to all direct messages|<kbd>P</kbd>|
-|Narrow to all starred messages|<kbd>f</kbd>|
-|Narrow to messages in which you're mentioned|<kbd>#</kbd>|
+
+## Switching Messages View
+|Command|Key Combination|
+| :--- | :---: |
+|View the stream of the current message|<kbd>s</kbd>|
+|View the topic of the current message|<kbd>S</kbd>|
+|Zoom in/out the message's conversation context|<kbd>z</kbd>|
+|Switch message view to the compose box target|<kbd>Meta</kbd> + <kbd>.</kbd>|
+|View all messages|<kbd>a</kbd> / <kbd>Esc</kbd>|
+|View all direct messages|<kbd>P</kbd>|
+|View all starred messages|<kbd>f</kbd>|
+|View all messages in which you're mentioned|<kbd>#</kbd>|
 |Next unread topic|<kbd>n</kbd>|
 |Next unread direct message|<kbd>p</kbd>|
 
@@ -49,18 +56,11 @@
 | :--- | :---: |
 |Edit message's content or topic|<kbd>e</kbd>|
 |Show/hide emoji picker for current message|<kbd>:</kbd>|
-|Narrow to the stream of the current message|<kbd>s</kbd>|
-|Narrow to the topic of the current message|<kbd>S</kbd>|
-|Narrow to a topic/direct-chat, or stream/all-direct-messages|<kbd>z</kbd>|
 |Toggle first emoji reaction on selected message|<kbd>=</kbd>|
 |Toggle thumbs-up reaction to the current message|<kbd>+</kbd>|
 |Toggle star status of the current message|<kbd>Ctrl</kbd> + <kbd>s</kbd> / <kbd>*</kbd>|
 |Show/hide message information|<kbd>i</kbd>|
 |Show/hide message sender information|<kbd>u</kbd>|
-|Show/hide edit history (from message information)|<kbd>e</kbd>|
-|View current message in browser (from message information)|<kbd>v</kbd>|
-|Show/hide full rendered message (from message information)|<kbd>f</kbd>|
-|Show/hide full raw message (from message information)|<kbd>r</kbd>|
 
 ## Stream list actions
 |Command|Key Combination|
@@ -68,8 +68,12 @@
 |Toggle topics in a stream|<kbd>t</kbd>|
 |Mute/unmute streams|<kbd>m</kbd>|
 |Show/hide stream information & modify settings|<kbd>i</kbd>|
-|Show/hide stream members (from stream information)|<kbd>m</kbd>|
-|Copy stream email to clipboard (from stream information)|<kbd>c</kbd>|
+
+## User list actions
+|Command|Key Combination|
+| :--- | :---: |
+|Show/hide user information|<kbd>i</kbd>|
+|Narrow to direct messages with user|<kbd>Enter</kbd>|
 
 ## Begin composing a message
 |Command|Key Combination|
@@ -90,9 +94,9 @@
 |Save current message as a draft|<kbd>Meta</kbd> + <kbd>s</kbd>|
 |Autocomplete @mentions, #stream_names, :emoji: and topics|<kbd>Ctrl</kbd> + <kbd>f</kbd>|
 |Cycle through autocomplete suggestions in reverse|<kbd>Ctrl</kbd> + <kbd>r</kbd>|
-|Narrow to compose box message recipient|<kbd>Meta</kbd> + <kbd>.</kbd>|
 |Exit message compose box|<kbd>Esc</kbd>|
 |Insert new line|<kbd>Enter</kbd>|
+|Open an external editor to edit the message content|<kbd>Ctrl</kbd> + <kbd>o</kbd>|
 
 ## Editor: Navigation
 |Command|Key Combination|
@@ -118,3 +122,17 @@
 |Delete previous character|<kbd>Ctrl</kbd> + <kbd>h</kbd>|
 |Swap with previous character|<kbd>Ctrl</kbd> + <kbd>t</kbd>|
 
+## Stream information (press i to view info of a stream)
+|Command|Key Combination|
+| :--- | :---: |
+|Show/hide stream members|<kbd>m</kbd>|
+|Copy stream email to clipboard|<kbd>c</kbd>|
+
+## Message information (press i to view info of a message)
+|Command|Key Combination|
+| :--- | :---: |
+|Show/hide edit history|<kbd>e</kbd>|
+|View current message in browser|<kbd>v</kbd>|
+|Show/hide full rendered message|<kbd>f</kbd>|
+|Show/hide full raw message|<kbd>r</kbd>|
+
diff --git a/zulipterminal/cli/run.py b/zulipterminal/cli/run.py
index 46d13e8..a5cce2c 100755
--- a/zulipterminal/cli/run.py
+++ b/zulipterminal/cli/run.py
@@ -33,6 +33,7 @@ from zulipterminal.version import ZT_VERSION
 class ConfigSource(Enum):
     DEFAULT = "from default config"
     ZULIPRC = "in zuliprc file"
+    ENV = "from environment"
     COMMANDLINE = "on command line"
 
 
@@ -62,6 +63,7 @@ VALID_BOOLEAN_SETTINGS: Dict[str, Tuple[str, str]] = {
     "autohide": ("autohide", "no_autohide"),
     "notify": ("enabled", "disabled"),
     "exit_confirmation": ("enabled", "disabled"),
+    "transparency": ("enabled", "disabled"),
 }
 
 COLOR_DEPTH_ARGS_TO_DEPTHS: Dict[str, int] = {
@@ -80,6 +82,8 @@ DEFAULT_SETTINGS = {
     "color-depth": "256",
     "maximum-footlinks": "3",
     "exit_confirmation": "enabled",
+    "transparency": "disabled",
+    "editor": "",
 }
 assert DEFAULT_SETTINGS["autohide"] in VALID_BOOLEAN_SETTINGS["autohide"]
 assert DEFAULT_SETTINGS["notify"] in VALID_BOOLEAN_SETTINGS["notify"]
@@ -87,6 +91,7 @@ assert DEFAULT_SETTINGS["color-depth"] in COLOR_DEPTH_ARGS_TO_DEPTHS
 assert (
     DEFAULT_SETTINGS["exit_confirmation"] in VALID_BOOLEAN_SETTINGS["exit_confirmation"]
 )
+assert DEFAULT_SETTINGS["transparency"] in VALID_BOOLEAN_SETTINGS["transparency"]
 
 
 def in_color(color: str, text: str) -> str:
@@ -155,6 +160,22 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
         help="do not mark messages as read in the session",
     )
 
+    transparency_group = parser.add_mutually_exclusive_group()
+    transparency_group.add_argument(
+        "--transparency",
+        dest="transparency",
+        action="store_const",
+        const="enabled",
+        help="enable transparent background (if supported by theme and terminal)",
+    )
+    transparency_group.add_argument(
+        "--no-transparency",
+        dest="transparency",
+        action="store_const",
+        const="disabled",
+        help="disable transparent background",
+    )
+
     notify_group = parser.add_mutually_exclusive_group()
     notify_group.add_argument(
         "--notify",
@@ -496,6 +517,11 @@ def main(options: Optional[List[str]] = None) -> None:
             theme_to_use = SettingData(real_theme_name, theme_to_use.source)
 
         ### Load overrides & validate remaining settings
+        if args.transparency:
+            zterm["transparency"] = SettingData(
+                args.transparency, ConfigSource.COMMANDLINE
+            )
+
         if args.autohide:
             zterm["autohide"] = SettingData(args.autohide, ConfigSource.COMMANDLINE)
 
@@ -553,18 +579,39 @@ def main(options: Optional[List[str]] = None) -> None:
             print_setting("maximum footlinks value", zterm["maximum-footlinks"])
         print_setting("color depth setting", zterm["color-depth"])
         print_setting("notify setting", zterm["notify"])
+        print_setting("transparency setting", zterm["transparency"])
+
+        if zterm["editor"].source == ConfigSource.ZULIPRC:
+            editor_command = zterm["editor"].value
+            editor_config_source = ConfigSource.ZULIPRC
+        else:
+            editor_command = os.environ.get(
+                "ZULIP_EDITOR_COMMAND",
+                os.environ.get("EDITOR", ""),
+            )
+            editor_config_source = ConfigSource.ENV
+
+        print_setting(
+            "external editor command",
+            SettingData(editor_command, editor_config_source),
+        )
 
         ### Generate data not output to user, but into Controller
+        # Translate valid strings for boolean values into True/False
+        boolean_settings: Dict[str, bool] = dict()
+        for setting, valid_values in VALID_BOOLEAN_SETTINGS.items():
+            boolean_settings[setting] = zterm[setting].value == valid_values[0]
+
         # Generate urwid palette
         color_depth_str = zterm["color-depth"].value
         color_depth = COLOR_DEPTH_ARGS_TO_DEPTHS[color_depth_str]
+        transparency_enabled = boolean_settings["transparency"]
 
-        theme_data = generate_theme(theme_to_use.value, color_depth)
-
-        # Translate valid strings for boolean values into True/False
-        boolean_settings: Dict[str, bool] = dict()
-        for setting, valid_boolean_values in VALID_BOOLEAN_SETTINGS.items():
-            boolean_settings[setting] = zterm[setting].value == valid_boolean_values[0]
+        theme_data = generate_theme(
+            theme_to_use.value,
+            color_depth=color_depth,
+            transparent_background=transparency_enabled,
+        )
 
         Controller(
             config_file=zuliprc_path,
@@ -575,6 +622,7 @@ def main(options: Optional[List[str]] = None) -> None:
             in_explore_mode=args.explore,
             **boolean_settings,
             debug_path=debug_path,
+            editor_command=editor_command,
         ).main()
     except ServerConnectionFailure as e:
         # Acts as separator between logs
diff --git a/zulipterminal/config/color.py b/zulipterminal/config/color.py
index 93333de..b5a6cb5 100644
--- a/zulipterminal/config/color.py
+++ b/zulipterminal/config/color.py
@@ -25,13 +25,18 @@ from pygments.token import (
 
 
 # fmt: off
+# Background is treated as transparent by default
+# This avoids the need for a dummy default color
+class Background(Enum):
+    COLOR         = 'default         default    default'
+
+
 # NOTE: The 24bit color codes use 256 color which can be
 # enhanced to be truly 24bit.
 # NOTE: The 256code format can be moved to h0-255 to
 # make use of the complete range instead of only 216 colors.
 class DefaultColor(Enum):
     # color       =  16code          256code    24code
-    DEFAULT       = 'default         default    default'
     BLACK         = 'black           g19        g19'
     DARK_RED      = 'dark_red        #a00       #a00'
     DARK_GREEN    = 'dark_green      #080       #080'
diff --git a/zulipterminal/config/keys.py b/zulipterminal/config/keys.py
index f2111c2..6b7bb31 100644
--- a/zulipterminal/config/keys.py
+++ b/zulipterminal/config/keys.py
@@ -169,30 +169,30 @@ KEY_BINDINGS: Dict[str, KeyBinding] = {
     },
     'STREAM_NARROW': {
         'keys': ['s'],
-        'help_text': 'Narrow to the stream of the current message',
-        'key_category': 'msg_actions',
+        'help_text': 'View the stream of the current message',
+        'key_category': 'narrowing',
     },
     'TOPIC_NARROW': {
         'keys': ['S'],
-        'help_text': 'Narrow to the topic of the current message',
-        'key_category': 'msg_actions',
+        'help_text': 'View the topic of the current message',
+        'key_category': 'narrowing',
+    },
+    'TOGGLE_NARROW': {
+        'keys': ['z'],
+        'help_text':
+            "Zoom in/out the message's conversation context",
+        'key_category': 'narrowing',
     },
     'NARROW_MESSAGE_RECIPIENT': {
         'keys': ['meta .'],
-        'help_text': 'Narrow to compose box message recipient',
-        'key_category': 'compose_box',
+        'help_text': 'Switch message view to the compose box target',
+        'key_category': 'narrowing',
     },
     'EXIT_COMPOSE': {
         'keys': ['esc'],
         'help_text': 'Exit message compose box',
         'key_category': 'compose_box',
     },
-    'TOGGLE_NARROW': {
-        'keys': ['z'],
-        'help_text':
-            'Narrow to a topic/direct-chat, or stream/all-direct-messages',
-        'key_category': 'msg_actions',
-    },
     'REACTION_AGREEMENT': {
         'keys': ['='],
         'help_text': 'Toggle first emoji reaction on selected message',
@@ -205,33 +205,33 @@ KEY_BINDINGS: Dict[str, KeyBinding] = {
     },
     'ALL_MESSAGES': {
         'keys': ['a', 'esc'],
-        'help_text': 'Narrow to all messages',
-        'key_category': 'navigation',
+        'help_text': 'View all messages',
+        'key_category': 'narrowing',
     },
     'ALL_PM': {
         'keys': ['P'],
-        'help_text': 'Narrow to all direct messages',
-        'key_category': 'navigation',
+        'help_text': 'View all direct messages',
+        'key_category': 'narrowing',
     },
     'ALL_STARRED': {
         'keys': ['f'],
-        'help_text': 'Narrow to all starred messages',
-        'key_category': 'navigation',
+        'help_text': 'View all starred messages',
+        'key_category': 'narrowing',
     },
     'ALL_MENTIONS': {
         'keys': ['#'],
-        'help_text': "Narrow to messages in which you're mentioned",
-        'key_category': 'navigation',
+        'help_text': "View all messages in which you're mentioned",
+        'key_category': 'narrowing',
     },
     'NEXT_UNREAD_TOPIC': {
         'keys': ['n'],
         'help_text': 'Next unread topic',
-        'key_category': 'navigation',
+        'key_category': 'narrowing',
     },
     'NEXT_UNREAD_PM': {
         'keys': ['p'],
         'help_text': 'Next unread direct message',
-        'key_category': 'navigation',
+        'key_category': 'narrowing',
     },
     'SEARCH_PEOPLE': {
         'keys': ['w'],
@@ -296,16 +296,16 @@ KEY_BINDINGS: Dict[str, KeyBinding] = {
     },
     'EDIT_HISTORY': {
         'keys': ['e'],
-        'help_text': 'Show/hide edit history (from message information)',
+        'help_text': 'Show/hide edit history',
         'excluded_from_random_tips': True,
-        'key_category': 'msg_actions',
+        'key_category': 'msg_info',
     },
     'VIEW_IN_BROWSER': {
         'keys': ['v'],
         'help_text':
-            'View current message in browser (from message information)',
+            'View current message in browser',
         'excluded_from_random_tips': True,
-        'key_category': 'msg_actions',
+        'key_category': 'msg_info',
     },
     'STREAM_INFO': {
         'keys': ['i'],
@@ -314,16 +314,16 @@ KEY_BINDINGS: Dict[str, KeyBinding] = {
     },
     'STREAM_MEMBERS': {
         'keys': ['m'],
-        'help_text': 'Show/hide stream members (from stream information)',
+        'help_text': 'Show/hide stream members',
         'excluded_from_random_tips': True,
-        'key_category': 'stream_list',
+        'key_category': 'stream_info',
     },
     'COPY_STREAM_EMAIL': {
         'keys': ['c'],
         'help_text':
-            'Copy stream email to clipboard (from stream information)',
+            'Copy stream email to clipboard',
         'excluded_from_random_tips': True,
-        'key_category': 'stream_list',
+        'key_category': 'stream_info',
     },
     'REDRAW': {
         'keys': ['ctrl l'],
@@ -337,8 +337,16 @@ KEY_BINDINGS: Dict[str, KeyBinding] = {
     },
     'USER_INFO': {
         'keys': ['i'],
-        'help_text': 'Show/hide user information (from users list)',
-        'key_category': 'general',
+        'help_text': 'Show/hide user information',
+        'key_category': 'user_list',
+    },
+    'NARROW_TO_USER_PM': {
+        # Added to clarify functionality of button activation,
+        # as opposed to opening user profile or other effects.
+        # Implementation uses ACTIVATE_BUTTON command.
+        'keys': ['enter'],
+        'help_text': 'Narrow to direct messages with user',
+        'key_category': 'user_list',
     },
     'BEGINNING_OF_LINE': {
         'keys': ['ctrl a', 'home'],
@@ -428,15 +436,20 @@ KEY_BINDINGS: Dict[str, KeyBinding] = {
         'help_text': 'Insert new line',
         'key_category': 'compose_box',
     },
+    'OPEN_EXTERNAL_EDITOR': {
+        'keys': ['ctrl o'],
+        'help_text': 'Open an external editor to edit the message content',
+        'key_category': 'compose_box',
+    },
     'FULL_RENDERED_MESSAGE': {
         'keys': ['f'],
-        'help_text': 'Show/hide full rendered message (from message information)',
-        'key_category': 'msg_actions',
+        'help_text': 'Show/hide full rendered message',
+        'key_category': 'msg_info',
     },
     'FULL_RAW_MESSAGE': {
         'keys': ['r'],
-        'help_text': 'Show/hide full raw message (from message information)',
-        'key_category': 'msg_actions',
+        'help_text': 'Show/hide full raw message',
+        'key_category': 'msg_info',
     },
     'NEW_HINT': {
         'keys': ['tab'],
@@ -449,13 +462,23 @@ KEY_BINDINGS: Dict[str, KeyBinding] = {
 HELP_CATEGORIES = {
     "general": "General",
     "navigation": "Navigation",
+    "narrowing": "Switching Messages View",
     "searching": "Searching",
     "msg_actions": "Message actions",
     "stream_list": "Stream list actions",
+    "user_list": "User list actions",
     "open_compose": "Begin composing a message",
     "compose_box": "Writing a message",
     "editor_navigation": "Editor: Navigation",
     "editor_text_manipulation": "Editor: Text Manipulation",
+    "stream_info": (
+        f"Stream information (press {KEY_BINDINGS['STREAM_INFO']['keys'][0]}"
+        f" to view info of a stream)"
+    ),
+    "msg_info": (
+        f"Message information (press {KEY_BINDINGS['MSG_INFO']['keys'][0]}"
+        f" to view info of a message)"
+    ),
 }
 
 ZT_TO_URWID_CMD_MAPPING = {
diff --git a/zulipterminal/config/themes.py b/zulipterminal/config/themes.py
index 7c6dfe5..d6fe406 100644
--- a/zulipterminal/config/themes.py
+++ b/zulipterminal/config/themes.py
@@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
 
 from pygments.token import STANDARD_TYPES, _TokenType
 
-from zulipterminal.config.color import term16
+from zulipterminal.config.color import Background, term16
 from zulipterminal.themes import gruvbox_dark, gruvbox_light, zt_blue, zt_dark, zt_light
 
 
@@ -165,16 +165,21 @@ def complete_and_incomplete_themes() -> Tuple[List[str], List[str]]:
         if getattr(theme, "STYLES", None)
         if set(theme.STYLES) == set(REQUIRED_STYLES)
         if getattr(theme, "META", None)
-        if set(theme.META) == set(REQUIRED_META)
-        for meta, conf in theme.META.items()
-        if set(conf) == set(REQUIRED_META.get(meta, {}))
+        if set(theme.META).issuperset(set(REQUIRED_META))
+        for meta, conf in REQUIRED_META.items()
+        if set(conf) == set(theme.META.get(meta, {}))
     }
     incomplete = set(THEMES) - complete
     return sorted(complete), sorted(incomplete)
 
 
-def generate_theme(theme_name: str, color_depth: int) -> ThemeSpec:
-    theme_module = THEMES[theme_name]
+def generate_theme(
+    name: str,
+    *,
+    color_depth: int,
+    transparent_background: bool,
+) -> ThemeSpec:
+    theme_module = THEMES[name]
 
     try:
         theme_colors = theme_module.Color
@@ -186,11 +191,14 @@ def generate_theme(theme_name: str, color_depth: int) -> ThemeSpec:
         theme_styles = theme_module.STYLES
     except AttributeError:
         raise MissingThemeAttributeError("STYLES") from None
-    urwid_theme = parse_themefile(theme_styles, color_depth)
 
     # META is not required, but if present should contain pygments data
     theme_meta = getattr(theme_module, "META", None)
     if theme_meta is not None:
+        # FIXME: Is META now required? Or only if Background.COLOR present?
+        # If used in styles and background is not specified, transparent by default!
+        background_color = theme_meta.get("background", Background.COLOR)
+
         pygments_data = theme_meta.get("pygments", None)
         if pygments_data is None:
             raise MissingThemeAttributeError('META["pygments"]') from None
@@ -199,8 +207,15 @@ def generate_theme(theme_name: str, color_depth: int) -> ThemeSpec:
                 raise MissingThemeAttributeError(f'META["pygments"]["{key}"]') from None
         pygments_styles = generate_pygments_styles(pygments_data)
     else:
+        background_color = Background.COLOR
         pygments_styles = []
 
+    urwid_theme = parse_themefile(
+        theme_styles,
+        color_depth,
+        background_color,
+        transparent_background,
+    )
     urwid_theme.extend(pygments_styles)
 
     return urwid_theme
@@ -232,11 +247,20 @@ def validate_colors(color_enum: Any, color_depth: int) -> None:
 
 
 def parse_themefile(
-    theme_styles: Dict[Optional[str], Tuple[Any, Any]], color_depth: int
+    theme_styles: Dict[Optional[str], Tuple[Any, Any]],
+    color_depth: int,
+    background_color: Any,
+    transparent_background: bool,
 ) -> ThemeSpec:
     urwid_theme = []
     for style_name, (fg_name, bg_name) in theme_styles.items():
         fg_code16, fg_code256, fg_code24, *fg_props = fg_name.value.split()
+
+        # Background.COLOR is transparent
+        # => Replace with background_color, unless transparency is requested
+        if bg_name == Background.COLOR and not transparent_background:
+            bg_name = background_color  # noqa: PLW2901  # overwrite loop variable
+
         bg_code16, bg_code256, bg_code24, *bg_props = bg_name.value.split()
 
         new_style: StyleSpec
diff --git a/zulipterminal/core.py b/zulipterminal/core.py
index 99d6cac..a23b159 100644
--- a/zulipterminal/core.py
+++ b/zulipterminal/core.py
@@ -69,7 +69,9 @@ class Controller:
         theme: ThemeSpec,
         color_depth: int,
         debug_path: Optional[str],
+        editor_command: str,
         in_explore_mode: bool,
+        transparency: bool,
         autohide: bool,
         notify: bool,
         exit_confirmation: bool,
@@ -78,10 +80,12 @@ class Controller:
         self.theme = theme
         self.color_depth = color_depth
         self.in_explore_mode = in_explore_mode
+        self.transparency_enabled = transparency
         self.autohide = autohide
         self.exit_confirmation = exit_confirmation
         self.notify_enabled = notify
         self.maximum_footlinks = maximum_footlinks
+        self.editor_command = editor_command
 
         self.debug_path = debug_path
 
@@ -310,6 +314,7 @@ class Controller:
                 autohide_enabled=self.autohide,
                 maximum_footlinks=self.maximum_footlinks,
                 exit_confirmation_enabled=self.exit_confirmation,
+                transparency_enabled=self.transparency_enabled,
             ),
             "area:help",
         )
@@ -515,10 +520,17 @@ class Controller:
 
     def save_draft_confirmation_popup(self, draft: Composition) -> None:
         question = urwid.Text(
-            "Save this message as a draft? (This will overwrite the existing draft.)"
+            (
+                "bold",
+                "Save this message as a draft? "
+                "(This will overwrite the existing draft.)",
+            ),
+            "center",
         )
         save_draft = partial(self.model.save_draft, draft)
-        self.loop.widget = PopUpConfirmationView(self, question, save_draft)
+        self.loop.widget = PopUpConfirmationView(
+            self, question, save_draft, location="center"
+        )
 
     def stream_muting_confirmation_popup(
         self, stream_id: int, stream_name: str
@@ -532,6 +544,21 @@ class Controller:
         mute_this_stream = partial(self.model.toggle_stream_muted_status, stream_id)
         self.loop.widget = PopUpConfirmationView(self, question, mute_this_stream)
 
+    def exit_compose_confirmation_popup(self) -> None:
+        question = urwid.Text(
+            (
+                "bold",
+                "Please confirm that you wish to exit the compose box.\n"
+                "(You can save the message as a draft upon returning to compose)",
+            ),
+            "center",
+        )
+        write_box = self.view.write_box
+        popup_view = PopUpConfirmationView(
+            self, question, write_box.exit_compose_box, location="center"
+        )
+        self.loop.widget = popup_view
+
     def copy_to_clipboard(self, text: str, text_category: str) -> None:
         try:
             pyperclip.copy(text)
diff --git a/zulipterminal/themes/colors_gruvbox.py b/zulipterminal/themes/colors_gruvbox.py
index 0bf7266..79d2e2f 100644
--- a/zulipterminal/themes/colors_gruvbox.py
+++ b/zulipterminal/themes/colors_gruvbox.py
@@ -21,7 +21,6 @@ from zulipterminal.config.color import color_properties
 
 class GruvBoxColor(Enum):
     # color          =  16code          256code   24code
-    DEFAULT          = 'default         default   default'
 
     # Only or primarily dark mode - grayscales
     # - generally background
diff --git a/zulipterminal/themes/gruvbox_dark.py b/zulipterminal/themes/gruvbox_dark.py
index 1abd779..4492b75 100644
--- a/zulipterminal/themes/gruvbox_dark.py
+++ b/zulipterminal/themes/gruvbox_dark.py
@@ -11,6 +11,7 @@ For further details on themefiles look at the theme contribution guide
 
 from pygments.styles.solarized import SolarizedDarkStyle
 
+from zulipterminal.config.color import Background
 from zulipterminal.themes.colors_gruvbox import DefaultBoldColor as Color
 
 
@@ -18,58 +19,58 @@ from zulipterminal.themes.colors_gruvbox import DefaultBoldColor as Color
 
 STYLES = {
     # style_name       :  foreground                   background
-    None               : (Color.LIGHT2,                Color.DARK0_HARD),
+    None               : (Color.LIGHT2,                Background.COLOR),
     'selected'         : (Color.DARK0_HARD,            Color.NEUTRAL_BLUE),
     'msg_selected'     : (Color.DARK0_HARD,            Color.NEUTRAL_BLUE),
     'header'           : (Color.NEUTRAL_BLUE,          Color.BRIGHT_BLUE),
     'general_narrow'   : (Color.DARK0_HARD,            Color.BRIGHT_BLUE),
-    'general_bar'      : (Color.LIGHT2,                Color.DARK0_HARD),
-    'msg_sender'       : (Color.NEUTRAL_YELLOW__BOLD,  Color.DARK0_HARD),
-    'unread'           : (Color.NEUTRAL_PURPLE,        Color.DARK0_HARD),
-    'user_active'      : (Color.BRIGHT_GREEN,          Color.DARK0_HARD),
-    'user_idle'        : (Color.NEUTRAL_YELLOW,        Color.DARK0_HARD),
-    'user_offline'     : (Color.LIGHT2,                Color.DARK0_HARD),
-    'user_inactive'    : (Color.LIGHT2,                Color.DARK0_HARD),
-    'user_bot'         : (Color.LIGHT2,                Color.DARK0_HARD),
-    'title'            : (Color.LIGHT2__BOLD,          Color.DARK0_HARD),
-    'column_title'     : (Color.LIGHT2__BOLD,          Color.DARK0_HARD),
-    'time'             : (Color.BRIGHT_BLUE,           Color.DARK0_HARD),
+    'general_bar'      : (Color.LIGHT2,                Background.COLOR),
+    'msg_sender'       : (Color.NEUTRAL_YELLOW__BOLD,  Background.COLOR),
+    'unread'           : (Color.NEUTRAL_PURPLE,        Background.COLOR),
+    'user_active'      : (Color.BRIGHT_GREEN,          Background.COLOR),
+    'user_idle'        : (Color.NEUTRAL_YELLOW,        Background.COLOR),
+    'user_offline'     : (Color.LIGHT2,                Background.COLOR),
+    'user_inactive'    : (Color.LIGHT2,                Background.COLOR),
+    'user_bot'         : (Color.LIGHT2,                Background.COLOR),
+    'title'            : (Color.LIGHT2__BOLD,          Background.COLOR),
+    'column_title'     : (Color.LIGHT2__BOLD,          Background.COLOR),
+    'time'             : (Color.BRIGHT_BLUE,           Background.COLOR),
     'bar'              : (Color.LIGHT2,                Color.GRAY_244),
-    'msg_emoji'        : (Color.NEUTRAL_PURPLE,        Color.DARK0_HARD),
-    'reaction'         : (Color.NEUTRAL_PURPLE__BOLD,  Color.DARK0_HARD),
+    'msg_emoji'        : (Color.NEUTRAL_PURPLE,        Background.COLOR),
+    'reaction'         : (Color.NEUTRAL_PURPLE__BOLD,  Background.COLOR),
     'reaction_mine'    : (Color.DARK0_HARD,            Color.NEUTRAL_PURPLE),
     'msg_heading'      : (Color.DARK0_HARD__BOLD,      Color.BRIGHT_GREEN),
     'msg_math'         : (Color.DARK0_HARD,            Color.GRAY_244),
-    'msg_mention'      : (Color.BRIGHT_RED__BOLD,      Color.DARK0_HARD),
-    'msg_link'         : (Color.BRIGHT_BLUE,           Color.DARK0_HARD),
-    'msg_link_index'   : (Color.BRIGHT_BLUE__BOLD,     Color.DARK0_HARD),
-    'msg_quote'        : (Color.NEUTRAL_YELLOW,        Color.DARK0_HARD),
-    'msg_bold'         : (Color.LIGHT2__BOLD,          Color.DARK0_HARD),
+    'msg_mention'      : (Color.BRIGHT_RED__BOLD,      Background.COLOR),
+    'msg_link'         : (Color.BRIGHT_BLUE,           Background.COLOR),
+    'msg_link_index'   : (Color.BRIGHT_BLUE__BOLD,     Background.COLOR),
+    'msg_quote'        : (Color.NEUTRAL_YELLOW,        Background.COLOR),
+    'msg_bold'         : (Color.LIGHT2__BOLD,          Background.COLOR),
     'msg_time'         : (Color.DARK0_HARD,            Color.LIGHT2),
     'footer'           : (Color.DARK0_HARD,            Color.LIGHT4),
-    'footer_contrast'  : (Color.LIGHT2,                Color.DARK0_HARD),
-    'starred'          : (Color.BRIGHT_RED__BOLD,      Color.DARK0_HARD),
-    'unread_count'     : (Color.NEUTRAL_YELLOW,        Color.DARK0_HARD),
-    'starred_count'    : (Color.LIGHT4,                Color.DARK0_HARD),
-    'table_head'       : (Color.LIGHT2__BOLD,          Color.DARK0_HARD),
+    'footer_contrast'  : (Color.LIGHT2,                Background.COLOR),
+    'starred'          : (Color.BRIGHT_RED__BOLD,      Background.COLOR),
+    'unread_count'     : (Color.NEUTRAL_YELLOW,        Background.COLOR),
+    'starred_count'    : (Color.LIGHT4,                Background.COLOR),
+    'table_head'       : (Color.LIGHT2__BOLD,          Background.COLOR),
     'filter_results'   : (Color.DARK0_HARD,            Color.BRIGHT_GREEN),
     'edit_topic'       : (Color.DARK0_HARD,            Color.GRAY_244),
     'edit_tag'         : (Color.DARK0_HARD,            Color.GRAY_244),
-    'edit_author'      : (Color.NEUTRAL_YELLOW,        Color.DARK0_HARD),
-    'edit_time'        : (Color.BRIGHT_BLUE,           Color.DARK0_HARD),
-    'current_user'     : (Color.LIGHT2,                Color.DARK0_HARD),
-    'muted'            : (Color.BRIGHT_BLUE,           Color.DARK0_HARD),
-    'popup_border'     : (Color.LIGHT2,                Color.DARK0_HARD),
-    'popup_category'   : (Color.BRIGHT_BLUE__BOLD,     Color.DARK0_HARD),
+    'edit_author'      : (Color.NEUTRAL_YELLOW,        Background.COLOR),
+    'edit_time'        : (Color.BRIGHT_BLUE,           Background.COLOR),
+    'current_user'     : (Color.LIGHT2,                Background.COLOR),
+    'muted'            : (Color.BRIGHT_BLUE,           Background.COLOR),
+    'popup_border'     : (Color.LIGHT2,                Background.COLOR),
+    'popup_category'   : (Color.BRIGHT_BLUE__BOLD,     Background.COLOR),
     'popup_contrast'   : (Color.DARK0_HARD,            Color.GRAY_244),
-    'popup_important'  : (Color.BRIGHT_RED__BOLD,      Color.DARK0_HARD),
-    'widget_disabled'  : (Color.GRAY_244,              Color.DARK0_HARD),
+    'popup_important'  : (Color.BRIGHT_RED__BOLD,      Background.COLOR),
+    'widget_disabled'  : (Color.GRAY_244,              Background.COLOR),
     'area:help'        : (Color.DARK0_HARD,            Color.BRIGHT_GREEN),
     'area:msg'         : (Color.DARK0_HARD,            Color.NEUTRAL_PURPLE),
     'area:stream'      : (Color.DARK0_HARD,            Color.BRIGHT_BLUE),
     'area:error'       : (Color.DARK0_HARD,            Color.BRIGHT_RED),
     'area:user'        : (Color.DARK0_HARD,            Color.BRIGHT_YELLOW),
-    'search_error'     : (Color.BRIGHT_RED,            Color.DARK0_HARD),
+    'search_error'     : (Color.BRIGHT_RED,            Background.COLOR),
     'task:success'     : (Color.DARK0_HARD,            Color.BRIGHT_GREEN),
     'task:error'       : (Color.DARK0_HARD,            Color.BRIGHT_RED),
     'task:warning'     : (Color.DARK0_HARD,            Color.NEUTRAL_PURPLE),
@@ -77,6 +78,7 @@ STYLES = {
 }
 
 META = {
+    'background': Color.DARK0_HARD,
     'pygments': {
         'styles'    : SolarizedDarkStyle().styles,
         'background': 'h236',
diff --git a/zulipterminal/themes/gruvbox_light.py b/zulipterminal/themes/gruvbox_light.py
index e477a9f..e2c09ae 100644
--- a/zulipterminal/themes/gruvbox_light.py
+++ b/zulipterminal/themes/gruvbox_light.py
@@ -10,6 +10,7 @@ For further details on themefiles look at the theme contribution guide
 """
 from pygments.styles.solarized import SolarizedLightStyle
 
+from zulipterminal.config.color import Background
 from zulipterminal.themes.colors_gruvbox import DefaultBoldColor as Color
 
 
@@ -17,58 +18,58 @@ from zulipterminal.themes.colors_gruvbox import DefaultBoldColor as Color
 
 STYLES = {
     # style_name       :  foreground                   background
-    None               : (Color.DARK2,                  Color.LIGHT0_HARD),
+    None               : (Color.DARK2,                  Background.COLOR),
     'selected'         : (Color.LIGHT0_HARD,            Color.NEUTRAL_BLUE),
     'msg_selected'     : (Color.LIGHT0_HARD,            Color.NEUTRAL_BLUE),
     'header'           : (Color.NEUTRAL_BLUE,           Color.FADED_BLUE),
     'general_narrow'   : (Color.LIGHT0_HARD,            Color.FADED_BLUE),
-    'general_bar'      : (Color.DARK2,                  Color.LIGHT0_HARD),
-    'msg_sender'       : (Color.NEUTRAL_YELLOW,         Color.LIGHT0_HARD),
-    'unread'           : (Color.NEUTRAL_PURPLE,         Color.LIGHT0_HARD),
-    'user_active'      : (Color.FADED_GREEN,            Color.LIGHT0_HARD),
-    'user_idle'        : (Color.NEUTRAL_YELLOW,         Color.LIGHT0_HARD),
-    'user_offline'     : (Color.DARK2,                  Color.LIGHT0_HARD),
-    'user_inactive'    : (Color.DARK2,                  Color.LIGHT0_HARD),
-    'user_bot'         : (Color.DARK2,                  Color.LIGHT0_HARD),
-    'title'            : (Color.DARK2__BOLD,            Color.LIGHT0_HARD),
-    'column_title'     : (Color.DARK2__BOLD,            Color.LIGHT0_HARD),
-    'time'             : (Color.FADED_BLUE,             Color.LIGHT0_HARD),
+    'general_bar'      : (Color.DARK2,                  Background.COLOR),
+    'msg_sender'       : (Color.NEUTRAL_YELLOW,         Background.COLOR),
+    'unread'           : (Color.NEUTRAL_PURPLE,         Background.COLOR),
+    'user_active'      : (Color.FADED_GREEN,            Background.COLOR),
+    'user_idle'        : (Color.NEUTRAL_YELLOW,         Background.COLOR),
+    'user_offline'     : (Color.DARK2,                  Background.COLOR),
+    'user_inactive'    : (Color.DARK2,                  Background.COLOR),
+    'user_bot'         : (Color.DARK2,                  Background.COLOR),
+    'title'            : (Color.DARK2__BOLD,            Background.COLOR),
+    'column_title'     : (Color.DARK2__BOLD,            Background.COLOR),
+    'time'             : (Color.FADED_BLUE,             Background.COLOR),
     'bar'              : (Color.DARK2,                  Color.GRAY_245),
-    'msg_emoji'        : (Color.NEUTRAL_PURPLE,         Color.LIGHT0_HARD),
-    'reaction'         : (Color.NEUTRAL_PURPLE__BOLD,   Color.LIGHT0_HARD),
+    'msg_emoji'        : (Color.NEUTRAL_PURPLE,         Background.COLOR),
+    'reaction'         : (Color.NEUTRAL_PURPLE__BOLD,   Background.COLOR),
     'reaction_mine'    : (Color.LIGHT0_HARD,            Color.NEUTRAL_PURPLE),
     'msg_heading'      : (Color.LIGHT0_HARD__BOLD,      Color.FADED_GREEN),
     'msg_math'         : (Color.LIGHT0_HARD,            Color.GRAY_245),
-    'msg_mention'      : (Color.FADED_RED__BOLD,        Color.LIGHT0_HARD),
-    'msg_link'         : (Color.FADED_BLUE,             Color.LIGHT0_HARD),
-    'msg_link_index'   : (Color.FADED_BLUE__BOLD,       Color.LIGHT0_HARD),
-    'msg_quote'        : (Color.NEUTRAL_YELLOW,         Color.LIGHT0_HARD),
-    'msg_bold'         : (Color.DARK2__BOLD,            Color.LIGHT0_HARD),
+    'msg_mention'      : (Color.FADED_RED__BOLD,        Background.COLOR),
+    'msg_link'         : (Color.FADED_BLUE,             Background.COLOR),
+    'msg_link_index'   : (Color.FADED_BLUE__BOLD,       Background.COLOR),
+    'msg_quote'        : (Color.NEUTRAL_YELLOW,         Background.COLOR),
+    'msg_bold'         : (Color.DARK2__BOLD,            Background.COLOR),
     'msg_time'         : (Color.LIGHT0_HARD,            Color.DARK2),
     'footer'           : (Color.LIGHT0_HARD,            Color.DARK4),
-    'footer_contrast'  : (Color.DARK2,                  Color.LIGHT0_HARD),
-    'starred'          : (Color.FADED_RED__BOLD,        Color.LIGHT0_HARD),
-    'unread_count'     : (Color.NEUTRAL_YELLOW,         Color.LIGHT0_HARD),
-    'starred_count'    : (Color.DARK4,                  Color.LIGHT0_HARD),
-    'table_head'       : (Color.DARK2__BOLD,            Color.LIGHT0_HARD),
+    'footer_contrast'  : (Color.DARK2,                  Background.COLOR),
+    'starred'          : (Color.FADED_RED__BOLD,        Background.COLOR),
+    'unread_count'     : (Color.NEUTRAL_YELLOW,         Background.COLOR),
+    'starred_count'    : (Color.DARK4,                  Background.COLOR),
+    'table_head'       : (Color.DARK2__BOLD,            Background.COLOR),
     'filter_results'   : (Color.LIGHT0_HARD,            Color.FADED_GREEN),
     'edit_topic'       : (Color.LIGHT0_HARD,            Color.GRAY_245),
     'edit_tag'         : (Color.LIGHT0_HARD,            Color.GRAY_245),
-    'edit_author'      : (Color.NEUTRAL_YELLOW,         Color.LIGHT0_HARD),
-    'edit_time'        : (Color.FADED_BLUE,             Color.LIGHT0_HARD),
-    'current_user'     : (Color.DARK2,                  Color.LIGHT0_HARD),
-    'muted'            : (Color.FADED_BLUE,             Color.LIGHT0_HARD),
-    'popup_border'     : (Color.DARK2,                  Color.LIGHT0_HARD),
-    'popup_category'   : (Color.FADED_BLUE__BOLD,       Color.LIGHT0_HARD),
+    'edit_author'      : (Color.NEUTRAL_YELLOW,         Background.COLOR),
+    'edit_time'        : (Color.FADED_BLUE,             Background.COLOR),
+    'current_user'     : (Color.DARK2,                  Background.COLOR),
+    'muted'            : (Color.FADED_BLUE,             Background.COLOR),
+    'popup_border'     : (Color.DARK2,                  Background.COLOR),
+    'popup_category'   : (Color.FADED_BLUE__BOLD,       Background.COLOR),
     'popup_contrast'   : (Color.LIGHT0_HARD,            Color.GRAY_245),
-    'popup_important'  : (Color.FADED_RED__BOLD,        Color.LIGHT0_HARD),
-    'widget_disabled'  : (Color.GRAY_245,               Color.LIGHT0_HARD),
+    'popup_important'  : (Color.FADED_RED__BOLD,        Background.COLOR),
+    'widget_disabled'  : (Color.GRAY_245,               Background.COLOR),
     'area:help'        : (Color.LIGHT0_HARD,            Color.FADED_GREEN),
     'area:msg'         : (Color.LIGHT0_HARD,            Color.NEUTRAL_PURPLE),
     'area:stream'      : (Color.LIGHT0_HARD,            Color.FADED_BLUE),
     'area:error'       : (Color.LIGHT0_HARD,            Color.FADED_RED),
     'area:user'        : (Color.LIGHT0_HARD,            Color.FADED_YELLOW),
-    'search_error'     : (Color.FADED_RED,              Color.LIGHT0_HARD),
+    'search_error'     : (Color.FADED_RED,              Background.COLOR),
     'task:success'     : (Color.LIGHT0_HARD,            Color.FADED_GREEN),
     'task:error'       : (Color.LIGHT0_HARD,            Color.FADED_RED),
     'task:warning'     : (Color.LIGHT0_HARD,            Color.NEUTRAL_PURPLE),
@@ -76,6 +77,7 @@ STYLES = {
 }
 
 META = {
+    'background': Color.LIGHT0_HARD,
     'pygments': {
         'styles'    : SolarizedLightStyle().styles,
         'background': '#ffffcc',
diff --git a/zulipterminal/themes/zt_blue.py b/zulipterminal/themes/zt_blue.py
index eb99c8d..0a43fe9 100644
--- a/zulipterminal/themes/zt_blue.py
+++ b/zulipterminal/themes/zt_blue.py
@@ -6,35 +6,36 @@ For further details on themefiles look at the theme contribution guide
 """
 from pygments.styles.zenburn import ZenburnStyle
 
+from zulipterminal.config.color import Background
 from zulipterminal.config.color import DefaultBoldColor as Color
 
 
 # fmt: off
 STYLES = {
     # style_name      :  foreground                 background
-    None              : (Color.BLACK,               Color.LIGHT_BLUE),
+    None              : (Color.BLACK,               Background.COLOR),
     'selected'        : (Color.BLACK,               Color.LIGHT_GRAY),
     'msg_selected'    : (Color.BLACK,               Color.LIGHT_GRAY),
     'header'          : (Color.BLACK,               Color.DARK_BLUE),
     'general_narrow'  : (Color.WHITE,               Color.DARK_BLUE),
-    'general_bar'     : (Color.DARK_BLUE,           Color.LIGHT_BLUE),
-    'msg_sender'      : (Color.DARK_RED,            Color.LIGHT_BLUE),
-    'unread'          : (Color.LIGHT_GRAY,          Color.LIGHT_BLUE),
-    'user_active'     : (Color.LIGHT_GREEN__BOLD,   Color.LIGHT_BLUE),
-    'user_idle'       : (Color.DARK_GRAY,           Color.LIGHT_BLUE),
-    'user_offline'    : (Color.BLACK,               Color.LIGHT_BLUE),
-    'user_inactive'   : (Color.BLACK,               Color.LIGHT_BLUE),
-    'user_bot'        : (Color.BLACK,               Color.LIGHT_BLUE),
+    'general_bar'     : (Color.DARK_BLUE,           Background.COLOR),
+    'msg_sender'      : (Color.DARK_RED,            Background.COLOR),
+    'unread'          : (Color.LIGHT_GRAY,          Background.COLOR),
+    'user_active'     : (Color.LIGHT_GREEN__BOLD,   Background.COLOR),
+    'user_idle'       : (Color.DARK_GRAY,           Background.COLOR),
+    'user_offline'    : (Color.BLACK,               Background.COLOR),
+    'user_inactive'   : (Color.BLACK,               Background.COLOR),
+    'user_bot'        : (Color.BLACK,               Background.COLOR),
     'title'           : (Color.WHITE__BOLD,         Color.DARK_BLUE),
-    'column_title'    : (Color.BLACK__BOLD,         Color.LIGHT_BLUE),
-    'time'            : (Color.DARK_BLUE,           Color.LIGHT_BLUE),
+    'column_title'    : (Color.BLACK__BOLD,         Background.COLOR),
+    'time'            : (Color.DARK_BLUE,           Background.COLOR),
     'bar'             : (Color.WHITE,               Color.DARK_BLUE),
-    'msg_emoji'       : (Color.DARK_MAGENTA,        Color.LIGHT_BLUE),
-    'reaction'        : (Color.DARK_MAGENTA__BOLD,  Color.LIGHT_BLUE),
+    'msg_emoji'       : (Color.DARK_MAGENTA,        Background.COLOR),
+    'reaction'        : (Color.DARK_MAGENTA__BOLD,  Background.COLOR),
     'reaction_mine'   : (Color.LIGHT_BLUE,          Color.DARK_MAGENTA),
     'msg_heading'     : (Color.WHITE__BOLD,         Color.BLACK),
     'msg_math'        : (Color.LIGHT_GRAY,          Color.DARK_GRAY),
-    'msg_mention'     : (Color.LIGHT_RED__BOLD,     Color.LIGHT_BLUE),
+    'msg_mention'     : (Color.LIGHT_RED__BOLD,     Background.COLOR),
     'msg_link'        : (Color.DARK_BLUE,           Color.LIGHT_GRAY),
     'msg_link_index'  : (Color.DARK_BLUE__BOLD,     Color.LIGHT_GRAY),
     'msg_quote'       : (Color.BROWN,               Color.DARK_BLUE),
@@ -42,28 +43,28 @@ STYLES = {
     'msg_time'        : (Color.DARK_BLUE,           Color.WHITE),
     'footer'          : (Color.WHITE,               Color.DARK_GRAY),
     'footer_contrast' : (Color.BLACK,               Color.WHITE),
-    'starred'         : (Color.LIGHT_RED__BOLD,     Color.LIGHT_BLUE),
-    'unread_count'    : (Color.YELLOW,              Color.LIGHT_BLUE),
-    'starred_count'   : (Color.BLACK,               Color.LIGHT_BLUE),
-    'table_head'      : (Color.BLACK__BOLD,         Color.LIGHT_BLUE),
+    'starred'         : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'unread_count'    : (Color.YELLOW,              Background.COLOR),
+    'starred_count'   : (Color.BLACK,               Background.COLOR),
+    'table_head'      : (Color.BLACK__BOLD,         Background.COLOR),
     'filter_results'  : (Color.WHITE,               Color.DARK_GREEN),
     'edit_topic'      : (Color.WHITE,               Color.DARK_BLUE),
     'edit_tag'        : (Color.WHITE,               Color.DARK_BLUE),
-    'edit_author'     : (Color.DARK_GRAY,           Color.LIGHT_BLUE),
-    'edit_time'       : (Color.DARK_BLUE,           Color.LIGHT_BLUE),
-    'current_user'    : (Color.LIGHT_GRAY,          Color.LIGHT_BLUE),
-    'muted'           : (Color.LIGHT_GRAY,          Color.LIGHT_BLUE),
-    'popup_border'    : (Color.WHITE,               Color.LIGHT_BLUE),
-    'popup_category'  : (Color.LIGHT_GRAY__BOLD,    Color.LIGHT_BLUE),
+    'edit_author'     : (Color.DARK_GRAY,           Background.COLOR),
+    'edit_time'       : (Color.DARK_BLUE,           Background.COLOR),
+    'current_user'    : (Color.LIGHT_GRAY,          Background.COLOR),
+    'muted'           : (Color.LIGHT_GRAY,          Background.COLOR),
+    'popup_border'    : (Color.WHITE,               Background.COLOR),
+    'popup_category'  : (Color.LIGHT_GRAY__BOLD,    Background.COLOR),
     'popup_contrast'  : (Color.WHITE,               Color.DARK_BLUE),
-    'popup_important' : (Color.LIGHT_RED__BOLD,     Color.LIGHT_BLUE),
-    'widget_disabled' : (Color.DARK_GRAY,           Color.LIGHT_BLUE),
+    'popup_important' : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'widget_disabled' : (Color.DARK_GRAY,           Background.COLOR),
     'area:help'       : (Color.WHITE,               Color.DARK_GREEN),
     'area:stream'     : (Color.WHITE,               Color.DARK_CYAN),
     'area:msg'        : (Color.WHITE,               Color.BROWN),
     'area:error'      : (Color.WHITE,               Color.DARK_RED),
     'area:user'       : (Color.WHITE,               Color.DARK_BLUE),
-    'search_error'    : (Color.LIGHT_RED,           Color.LIGHT_BLUE),
+    'search_error'    : (Color.LIGHT_RED,           Background.COLOR),
     'task:success'    : (Color.WHITE,               Color.DARK_GREEN),
     'task:error'      : (Color.WHITE,               Color.DARK_RED),
     'task:warning'    : (Color.WHITE,               Color.BROWN),
@@ -71,6 +72,7 @@ STYLES = {
 }
 
 META = {
+    'background': Color.LIGHT_BLUE,
     'pygments': {
         'styles'    : ZenburnStyle().styles,
         'background': 'h25',
diff --git a/zulipterminal/themes/zt_dark.py b/zulipterminal/themes/zt_dark.py
index 69a5f4a..447abd7 100644
--- a/zulipterminal/themes/zt_dark.py
+++ b/zulipterminal/themes/zt_dark.py
@@ -6,64 +6,65 @@ For further details on themefiles look at the theme contribution guide.
 """
 from pygments.styles.material import MaterialStyle
 
+from zulipterminal.config.color import Background
 from zulipterminal.config.color import DefaultBoldColor as Color
 
 
 # fmt: off
 STYLES = {
     # style_name      :  foreground                 background
-    None              : (Color.WHITE,               Color.BLACK),
+    None              : (Color.WHITE,               Background.COLOR),
     'selected'        : (Color.WHITE,               Color.DARK_BLUE),
     'msg_selected'    : (Color.WHITE,               Color.DARK_BLUE),
     'header'          : (Color.DARK_CYAN,           Color.DARK_BLUE),
     'general_narrow'  : (Color.WHITE,               Color.DARK_BLUE),
-    'general_bar'     : (Color.WHITE,               Color.BLACK),
-    'msg_sender'      : (Color.YELLOW__BOLD,        Color.BLACK),
-    'unread'          : (Color.DARK_BLUE,           Color.BLACK),
-    'user_active'     : (Color.LIGHT_GREEN,         Color.BLACK),
-    'user_idle'       : (Color.YELLOW,              Color.BLACK),
-    'user_offline'    : (Color.WHITE,               Color.BLACK),
-    'user_inactive'   : (Color.WHITE,               Color.BLACK),
-    'user_bot'        : (Color.WHITE,               Color.BLACK),
-    'title'           : (Color.WHITE__BOLD,         Color.BLACK),
-    'column_title'    : (Color.WHITE__BOLD,         Color.BLACK),
-    'time'            : (Color.LIGHT_BLUE,          Color.BLACK),
+    'general_bar'     : (Color.WHITE,               Background.COLOR),
+    'msg_sender'      : (Color.YELLOW__BOLD,        Background.COLOR),
+    'unread'          : (Color.DARK_BLUE,           Background.COLOR),
+    'user_active'     : (Color.LIGHT_GREEN,         Background.COLOR),
+    'user_idle'       : (Color.YELLOW,              Background.COLOR),
+    'user_offline'    : (Color.WHITE,               Background.COLOR),
+    'user_inactive'   : (Color.WHITE,               Background.COLOR),
+    'user_bot'        : (Color.WHITE,               Background.COLOR),
+    'title'           : (Color.WHITE__BOLD,         Background.COLOR),
+    'column_title'    : (Color.WHITE__BOLD,         Background.COLOR),
+    'time'            : (Color.LIGHT_BLUE,          Background.COLOR),
     'bar'             : (Color.WHITE,               Color.DARK_GRAY),
-    'msg_emoji'       : (Color.LIGHT_MAGENTA,       Color.BLACK),
-    'reaction'        : (Color.LIGHT_MAGENTA__BOLD, Color.BLACK),
+    'msg_emoji'       : (Color.LIGHT_MAGENTA,       Background.COLOR),
+    'reaction'        : (Color.LIGHT_MAGENTA__BOLD, Background.COLOR),
     'reaction_mine'   : (Color.BLACK,               Color.LIGHT_MAGENTA),
     'msg_heading'     : (Color.LIGHT_CYAN__BOLD,    Color.DARK_MAGENTA),
     'msg_math'        : (Color.LIGHT_GRAY,          Color.DARK_GRAY),
-    'msg_mention'     : (Color.LIGHT_RED__BOLD,     Color.BLACK),
-    'msg_link'        : (Color.LIGHT_BLUE,          Color.BLACK),
-    'msg_link_index'  : (Color.LIGHT_BLUE__BOLD,    Color.BLACK),
-    'msg_quote'       : (Color.BROWN,               Color.BLACK),
-    'msg_bold'        : (Color.WHITE__BOLD,         Color.BLACK),
+    'msg_mention'     : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'msg_link'        : (Color.LIGHT_BLUE,          Background.COLOR),
+    'msg_link_index'  : (Color.LIGHT_BLUE__BOLD,    Background.COLOR),
+    'msg_quote'       : (Color.BROWN,               Background.COLOR),
+    'msg_bold'        : (Color.WHITE__BOLD,         Background.COLOR),
     'msg_time'        : (Color.BLACK,               Color.WHITE),
     'footer'          : (Color.BLACK,               Color.LIGHT_GRAY),
-    'footer_contrast' : (Color.WHITE,               Color.BLACK),
-    'starred'         : (Color.LIGHT_RED__BOLD,     Color.BLACK),
-    'unread_count'    : (Color.YELLOW,              Color.BLACK),
-    'starred_count'   : (Color.LIGHT_GRAY,          Color.BLACK),
-    'table_head'      : (Color.WHITE__BOLD,         Color.BLACK),
+    'footer_contrast' : (Color.WHITE,               Background.COLOR),
+    'starred'         : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'unread_count'    : (Color.YELLOW,              Background.COLOR),
+    'starred_count'   : (Color.LIGHT_GRAY,          Background.COLOR),
+    'table_head'      : (Color.WHITE__BOLD,         Background.COLOR),
     'filter_results'  : (Color.WHITE,               Color.DARK_GREEN),
     'edit_topic'      : (Color.WHITE,               Color.DARK_GRAY),
     'edit_tag'        : (Color.WHITE,               Color.DARK_GRAY),
-    'edit_author'     : (Color.YELLOW,              Color.BLACK),
-    'edit_time'       : (Color.LIGHT_BLUE,          Color.BLACK),
-    'current_user'    : (Color.WHITE,               Color.BLACK),
-    'muted'           : (Color.LIGHT_BLUE,          Color.BLACK),
-    'popup_border'    : (Color.WHITE,               Color.BLACK),
-    'popup_category'  : (Color.LIGHT_BLUE__BOLD,    Color.BLACK),
+    'edit_author'     : (Color.YELLOW,              Background.COLOR),
+    'edit_time'       : (Color.LIGHT_BLUE,          Background.COLOR),
+    'current_user'    : (Color.WHITE,               Background.COLOR),
+    'muted'           : (Color.LIGHT_BLUE,          Background.COLOR),
+    'popup_border'    : (Color.WHITE,               Background.COLOR),
+    'popup_category'  : (Color.LIGHT_BLUE__BOLD,    Background.COLOR),
     'popup_contrast'  : (Color.WHITE,               Color.DARK_GRAY),
-    'popup_important' : (Color.LIGHT_RED__BOLD,     Color.BLACK),
-    'widget_disabled' : (Color.DARK_GRAY,           Color.BLACK),
+    'popup_important' : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'widget_disabled' : (Color.DARK_GRAY,           Background.COLOR),
     'area:help'       : (Color.WHITE,               Color.DARK_GREEN),
     'area:msg'        : (Color.WHITE,               Color.BROWN),
     'area:stream'     : (Color.WHITE,               Color.DARK_CYAN),
     'area:error'      : (Color.WHITE,               Color.DARK_RED),
     'area:user'       : (Color.WHITE,               Color.DARK_BLUE),
-    'search_error'    : (Color.LIGHT_RED,           Color.BLACK),
+    'search_error'    : (Color.LIGHT_RED,           Background.COLOR),
     'task:success'    : (Color.WHITE,               Color.DARK_GREEN),
     'task:error'      : (Color.WHITE,               Color.DARK_RED),
     'task:warning'    : (Color.WHITE,               Color.BROWN),
@@ -71,6 +72,7 @@ STYLES = {
 }
 
 META = {
+    'background': Color.BLACK,
     'pygments': {
         'styles'    : MaterialStyle().styles,
         'background': 'h235',
diff --git a/zulipterminal/themes/zt_light.py b/zulipterminal/themes/zt_light.py
index 6b0ee57..388a9c6 100644
--- a/zulipterminal/themes/zt_light.py
+++ b/zulipterminal/themes/zt_light.py
@@ -6,64 +6,65 @@ For further details on themefiles look at the theme contribution guide.
 """
 from pygments.styles.perldoc import PerldocStyle
 
+from zulipterminal.config.color import Background
 from zulipterminal.config.color import DefaultBoldColor as Color
 
 
 # fmt: off
 STYLES = {
     # style_name      :  foreground                 background
-    None              : (Color.BLACK,               Color.WHITE),
+    None              : (Color.BLACK,               Background.COLOR),
     'selected'        : (Color.BLACK,               Color.LIGHT_GREEN),
     'msg_selected'    : (Color.BLACK,               Color.LIGHT_GREEN),
     'header'          : (Color.WHITE,               Color.DARK_BLUE),
     'general_narrow'  : (Color.WHITE,               Color.DARK_BLUE),
-    'general_bar'     : (Color.DARK_BLUE,           Color.WHITE),
-    'msg_sender'      : (Color.DARK_GREEN,          Color.WHITE),
+    'general_bar'     : (Color.DARK_BLUE,           Background.COLOR),
+    'msg_sender'      : (Color.DARK_GREEN,          Background.COLOR),
     'unread'          : (Color.DARK_GRAY,           Color.LIGHT_GRAY),
-    'user_active'     : (Color.DARK_GREEN,          Color.WHITE),
-    'user_idle'       : (Color.DARK_BLUE,           Color.WHITE),
-    'user_offline'    : (Color.BLACK,               Color.WHITE),
-    'user_inactive'   : (Color.BLACK,               Color.WHITE),
-    'user_bot'        : (Color.BLACK,               Color.WHITE),
+    'user_active'     : (Color.DARK_GREEN,          Background.COLOR),
+    'user_idle'       : (Color.DARK_BLUE,           Background.COLOR),
+    'user_offline'    : (Color.BLACK,               Background.COLOR),
+    'user_inactive'   : (Color.BLACK,               Background.COLOR),
+    'user_bot'        : (Color.BLACK,               Background.COLOR),
     'title'           : (Color.WHITE__BOLD,         Color.DARK_GRAY),
-    'column_title'    : (Color.BLACK__BOLD,         Color.WHITE),
-    'time'            : (Color.DARK_BLUE,           Color.WHITE),
+    'column_title'    : (Color.BLACK__BOLD,         Background.COLOR),
+    'time'            : (Color.DARK_BLUE,           Background.COLOR),
     'bar'             : (Color.WHITE,               Color.DARK_GRAY),
-    'msg_emoji'       : (Color.LIGHT_MAGENTA,       Color.WHITE),
-    'reaction'        : (Color.LIGHT_MAGENTA__BOLD, Color.WHITE),
+    'msg_emoji'       : (Color.LIGHT_MAGENTA,       Background.COLOR),
+    'reaction'        : (Color.LIGHT_MAGENTA__BOLD, Background.COLOR),
     'reaction_mine'   : (Color.WHITE,               Color.LIGHT_MAGENTA),
     'msg_heading'     : (Color.WHITE__BOLD,         Color.DARK_RED),
     'msg_math'        : (Color.DARK_GRAY,           Color.LIGHT_GRAY),
-    'msg_mention'     : (Color.LIGHT_RED__BOLD,     Color.WHITE),
-    'msg_link'        : (Color.DARK_BLUE,           Color.WHITE),
-    'msg_link_index'  : (Color.DARK_BLUE__BOLD,     Color.WHITE),
+    'msg_mention'     : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'msg_link'        : (Color.DARK_BLUE,           Background.COLOR),
+    'msg_link_index'  : (Color.DARK_BLUE__BOLD,     Background.COLOR),
     'msg_quote'       : (Color.BLACK,               Color.BROWN),
     'msg_bold'        : (Color.WHITE__BOLD,         Color.DARK_GRAY),
     'msg_time'        : (Color.WHITE,               Color.DARK_GRAY),
     'footer'          : (Color.WHITE,               Color.DARK_GRAY),
-    'footer_contrast' : (Color.BLACK,               Color.WHITE),
-    'starred'         : (Color.LIGHT_RED__BOLD,     Color.WHITE),
-    'unread_count'    : (Color.DARK_BLUE__BOLD,     Color.WHITE),
-    'starred_count'   : (Color.BLACK,               Color.WHITE),
-    'table_head'      : (Color.BLACK__BOLD,         Color.WHITE),
+    'footer_contrast' : (Color.BLACK,               Background.COLOR),
+    'starred'         : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'unread_count'    : (Color.DARK_BLUE__BOLD,     Background.COLOR),
+    'starred_count'   : (Color.BLACK,               Background.COLOR),
+    'table_head'      : (Color.BLACK__BOLD,         Background.COLOR),
     'filter_results'  : (Color.WHITE,               Color.DARK_GREEN),
     'edit_topic'      : (Color.WHITE,               Color.DARK_GRAY),
     'edit_tag'        : (Color.WHITE,               Color.DARK_GRAY),
-    'edit_author'     : (Color.DARK_GREEN,          Color.WHITE),
-    'edit_time'       : (Color.DARK_BLUE,           Color.WHITE),
-    'current_user'    : (Color.DARK_GRAY,           Color.WHITE),
-    'muted'           : (Color.DARK_GRAY,           Color.WHITE),
-    'popup_border'    : (Color.BLACK,               Color.WHITE),
+    'edit_author'     : (Color.DARK_GREEN,          Background.COLOR),
+    'edit_time'       : (Color.DARK_BLUE,           Background.COLOR),
+    'current_user'    : (Color.DARK_GRAY,           Background.COLOR),
+    'muted'           : (Color.DARK_GRAY,           Background.COLOR),
+    'popup_border'    : (Color.BLACK,               Background.COLOR),
     'popup_category'  : (Color.DARK_GRAY__BOLD,     Color.LIGHT_GRAY),
     'popup_contrast'  : (Color.WHITE,               Color.DARK_GRAY),
-    'popup_important' : (Color.LIGHT_RED__BOLD,     Color.WHITE),
-    'widget_disabled' : (Color.LIGHT_GRAY,          Color.WHITE),
+    'popup_important' : (Color.LIGHT_RED__BOLD,     Background.COLOR),
+    'widget_disabled' : (Color.LIGHT_GRAY,          Background.COLOR),
     'area:help'       : (Color.BLACK,               Color.LIGHT_GREEN),
     'area:stream'     : (Color.BLACK,               Color.LIGHT_BLUE),
     'area:msg'        : (Color.BLACK,               Color.YELLOW),
     'area:error'      : (Color.BLACK,               Color.LIGHT_RED),
     'area:user'       : (Color.WHITE,               Color.DARK_BLUE),
-    'search_error'    : (Color.LIGHT_RED,           Color.WHITE),
+    'search_error'    : (Color.LIGHT_RED,           Background.COLOR),
     'task:success'    : (Color.BLACK,               Color.DARK_GREEN),
     'task:error'      : (Color.WHITE,               Color.DARK_RED),
     'task:warning'    : (Color.BLACK,               Color.YELLOW),
@@ -71,6 +72,7 @@ STYLES = {
 }
 
 META = {
+    'background': Color.WHITE,
     'pygments': {
         'styles'    : PerldocStyle().styles,
         'background': PerldocStyle().background_color,
diff --git a/zulipterminal/ui_tools/boxes.py b/zulipterminal/ui_tools/boxes.py
index fa3dc1f..1a479ca 100644
--- a/zulipterminal/ui_tools/boxes.py
+++ b/zulipterminal/ui_tools/boxes.py
@@ -3,14 +3,18 @@ UI boxes for entering text: WriteBox, MessageSearchBox, PanelSearchBox
 """
 
 import re
+import shlex
+import shutil
+import subprocess
 import unicodedata
 from collections import Counter
 from datetime import datetime, timedelta
+from tempfile import NamedTemporaryFile
 from time import sleep
 from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
 
 import urwid
-from typing_extensions import Literal
+from typing_extensions import Final, Literal
 from urwid_readline import ReadlineEdit
 
 from zulipterminal.api_types import Composition, PrivateComposition, StreamComposition
@@ -49,6 +53,11 @@ from zulipterminal.ui_tools.buttons import EditModeButton
 from zulipterminal.urwid_types import urwid_Size
 
 
+# This constant defines the maximum character length of a message
+# in the compose box that does not trigger a confirmation popup.
+MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP: Final = 15
+
+
 class _MessageEditState(NamedTuple):
     message_id: int
     old_topic: str
@@ -709,14 +718,28 @@ class WriteBox(urwid.Pile):
 
         return emoji_typeahead, emojis
 
+    def exit_compose_box(self) -> None:
+        self._set_default_footer_after_autocomplete()
+        self._set_compose_attributes_to_defaults()
+        self.view.controller.exit_editor_mode()
+        self.main_view(False)
+        self.view.middle_column.set_focus("body")
+
+    def _set_default_footer_after_autocomplete(self) -> None:
+        self.is_in_typeahead_mode = False
+        self.view.set_footer_text()
+
     def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
         if self.is_in_typeahead_mode and not (
             is_command_key("AUTOCOMPLETE", key)
             or is_command_key("AUTOCOMPLETE_REVERSE", key)
         ):
-            # set default footer when done with autocomplete
-            self.is_in_typeahead_mode = False
-            self.view.set_footer_text()
+            # As is, this exits autocomplete even if the user chooses to resume compose.
+            # Including a check for "EXIT_COMPOSE" in the above logic would avoid
+            # resetting the footer until actually exiting compose, but autocomplete
+            # itself does not continue on resume with such a solution.
+            # TODO: Fully implement resuming of autocomplete upon resuming compose.
+            self._set_default_footer_after_autocomplete()
 
         if is_command_key("SEND_MESSAGE", key):
             self.send_stop_typing_status()
@@ -799,14 +822,68 @@ class WriteBox(urwid.Pile):
                         "Cannot narrow to message without specifying recipients."
                     )
         elif is_command_key("EXIT_COMPOSE", key):
+            saved_draft = self.model.session_draft_message()
             self.send_stop_typing_status()
-            self._set_compose_attributes_to_defaults()
-            self.view.controller.exit_editor_mode()
-            self.main_view(False)
-            self.view.middle_column.set_focus("body")
+
+            compose_not_in_edit_mode = self.msg_edit_state is None
+            compose_box_content = self.msg_write_box.edit_text
+            saved_draft_content = saved_draft.get("content") if saved_draft else None
+
+            exceeds_max_length = (
+                len(compose_box_content) >= MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP
+            )
+            not_saved_as_draft = (
+                saved_draft is None or compose_box_content != saved_draft_content
+            )
+
+            if compose_not_in_edit_mode and exceeds_max_length and not_saved_as_draft:
+                self.view.controller.exit_compose_confirmation_popup()
+            else:
+                self.exit_compose_box()
         elif is_command_key("MARKDOWN_HELP", key):
             self.view.controller.show_markdown_help()
             return key
+        elif is_command_key("OPEN_EXTERNAL_EDITOR", key):
+            editor_command = self.view.controller.editor_command
+
+            # None would indicate for shlex.split to read sys.stdin for Python < 3.12
+            # It should never occur in practice
+            assert isinstance(editor_command, str)
+
+            if editor_command == "":
+                self.view.controller.report_error(
+                    "No external editor command specified; "
+                    "Set 'editor' in zuliprc file, or "
+                    "$ZULIP_EDITOR_COMMAND or $EDITOR environment variables."
+                )
+                return key
+
+            editor_command_line: List[str] = shlex.split(editor_command)
+            if not editor_command_line:
+                fullpath_program = None  # A command may be specified, but empty
+            else:
+                fullpath_program = shutil.which(editor_command_line[0])
+            if fullpath_program is None:
+                self.view.controller.report_error(
+                    "External editor command not found; "
+                    "Check your zuliprc file, $EDITOR or $ZULIP_EDITOR_COMMAND."
+                )
+                return key
+            editor_command_line[0] = fullpath_program
+
+            with NamedTemporaryFile(suffix=".md") as edit_tempfile:
+                with open(edit_tempfile.name, mode="w") as edit_writer:
+                    edit_writer.write(self.msg_write_box.edit_text)
+                self.view.controller.loop.screen.stop()
+
+                editor_command_line.append(edit_tempfile.name)
+                subprocess.call(editor_command_line)
+
+                with open(edit_tempfile.name, mode="r") as edit_reader:
+                    self.msg_write_box.edit_text = edit_reader.read().rstrip()
+            self.view.controller.loop.screen.start()
+            return key
+
         elif is_command_key("SAVE_AS_DRAFT", key):
             if self.msg_edit_state is None:
                 if self.compose_box_status == "open_with_private":
diff --git a/zulipterminal/ui_tools/buttons.py b/zulipterminal/ui_tools/buttons.py
index 91c428a..8e67d79 100644
--- a/zulipterminal/ui_tools/buttons.py
+++ b/zulipterminal/ui_tools/buttons.py
@@ -210,16 +210,26 @@ class StreamButton(TopButton):
         self.count = count
         self.view = view
 
+        # FIXME: This section should be moved to a general palette-extension method
+        # which is triggered when streams are initially loaded and when colors are
+        # adjusted, or new streams added
         for entry in view.palette:
             if entry[0] is None:
+                # NOTE: entry is generated at runtime, so length is dynamic
+                # entry[5] is 256-color background entry
+                # entry[2] is 16-color background entry
                 background = entry[5] if len(entry) > 4 else entry[2]
-                inverse_text = background if background else "black"
+                inverse_text = "black" if background in ["default", ""] else background
                 break
+        # These tuples represent (new) Urwid palette entries for the stream color:
+        #   (style_name, 16-color fg, 16-color bg, mono, 256+color fg, 256+color bg)
+        # The normalized color becomes a named style (eg. "#abc") for colored foreground
         view.palette.append(
-            (self.color, "", "", "bold", f"{self.color}, bold", background)
+            (f"{self.color}", "", "", "bold", f"{self.color}, bold", background)
         )
+        # The s-prefixed style name (eg "s#abc") is used for inverted styling
         view.palette.append(
-            ("s" + self.color, "", "", "standout", inverse_text, self.color)
+            (f"s{self.color}", "", "", "standout", inverse_text, self.color)
         )
 
         stream_marker = STREAM_ACCESS_TYPE[stream_access_type]["icon"]
diff --git a/zulipterminal/ui_tools/views.py b/zulipterminal/ui_tools/views.py
index c2034b3..6d01a82 100644
--- a/zulipterminal/ui_tools/views.py
+++ b/zulipterminal/ui_tools/views.py
@@ -1091,6 +1091,7 @@ class AboutView(PopUpView):
         maximum_footlinks: int,
         notify_enabled: bool,
         exit_confirmation_enabled: bool,
+        transparency_enabled: bool,
     ) -> None:
         self.feature_level_content = (
             [("Feature level", str(server_feature_level))]
@@ -1113,6 +1114,7 @@ class AboutView(PopUpView):
                         "Exit confirmation",
                         "enabled" if exit_confirmation_enabled else "disabled",
                     ),
+                    ("Transparency", "enabled" if transparency_enabled else "disabled"),
                 ],
             ),
             (


Test patch:
diff --git a/tests/cli/test_run.py b/tests/cli/test_run.py
index 7b4fe02..fba198e 100644
--- a/tests/cli/test_run.py
+++ b/tests/cli/test_run.py
@@ -27,6 +27,8 @@ from zulipterminal.version import ZT_VERSION
 MODULE = "zulipterminal.cli.run"
 CONTROLLER = MODULE + ".Controller"
 
+os.environ["ZULIP_EDITOR_COMMAND"] = ""
+
 
 @pytest.mark.parametrize(
     "color, code",
@@ -143,6 +145,8 @@ def test_main_help(capsys: CaptureFixture[str], options: str) -> None:
         "--color-depth",
         "--notify",
         "--no-notify",
+        "--transparency",
+        "--no-transparency",
     }
     optional_argument_lines = {
         line[2:] for line in lines if len(line) > 2 and line[2] == "-"
@@ -203,6 +207,8 @@ def test_valid_zuliprc_but_no_connection(
         "   maximum footlinks value '3' specified from default config.",
         "   color depth setting '256' specified from default config.",
         "   notify setting 'disabled' specified from default config.",
+        "   transparency setting 'disabled' specified from default config.",
+        "   external editor command '' specified from environment.",
         "\x1b[91m",
         f"Error connecting to Zulip server: {server_connection_error}.\x1b[0m",
     ]
@@ -262,6 +268,8 @@ def test_warning_regarding_incomplete_theme(
         "   maximum footlinks value '3' specified from default config.",
         "   color depth setting '256' specified from default config.",
         "   notify setting 'disabled' specified from default config.",
+        "   transparency setting 'disabled' specified from default config.",
+        "   external editor command '' specified from environment.",
         "\x1b[91m",
         f"Error connecting to Zulip server: {server_connection_error}.\x1b[0m",
     ]
@@ -481,6 +489,8 @@ def test_successful_main_function_with_config(
         f"   maximum footlinks value {footlinks_output}",
         "   color depth setting '256' specified in zuliprc file.",
         "   notify setting 'enabled' specified in zuliprc file.",
+        "   transparency setting 'disabled' specified from default config.",
+        "   external editor command '' specified from environment.",
     ]
     assert lines == expected_lines
 
diff --git a/tests/config/test_themes.py b/tests/config/test_themes.py
index b95c4e3..34c7e17 100644
--- a/tests/config/test_themes.py
+++ b/tests/config/test_themes.py
@@ -9,6 +9,7 @@ from pygments.token import STANDARD_TYPES, _TokenType
 from pytest import param as case
 from pytest_mock import MockerFixture
 
+from zulipterminal.config.color import Background
 from zulipterminal.config.regexes import REGEX_COLOR_VALID_FORMATS
 from zulipterminal.config.themes import (
     REQUIRED_STYLES,
@@ -86,7 +87,8 @@ def test_builtin_theme_completeness(theme_name: str) -> None:
     # Check if color used in STYLE exists in Color.
     for style_name, style_conf in theme_styles.items():
         fg, bg = style_conf
-        assert fg in theme_colors and bg in theme_colors
+        assert fg in theme_colors
+        assert bg in theme_colors or bg in Background
     # Check completeness of META
     expected_META = {"pygments": ["styles", "background", "overrides"]}
     for metadata, config in expected_META.items():
@@ -113,6 +115,7 @@ def test_complete_and_incomplete_themes__bundled_theme_output() -> None:
         case({"META": None}, False, id="META_absent"),
         case({"META": {}}, False, id="META_empty"),
         case({"META": {"pygments": {}}}, False, id="META_pygments_empty"),
+        case({"META": "extra_field"}, True, id="META_with_extra_field"),
     ],
 )
 def test_complete_and_incomplete_themes__single_theme_completeness(
@@ -131,7 +134,7 @@ def test_complete_and_incomplete_themes__single_theme_completeness(
         STYLES = {
             style: (FakeColor.COLOR_1, FakeColor.COLOR_2) for style in REQUIRED_STYLES
         }
-        META = {
+        META: Dict[str, Any] = {
             "pygments": {
                 "styles": None,
                 "background": None,
@@ -144,6 +147,8 @@ def test_complete_and_incomplete_themes__single_theme_completeness(
     for field, action in missing.items():
         if action == "incomplete_style":
             setattr(FakeTheme, field, incomplete_style)
+        elif action == "extra_field":
+            FakeTheme.META["extra_field"] = 5  # Non-iterable misc data
         elif action is None:
             delattr(FakeTheme, field)
         else:
@@ -197,7 +202,9 @@ def test_generate_theme__has_required_attributes(
 
     mocker.patch(MODULE + ".THEMES", {fake_theme_name: FakeTheme})
 
-    generated_theme = generate_theme(fake_theme_name, depth)
+    generated_theme = generate_theme(
+        fake_theme_name, color_depth=depth, transparent_background=False
+    )
 
     assert len(generated_theme) == len(theme_styles) + expected_pygments_length
     assert (single_style, "", "", "", "a", "b") in generated_theme
@@ -214,9 +221,11 @@ def test_generate_theme__missing_attributes_in_theme(
 
     mocker.patch(MODULE + ".THEMES", {fake_theme_name: FakeTheme})
 
+    kwargs: Dict[str, Any] = dict(color_depth=depth, transparent_background=False)
+
     # No attributes (STYLES or META) - flag missing Color
     with pytest.raises(MissingThemeAttributeError) as e:
-        generate_theme(fake_theme_name, depth)
+        generate_theme(fake_theme_name, **kwargs)
     assert str(e.value) == "Theme is missing required attribute 'Color'"
 
     # Color but missing STYLES - flag missing STYLES
@@ -227,7 +236,7 @@ def test_generate_theme__missing_attributes_in_theme(
     FakeTheme.Color = FakeColor  # type: ignore [attr-defined]
 
     with pytest.raises(MissingThemeAttributeError) as e:
-        generate_theme(fake_theme_name, depth)
+        generate_theme(fake_theme_name, **kwargs)
     assert str(e.value) == "Theme is missing required attribute 'STYLES'"
 
     # Color, STYLES and META, but no pygments data in META
@@ -236,7 +245,7 @@ def test_generate_theme__missing_attributes_in_theme(
     FakeTheme.META = {}  # type: ignore [attr-defined]
 
     with pytest.raises(MissingThemeAttributeError) as e:
-        generate_theme(fake_theme_name, depth)
+        generate_theme(fake_theme_name, **kwargs)
     assert str(e.value) == """Theme is missing required attribute 'META["pygments"]'"""
 
     # Color, STYLES and META, but incomplete pygments in META
@@ -245,7 +254,7 @@ def test_generate_theme__missing_attributes_in_theme(
     }
 
     with pytest.raises(MissingThemeAttributeError) as e:
-        generate_theme(fake_theme_name, depth)
+        generate_theme(fake_theme_name, **kwargs)
     assert (
         str(e.value)
         == """Theme is missing required attribute 'META["pygments"]["overrides"]'"""
@@ -300,7 +309,10 @@ def test_parse_themefile(
 
     req_styles = {"s1": "", "s2": "bold"}
     mocker.patch.dict("zulipterminal.config.themes.REQUIRED_STYLES", req_styles)
-    assert parse_themefile(theme_styles, color_depth) == expected_urwid_theme
+    assert (
+        parse_themefile(theme_styles, color_depth, Color.DARK_MAGENTA, False)
+        == expected_urwid_theme
+    )
 
 
 @pytest.mark.parametrize(
diff --git a/tests/core/test_core.py b/tests/core/test_core.py
index be05d49..ff6701a 100644
--- a/tests/core/test_core.py
+++ b/tests/core/test_core.py
@@ -44,11 +44,14 @@ class TestController:
 
         self.config_file = "path/to/zuliprc"
         self.theme_name = "zt_dark"
-        self.theme = generate_theme("zt_dark", 256)
+        self.theme = generate_theme(
+            "zt_dark", color_depth=256, transparent_background=False
+        )
         self.in_explore_mode = False
         self.autohide = True  # FIXME Add tests for no-autohide
         self.notify_enabled = False
         self.exit_confirmation = True
+        self.transparency_enabled = False
         self.maximum_footlinks = 3
         result = Controller(
             config_file=self.config_file,
@@ -58,10 +61,12 @@ class TestController:
             color_depth=256,
             in_explore_mode=self.in_explore_mode,
             debug_path=None,
+            editor_command="",
             **dict(
                 autohide=self.autohide,
                 notify=self.notify_enabled,
                 exit_confirmation=self.exit_confirmation,
+                transparency=self.transparency_enabled,
             ),
         )
         result.view.message_view = mocker.Mock()  # set in View.__init__
diff --git a/tests/ui_tools/test_boxes.py b/tests/ui_tools/test_boxes.py
index 4b28a1e..c4e89a2 100644
--- a/tests/ui_tools/test_boxes.py
+++ b/tests/ui_tools/test_boxes.py
@@ -25,7 +25,12 @@ from zulipterminal.config.symbols import (
 )
 from zulipterminal.config.ui_mappings import StreamAccessType
 from zulipterminal.helper import Index, MinimalUserData
-from zulipterminal.ui_tools.boxes import PanelSearchBox, WriteBox, _MessageEditState
+from zulipterminal.ui_tools.boxes import (
+    MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP,
+    PanelSearchBox,
+    WriteBox,
+    _MessageEditState,
+)
 from zulipterminal.urwid_types import urwid_Size
 
 
@@ -236,7 +241,7 @@ class TestWriteBox:
         assert not write_box.model.send_private_message.called
 
     @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE"))
-    def test__compose_attributes_reset_for_private_compose(
+    def test__compose_attributes_reset_for_private_compose__no_popup(
         self,
         key: str,
         mocker: MockerFixture,
@@ -247,17 +252,41 @@ class TestWriteBox:
         mocker.patch("urwid.connect_signal")
         write_box.model.user_id_email_dict = user_id_email_dict
         write_box.private_box_view(recipient_user_ids=[11])
-        write_box.msg_write_box.edit_text = "random text"
+
+        write_box.msg_write_box.edit_text = "." * (
+            MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP - 1
+        )
 
         size = widget_size(write_box)
         write_box.keypress(size, key)
 
+        write_box.view.controller.exit_compose_confirmation_popup.assert_not_called()
         assert write_box.to_write_box is None
         assert write_box.msg_write_box.edit_text == ""
         assert write_box.compose_box_status == "closed"
 
     @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE"))
-    def test__compose_attributes_reset_for_stream_compose(
+    def test__compose_attributes_reset_for_private_compose__popup(
+        self,
+        key: str,
+        mocker: MockerFixture,
+        write_box: WriteBox,
+        widget_size: Callable[[Widget], urwid_Size],
+        user_id_email_dict: Dict[int, str],
+    ) -> None:
+        mocker.patch("urwid.connect_signal")
+        write_box.model.user_id_email_dict = user_id_email_dict
+        write_box.private_box_view(recipient_user_ids=[11])
+
+        write_box.msg_write_box.edit_text = "." * MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP
+
+        size = widget_size(write_box)
+        write_box.keypress(size, key)
+
+        write_box.view.controller.exit_compose_confirmation_popup.assert_called_once()
+
+    @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE"))
+    def test__compose_attributes_reset_for_stream_compose__no_popup(
         self,
         key: str,
         mocker: MockerFixture,
@@ -266,15 +295,37 @@ class TestWriteBox:
     ) -> None:
         mocker.patch(WRITEBOX + "._set_stream_write_box_style")
         write_box.stream_box_view(stream_id=1)
-        write_box.msg_write_box.edit_text = "random text"
+
+        write_box.msg_write_box.edit_text = "." * (
+            MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP - 1
+        )
 
         size = widget_size(write_box)
         write_box.keypress(size, key)
 
+        write_box.view.controller.exit_compose_confirmation_popup.assert_not_called()
         assert write_box.stream_id is None
         assert write_box.msg_write_box.edit_text == ""
         assert write_box.compose_box_status == "closed"
 
+    @pytest.mark.parametrize("key", keys_for_command("EXIT_COMPOSE"))
+    def test__compose_attributes_reset_for_stream_compose__popup(
+        self,
+        key: str,
+        mocker: MockerFixture,
+        write_box: WriteBox,
+        widget_size: Callable[[Widget], urwid_Size],
+    ) -> None:
+        mocker.patch(WRITEBOX + "._set_stream_write_box_style")
+        write_box.stream_box_view(stream_id=1)
+
+        write_box.msg_write_box.edit_text = "." * MAX_MESSAGE_LENGTH_CONFIRMATION_POPUP
+
+        size = widget_size(write_box)
+        write_box.keypress(size, key)
+
+        write_box.view.controller.exit_compose_confirmation_popup.assert_called_once_with()
+
     @pytest.mark.parametrize(
         ["raw_recipients", "tidied_recipients"],
         [
@@ -1508,38 +1559,59 @@ class TestWriteBox:
             )
 
     @pytest.mark.parametrize(
-        "key, current_typeahead_mode, expected_typeahead_mode, expect_footer_was_reset",
+        "key, current_typeahead_mode, expected_typeahead_mode",
         [
-            # footer does not reset
-            (primary_key_for_command("AUTOCOMPLETE"), False, False, False),
-            (primary_key_for_command("AUTOCOMPLETE_REVERSE"), False, False, False),
-            (primary_key_for_command("AUTOCOMPLETE"), True, True, False),
-            (primary_key_for_command("AUTOCOMPLETE_REVERSE"), True, True, False),
-            # footer resets
-            (primary_key_for_command("EXIT_COMPOSE"), True, False, True),
-            ("space", True, False, True),
-            ("k", True, False, True),
+            (primary_key_for_command("AUTOCOMPLETE"), False, False),
+            (primary_key_for_command("AUTOCOMPLETE_REVERSE"), False, False),
+            (primary_key_for_command("AUTOCOMPLETE"), True, True),
+            (primary_key_for_command("AUTOCOMPLETE_REVERSE"), True, True),
         ],
     )
-    def test_keypress_typeahead_mode_autocomplete_key(
+    def test__keypress_typeahead_mode_autocomplete_key_footer_no_reset(
         self,
+        mocker: MockerFixture,
         write_box: WriteBox,
         widget_size: Callable[[Widget], urwid_Size],
         current_typeahead_mode: bool,
         expected_typeahead_mode: bool,
-        expect_footer_was_reset: bool,
         key: str,
     ) -> None:
+        write_box.msg_write_box = mocker.Mock(edit_text="")
         write_box.is_in_typeahead_mode = current_typeahead_mode
         size = widget_size(write_box)
 
         write_box.keypress(size, key)
 
         assert write_box.is_in_typeahead_mode == expected_typeahead_mode
-        if expect_footer_was_reset:
-            self.view.set_footer_text.assert_called_once_with()
-        else:
-            self.view.set_footer_text.assert_not_called()
+        assert not self.view.set_footer_text.called
+
+    @pytest.mark.parametrize(
+        "key, current_typeahead_mode, expected_typeahead_mode",
+        [
+            (primary_key_for_command("EXIT_COMPOSE"), True, False),
+            ("space", True, False),
+            ("k", True, False),
+        ],
+    )
+    def test__keypress_typeahead_mode_autocomplete_key_footer_reset(
+        self,
+        mocker: MockerFixture,
+        write_box: WriteBox,
+        widget_size: Callable[[Widget], urwid_Size],
+        current_typeahead_mode: bool,
+        expected_typeahead_mode: bool,
+        key: str,
+    ) -> None:
+        write_box.msg_write_box = mocker.Mock(edit_text="")
+        write_box.is_in_typeahead_mode = current_typeahead_mode
+        size = widget_size(write_box)
+
+        write_box.keypress(size, key)
+
+        assert write_box.is_in_typeahead_mode == expected_typeahead_mode
+
+        # We may prefer called-once in future, but the key part is that we do reset
+        assert self.view.set_footer_text.called
 
     @pytest.mark.parametrize(
         [
diff --git a/tests/ui_tools/test_popups.py b/tests/ui_tools/test_popups.py
index d58b6c3..878be17 100644
--- a/tests/ui_tools/test_popups.py
+++ b/tests/ui_tools/test_popups.py
@@ -207,6 +207,7 @@ class TestAboutView:
             autohide_enabled=False,
             maximum_footlinks=3,
             exit_confirmation_enabled=False,
+            transparency_enabled=False,
         )
 
     @pytest.mark.parametrize(
@@ -257,6 +258,7 @@ class TestAboutView:
             autohide_enabled=False,
             maximum_footlinks=3,
             exit_confirmation_enabled=False,
+            transparency_enabled=False,
         )
 
         assert len(about_view.feature_level_content) == (
@@ -293,6 +295,7 @@ Maximum footlinks: 3
 Color depth: 256
 Notifications: disabled
 Exit confirmation: disabled
+Transparency: disabled
 
 #### Detected Environment
 Platform: WSL