home · contact · privacy
Differentiate attribute unsetting in debug logs, reduce init clearing logs.
authorChristian Heller <c.heller@plomlompom.de>
Fri, 21 Nov 2025 18:58:08 +0000 (19:58 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Fri, 21 Nov 2025 18:58:08 +0000 (19:58 +0100)
src/ircplom/client.py
src/ircplom/client_tui.py
src/ircplom/db_primitives.py
src/tests/caps.test
src/tests/channels.test
src/tests/isupports.test
src/tests/lib/conn
src/tests/lib/part
src/tests/test.test
src/tests/tui_draw.test

index 0dd6679b763c61678e6b3925712cca7dd52464f2..23accb62463d058bdcbc9e8a98c6276a17c39607 100644 (file)
@@ -493,7 +493,7 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields):
                        else getattr(parent, last_step))
 
         # treat anything without UpdatingMixin as single endnode to return
-        # (NB: this would include None!)
+        # (NB: this would include None, as a deletion signal)
         if not isinstance(val_at_path, UpdatingMixin):
             return update_unless_cached(path, val_at_path)
 
@@ -503,13 +503,13 @@ class _ClientDb(Clearable, UpdatingAttrsMixin, SharedClientDbFields):
                 return update_unless_cached(path, val_at_path.completed)
             return []
 
-        # for empty Dict, return clearing signal
+        # for empty Dict, return one as clearing signal
         if isinstance(val_at_path, Dict) and not val_at_path.keys():
             for cache_path in [p for p in self._updates_cache
                                if len(p) > len(path)
                                and p[:len(path)] == path]:
                 del self._updates_cache[cache_path]
-            return update_unless_cached(path, None)
+            return update_unless_cached(path, Dict())
 
         # if node at path has children, (only) return _their_ endnode updates
         if isinstance(val_at_path, Dict):
index a04bca4a7585fd7a238c765c18cad328f40dc7d0..35c1bebfb4f2f0791f4176c2d29bdbda74b5896d 100644 (file)
@@ -221,10 +221,10 @@ class _UpdatingNode(AutoAttrMixin):
             return
         update.old_value = node
         do_report = update.force_log
-        if update.value is None:
-            if self._is_set(update.key):
-                self._unset(update.key)
-                do_report |= True
+        if (update.value is None or update.value == Dict())\
+                and self._is_set(update.key, check_empty=True):
+            self._unset(update.key)
+            do_report |= True
         elif update.old_value != update.value:
             self._set(update.key, update.value)
             do_report |= True
@@ -236,6 +236,8 @@ class _UpdatingNode(AutoAttrMixin):
         if result == tuple():
             announcement += 'emptied'
         elif result is None:
+            announcement += 'deleted'
+        elif isinstance(result, Dict) and not result.keys():
             announcement += 'cleared'
         else:
             announcement += 'set to: '
@@ -253,8 +255,13 @@ class _UpdatingNode(AutoAttrMixin):
     def _unset(self, key: str) -> None:
         getattr(self, key).clear()
 
-    def _is_set(self, key: str) -> bool:
-        return hasattr(self, key)
+    def _is_set(self, key: str, check_empty=False) -> bool:
+        if hasattr(self, key):
+            if not check_empty:
+                return True
+            val = getattr(self, key)
+            return (not isinstance(val, Dict)) or bool(val.keys())
+        return False
 
 
 class _UpdatingDict(Dict[DictItem], _UpdatingNode):
@@ -270,7 +277,7 @@ class _UpdatingDict(Dict[DictItem], _UpdatingNode):
     def _unset(self, key: str) -> None:
         del self[key]
 
-    def _is_set(self, key: str) -> bool:
+    def _is_set(self, key: str, check_empty=False) -> bool:
         return key in self._dict
 
 
index 2e2993bd955d183b124a2793db54f36ffd7b5515..76d2c85ec01017a6b3fe0687d734dd871834d1e9 100644 (file)
@@ -69,6 +69,9 @@ class Dict(Clearable, Generic[DictItem]):
     def __delitem__(self, key: str) -> None:
         del self._dict[key]
 
+    def __eq__(self, other) -> bool:
+        return isinstance(other, Dict) and self.items() == other.items()
+
     def clear(self) -> None:
         'Wrapper around dict.clear().'
         self._dict.clear()
@@ -193,6 +196,7 @@ class UpdatingAttrsMixin(UpdatingMixin, AutoAttrMixin):
     'Calls update trigger on changes to attributes.'
 
     def _auto_setattr(self, key: str, cls: Callable) -> None:
+        # bypass .__setattr__: avoid ._on_update on useless emptiness
         super().__setattr__(key, cls(
             on_update=lambda *steps: self._on_update(key, *steps)))
 
index 63d9c3fd1986a56377577082f089747739beb81f..509fb6507e87dbdccb196ef03582bb3ebfd15785 100644 (file)
@@ -83,5 +83,5 @@ insert cap-msg : + CAPMSG :foo NEW :foo bar=baz
 log 1 $ caps:bar:data set to: [baz]
 log 1 $ caps:foo:data set to: []
 insert cap-msg : + CAPMSG :foo DEL :sasl foo
-log 1 $ caps:foo cleared
-log 1 $ caps:sasl cleared
+log 1 $ caps:foo deleted
+log 1 $ caps:sasl deleted
index c44349e550534c8e32d2656d0390e52ab4de12f4..31838a8833ce903a48e539cca0ec5b7141d004fc 100644 (file)
@@ -103,7 +103,7 @@ insert user-set-to :1 + USER_ID=1 USERNICK :bar
 insert join-channel-1 : + CHANNEL=#ch_test0 RESIDENT_IDS :[1], [me]
 log 3 $ residents: bar, foo
 insert part : + CHANNEL=#ch_test0 CHAN_WIN_ID=3 USERIDS_CLEAR :set to: [1]
-log 1 $ users:1 cleared
+log 1 $ users:1 deleted
 
 # check /join into channel with many other users, with multi-line 353
 insert join-channel-0 : + CHANNEL=#ch_test0 RESIDENT_NAMES :foo baz oof
@@ -150,13 +150,13 @@ log 3 < (*.?.net) msg_test6 msg_test7
 # check part of user visible, and of user NOT visible in other channel
 insert part-other : + NICK=baz USER_ID=2 REMAINING_IDS :[3], [4], [5], [me]
 insert part-other : + NICK=oof USER_ID=3 REMAINING_IDS :[4], [5], [me]
-log 1 $ users:3 cleared
+log 1 $ users:3 deleted
 
 # check other-user part with exit message
 insert servermsglogged : + MSG ::zab!~zab@zab.zab PART #ch_test0 :goodbye
 insert user-set-to 1: + USER_ID=5 USERNAME=~zab USERHOST :zab.zab
 insert parts-core : + CHAN_WIN_ID=3 CHANNEL=#ch_test0 USER_ID=5 NICK=zab exitPREFIX=:§ exitMSG=goodbye USERIDS_CLEAR=set§to:§[4],§[me] § : 
-log 1 $ users:5 cleared
+log 1 $ users:5 deleted
 
 # check re-join of user kept visible in other channel
 insert servermsglogged : + MSG ::baz!~baz@baz.baz JOIN :#ch_test0
@@ -183,7 +183,7 @@ log 1 $ users:2:exit_msg set to: [QClient Quit]
 log , $ baz!~baz@baz.baz quits: Client Quit
 insert quit : + CHAN_WIN_ID=3 CHANNEL=#ch_test0 USER_ID=2 NICK=baz REMAINING_IDS :[4], [6], [me]
 insert quit : + CHAN_WIN_ID=4 CHANNEL=#ch_test1 USER_ID=2 NICK=baz REMAINING_IDS :[me]
-log 1 $ users:2 cleared
+log 1 $ users:2 deleted
 
 # check effects of own QUIT while present in one channel
 insert part : + CHAN_WIN_ID=4 USERIDS_CLEAR=emptied CHANNEL :#ch_test1
index 543603454d2f8b976f587a2fd52522cb23ac5d81..8b9bd1b7862d34e25eb14b18ba0a6b1098d25c2f 100644 (file)
@@ -52,9 +52,9 @@ log 1 $ isupport:STU set to: [VWX,YZ]
 
 # check minus args clearing non-defaulty ISUPPORTs, and intermingling with positive args 
 insert servermsglogged : +0 MSG ::foo.bar.baz 005 foo -GHI DEF=MNO -STU :ignore me
-log 1 $ isupport:GHI cleared
+log 1 $ isupport:GHI deleted
 log 1 $ isupport:DEF set to: [MNO]
-log 1 $ isupport:STU cleared
+log 1 $ isupport:STU deleted
 
 # check setting and un-setting of defaulty ISUPPORTs
 insert un-defaults
index 680317b8149006cf59c1cad4253cc6a8c23a0fa7..8ac6b61e5451508a9e3ae668462e808f323a9be7 100644 (file)
@@ -10,11 +10,8 @@ log 2 $ - no password
 
 × connect
 > /connect foo.bar.baz:6697 foo bar:baz
-insert isupport-clear : +1
-log 1 $ caps cleared
-log 1 $ channels cleared
+insert isupport-clear 1: +1
 log , $ DISCONNECTED
-log 1 $ users cleared
 log 1 $ hostname set to: [foo.bar.baz]
 log 1 $ port set to: [6697]
 log 1 $ nick_wanted set to: [foo]
index 9464cee064b0440df307c2dce0b233a6d688f7df..fc214d8d98747a9674e1ac950ab64857b86a79bd 100644 (file)
@@ -4,7 +4,7 @@ insert ./lib/servermsglogged
 log 1 $ channels:CHANNEL:exits:USER_ID set to: [exitTYPEexitMSG]
 log 1 $ channels:CHANNEL:user_ids USERIDS_CLEAR
 log CHAN_WIN_ID $ NICK!~NICK@NICK.NICK exitDESCexitPREFIXexitMSG
-log 1 $ channels:CHANNEL:exits:USER_ID cleared
+log 1 $ channels:CHANNEL:exits:USER_ID deleted
 
 × parts-core
 insert exit-channel : + exitTYPE=P exitDESC :parts
@@ -15,7 +15,7 @@ insert exit-channel : + exitTYPE=P exitDESC :parts
 log 1 > PART :CHANNEL
 insert servermsglogged : + MSG ::foo!~baz@baz.bar.foo PART :CHANNEL
 insert parts-core : + exitPREFIX= exitMSG= USER_ID=me NICK=foo foo@foo.foo :baz@baz.bar.foo
-log 1 $ channels:CHANNEL cleared
+log 1 $ channels:CHANNEL deleted
 
 × quits
 insert exit-channel : + exitTYPE=Q exitDESC=quits exitPREFIX=:§ exitMSG=Client§Quit § : 
index b5209b5aba7718885dd46cc1ce83fa97d5ef4724..3e776eb896886f1aed3a3505a4f329ed811db8b5 100644 (file)
@@ -150,12 +150,12 @@ log 1 $ users:2:exit_msg set to: [QClient Quit]
 log 6,7 $ barbar!~bar@bar.bar quits: Client Quit
 insert quit : + CHAN_WIN_ID=4 CHANNEL=#test USER_ID=2 NICK=bar bar!=barbar! REMAINING_IDS :[1], [me]
 insert quit : + CHAN_WIN_ID=5 CHANNEL=#testtest USER_ID=2 NICK=bar bar!=barbar! REMAINING_IDS :[me]
-log 1 $ users:2 cleared
+log 1 $ users:2 deleted
 
 # handle self-PART: clear channel, and its squatters
 > /window 4
 insert part : + CHAN_WIN_ID=4 CHANNEL=#test USERIDS_CLEAR :set to: [1]
-log 1 $ users:1 cleared
+log 1 $ users:1 deleted
 
 # handle lack of implementation
 insert no-handler : +0 ALERT_WIN_IDS=2,3,4,5,6,7 ? :foo bar baz
index 618857e25bed760f0c0041099117a3ce3fbe4d3a..a55c3224f028807bf3ccd93c508472be322d59c1 100644 (file)
@@ -124,55 +124,54 @@ insert line-tui-log : +31 ? :  2) foo.bar.baz:server§§
 
 × history_1
 insert line-cal : +0
-insert line-server-log : +1 ? :isupport cleared
-insert line-server-log : +2 ? :isupport:CHANTYPES set to: [#&]
-insert line-server-log : +3 ? :isupport:PREFIX set to: [(ov)@+]
-insert line-server-log : +4 ? :isupport:USERLEN set to: [10]
-insert line-server-log : +5 ? :caps cleared
-insert line-server-log : +6 ? :channels cleared
-insert line-server-log : +7 ? :users cleared
-insert line-server-log : +8 ? :hostname set to: [foo.bar.baz]
-insert line-server-log : +9 ? :port set to: [6697]
-insert line-server-log : +10 ? :nick_wanted set to: [foo]
-insert line-server-log : +11 ? :user_wanted set to: [baz]
-insert line-server-log : +12 ? :realname set to: [bar]
-insert line-server-log : +13 ? :connection_state set to: [connecting]
-insert line-server-log : +14 ? :connection_state set to: [connected]
-insert line-client-msg : +15 ? :CAP LS :302
-insert line-client-msg : +16 ? :USER baz 0 * :bar
-insert line-client-msg : +17 ? :NICK :foo
-insert lines-ping-pong : +18 ?? :?
+insert line-server-log : +1 ? :isupport:CHANTYPES set to: [#&]
+insert line-server-log : +2 ? :isupport:PREFIX set to: [(ov)@+]
+insert line-server-log : +3 ? :isupport:USERLEN set to: [10]
+insert line-server-log : +4 ? :hostname set to: [foo.bar.baz]
+insert line-server-log : +5 ? :port set to: [6697]
+insert line-server-log : +6 ? :nick_wanted set to: [foo]
+insert line-server-log : +7 ? :user_wanted set to: [baz]
+insert line-server-log : +8 ? :realname set to: [bar]
+insert line-server-log : +9 ? :connection_state set to: [connecting]
+insert line-server-log : +10 ? :connection_state set to: [connected]
+insert line-client-msg : +11 ? :CAP LS :302
+insert line-client-msg : +12 ? :USER baz 0 * :bar
+insert line-client-msg : +13 ? :NICK :foo
+insert lines-ping-pong : +14 ?? :0
+insert lines-ping-pong : +16 ?? :1
+insert lines-ping-pong : +18 ?? :2
 insert line-ping : +20 ? :9 123456789 123456789 123456789 123456789 123456789 123456789§§
 insert line-bright-white : +21 ? :   123456789 123456789
 insert line-pong : +22 ? :9 123456789 123456789 123456789 123456789 123456789 123456789§§
 insert line-bright-green : +23 ? :   123456789 123456789
-insert lines-ping-pong : +24 ?? :foo
-insert lines-ping-pong : +26 ?? :bar
-insert lines-ping-pong : +28 ?? :baz
-insert lines-ping-pong : +30 ?? :0
-insert lines-ping-pong : +32 ?? :1
-insert lines-ping-pong : +34 ?? :2
-insert lines-ping-pong : +36 ?? :3
-insert lines-ping-pong : +38 ?? :4
-insert lines-ping-pong : +40 ?? :5
-insert lines-ping-pong : +42 ?? :6
-insert lines-ping-pong : +44 ?? :7
-insert lines-ping-pong : +46 ?? :8
-insert lines-ping-pong : +48 ?? :9
-insert lines-ping-pong : +50 ?? :10
-insert lines-ping-pong : +52 ?? :11
-insert lines-ping-pong : +54 ?? :12
-insert lines-ping-pong : +56 ?? :13
-insert lines-ping-pong : +58 ?? :14
-insert lines-ping-pong : +60 ?? :15
-insert lines-ping-pong : +62 ?? :16
-insert lines-ping-pong : +64 ?? :17
-insert lines-ping-pong : +66 ?? :18
-insert lines-ping-pong : +68 ?? :19
-insert lines-ping-pong : +70 ?? :20
-insert lines-ping-pong : +72 ?? :21
-insert lines-ping-pong : +74 ?? :22
-insert lines-ping-pong : +76 ?? :23
+insert lines-ping-pong : +24 ?? :3
+insert lines-ping-pong : +26 ?? :4
+insert lines-ping-pong : +28 ?? :5
+insert lines-ping-pong : +30 ?? :6
+insert lines-ping-pong : +32 ?? :7
+insert lines-ping-pong : +34 ?? :8
+insert lines-ping-pong : +36 ?? :9
+insert lines-ping-pong : +38 ?? :10
+insert lines-ping-pong : +40 ?? :11
+insert lines-ping-pong : +42 ?? :12
+insert lines-ping-pong : +44 ?? :13
+insert lines-ping-pong : +46 ?? :14
+insert lines-ping-pong : +48 ?? :15
+insert lines-ping-pong : +50 ?? :16
+insert lines-ping-pong : +52 ?? :17
+insert lines-ping-pong : +54 ?? :18
+insert lines-ping-pong : +56 ?? :19
+insert lines-ping-pong : +58 ?? :20
+insert lines-ping-pong : +60 ?? :21
+insert lines-ping-pong : +62 ?? :22
+insert lines-ping-pong : +64 ?? :23
+insert lines-ping-pong : +66 ?? :24
+insert lines-ping-pong : +68 ?? :25
+insert lines-ping-pong : +70 ?? :26
+insert lines-ping-pong : +72 ?? :27
+insert lines-ping-pong : +74 ?? :28
+insert lines-ping-pong : +76 ?? :29
+insert lines-ping-pong : +78 ?? :30
 
 × ×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
 
@@ -349,12 +348,12 @@ insert connect-to-connected
 insert lines-empty :20
 insert history_0 :1 +20
 insert line-scrolldown : +21 XXXXXX :[25] v
-insert lines-status-prompt-start : +22 X123456789X123456789X :([(0:1)] (1:18) (2:7)
+insert lines-status-prompt-start : +22 X123456789X123456789X :([(0:1)] (1:14) (2:7)
 
 # check switch to other window, updates to status line (movement of brackets, clearing of own unread-lines count)
 > /window 1
-insert lines-empty :4
-insert history_1 :18 +4
+insert lines-empty :8
+insert history_1 :14 +8
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====((0:1) [1] (2:7)
 
 # check switch-back to window 0, retaining clearing of window 1's unread-lines count
@@ -394,8 +393,8 @@ insert lines-status-prompt-start : +22 X123456789X123456789X :=========([0] 1 (2
 
 # check that second switch to new window, previously left on bottom of history, establishes bookmark at bottom of log
 > /window 1
-insert lines-empty :3
-insert history_1 :18 +3
+insert lines-empty :7
+insert history_1 :14 +7
 insert line-bookmark : +21
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
 
@@ -414,32 +413,34 @@ insert line-bookmark : +17
 insert history_0 28:32 +18
 insert lines-status-prompt-start : +22 X123456789X123456789X :=========([0] 1 (2:7)
 
-# check new lines growing in other window, one of which long enough to wrap, to be re-start count in status (with wrapped only as single) 
-insert ping-pong : +0 ? :?
+# check new lines growing in other window, one of which long enough to wrap, to re-start count in status (with wrapped only as single) 
+insert ping-pong : +0 ? :0
+insert ping-pong : +0 ? :1
+insert ping-pong : +0 ? :2
 insert ping-pong : +0 ? :9 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789
 insert history_0 11:28 +0
 insert line-bookmark : +17
 insert history_0 28:32 +18
-insert lines-status-prompt-start : +22 X123456789X123456789X :=====([0] (1:4) (2:7)
+insert lines-status-prompt-start : +22 X123456789X123456789X :=====([0] (1:8) (2:7)
 
 # check that switching to window with new lines, but left scroll-to-bottom, keeps the scroll-to-bottom, keeps bookmark after last line previously seen there
 > /window 1
-insert history_1 3:18 +0
-insert line-bookmark : +15
-insert history_1 18:24 +16
+insert history_1 3:14 +0
+insert line-bookmark : +11
+insert history_1 14:24 +12
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
 
 # check that growth below scroll does not by itself re-position bookmark in history
 > /window.history.scroll up
-insert ping-pong : +0 ? :foo
+insert ping-pong : +0 ? :3
 insert lines-empty :7
 insert history_1 0:14 +7
 insert line-scrolldown : +21 XXXXXX :[10] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:2)] (2:7)
 > /window.history.scroll down
-insert history_1 4:18 +0
-insert line-bookmark : +14
-insert history_1 18:24 +15
+insert history_1 4:14 +0
+insert line-bookmark : +10
+insert history_1 14:24 +11
 insert line-scrolldown : +21 XXXXXX :[2] vv
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:2)] (2:7)
 
@@ -456,7 +457,7 @@ insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:2)
 
 # check that growing lines below scroll in other preserves non-bottom bookmark (left in sight on previous window leave)
 > /window 0
-insert ping-pong : +0 ? :bar
+insert ping-pong : +0 ? :4
 insert history_0 11:32 +0
 insert line-bookmark : +21
 insert lines-status-prompt-start : +22 X123456789X123456789X :=====([0] (1:4) (2:7)
@@ -473,7 +474,7 @@ insert history_1 :14 +7
 insert line-scrolldown : +21 XXXXXX :[12] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:4)] (2:7)
 > /window 0
-insert ping-pong : +0 ? :baz
+insert ping-pong : +0 ? :5
 insert history_0 11:32 +0
 insert line-bookmark : +21
 insert lines-status-prompt-start : +22 X123456789X123456789X :=====([0] (1:6) (2:7)
@@ -494,7 +495,7 @@ insert history_1 24:30 +16
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
 
 # check log growth beyond max depth only remarkable after scrolling up to limit
-insert ping-pong : +0 ? :0
+insert ping-pong : +0 ? :6
 insert history_1 11:24 +0
 insert line-bookmark : +13
 insert history_1 24:32 +14
@@ -515,7 +516,7 @@ insert line-scrolldown : +21 XXXXXX :[29] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
 
 # check cut-off log growth seen from topmost scroll not affecting scrolldown count, but unread-lines status
-insert ping-pong : +0 ? :0
+insert ping-pong : +0 ? :7
 insert lines-empty :20
 insert history_1 2:3 +20
 insert line-scrolldown : +21 XXXXXX :[29] v
@@ -526,13 +527,6 @@ insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:2)
 insert history_0 11:32 +0
 insert line-bookmark : +21
 insert lines-status-prompt-start : +22 X123456789X123456789X :=====([0] (1:2) (2:7)
-insert ping-pong : +0 ? :1
-insert ping-pong : +0 ? :2
-insert ping-pong : +0 ? :3
-insert ping-pong : +0 ? :4
-insert ping-pong : +0 ? :5
-insert ping-pong : +0 ? :6
-insert ping-pong : +0 ? :7
 insert ping-pong : +0 ? :8
 insert ping-pong : +0 ? :9
 insert ping-pong : +0 ? :10
@@ -541,6 +535,13 @@ insert ping-pong : +0 ? :12
 insert ping-pong : +0 ? :13
 insert ping-pong : +0 ? :14
 insert ping-pong : +0 ? :15
+insert ping-pong : +0 ? :16
+insert ping-pong : +0 ? :17
+insert ping-pong : +0 ? :18
+insert ping-pong : +0 ? :19
+insert ping-pong : +0 ? :20
+insert ping-pong : +0 ? :21
+insert ping-pong : +0 ? :22
 insert history_0 11:32 +0
 insert line-bookmark : +21
 insert lines-status-prompt-start : +22 X123456789X123456789X :====([0] (1:32) (2:7)
@@ -548,59 +549,52 @@ insert lines-status-prompt-start : +22 X123456789X123456789X :====([0] (1:32) (2
 # check switching into window scrolled-top with more unread lines than max log depth shrinks former to latter, and scrolling down no bookmark is to be found
 > /window 1
 insert lines-empty :20
-insert history_1 32:33 +20
+insert history_1 34:35 +20
 insert line-scrolldown : +21 XXXXXX :[29] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :====(0 [(1:29)] (2:7)
 > /window.history.scroll down
 insert lines-empty :9
-insert history_1 32:44 +9
+insert history_1 34:46 +9
 insert line-scrolldown : +21 XXXXXX :[18] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :====(0 [(1:18)] (2:7)
 > /window.history.scroll down
-insert history_1 34:55 +0
+insert history_1 36:57 +0
 insert line-scrolldown : +21 XXXXXX :[7] vv
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:7)] (2:7)
 > /window.history.scroll down
-insert history_1 40:60 +0
+insert history_1 42:62 +0
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
 
 # check bookmark remains absent with log growing while scrolled to bottom, …
-insert ping-pong : +0 ? :16
-insert history_1 42:62 +0
+insert ping-pong : +0 ? :23
+insert history_1 44:64 +0
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
 
 # … as well as if further growth happens below scroll 
 > /window.history.scroll up
 insert lines-empty :2
-insert history_1 34:53 +2
+insert history_1 36:55 +2
 insert line-scrolldown : +21 XXXXXX :[11] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
-insert ping-pong : +0 ? :17
+insert ping-pong : +0 ? :24
 insert lines-empty :4
-insert history_1 36:53 +4
+insert history_1 38:55 +4
 insert line-scrolldown : +21 XXXXXX :[13] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:2)] (2:7)
 > /window.history.scroll down 
-insert history_1 43:64 +0
+insert history_1 45:66 +0
 insert line-scrolldown : +21 XXXXXX :[2] vv
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=====(0 [(1:2)] (2:7)
 > /window.history.scroll down 
-insert history_1 44:66 +0
+insert history_1 46:68 +0
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
 
 # check unread-lines status not growing beyond max log if within active window, and …
 > /window.history.scroll up
 insert lines-empty :2
-insert history_1 36:53 +2
+insert history_1 38:55 +2
 insert line-scrolldown : +21 XXXXXX :[11] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :=========(0 [1] (2:7)
-insert ping-pong : +0 ? :18
-insert ping-pong : +0 ? :19
-insert ping-pong : +0 ? :20
-insert ping-pong : +0 ? :21
-insert ping-pong : +0 ? :22
-insert ping-pong : +0 ? :23
-insert ping-pong : +0 ? :24
 insert ping-pong : +0 ? :25
 insert ping-pong : +0 ? :26
 insert ping-pong : +0 ? :27
@@ -609,8 +603,15 @@ insert ping-pong : +0 ? :29
 insert ping-pong : +0 ? :30
 insert ping-pong : +0 ? :31
 insert ping-pong : +0 ? :32
+insert ping-pong : +0 ? :33
+insert ping-pong : +0 ? :34
+insert ping-pong : +0 ? :35
+insert ping-pong : +0 ? :36
+insert ping-pong : +0 ? :37
+insert ping-pong : +0 ? :38
+insert ping-pong : +0 ? :39
 insert lines-empty :20
-insert history_1 66:67 +20
+insert history_1 68:69 +20
 insert line-scrolldown : +21 XXXXXX :[29] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :====(0 [(1:29)] (2:7)
 
@@ -621,14 +622,14 @@ insert line-bookmark : +21
 insert lines-status-prompt-start : +22 X123456789X123456789X :====([0] (1:29) (2:7)
 > /window 1
 insert lines-empty :19
-insert history_1 66:67 +19
+insert history_1 68:69 +19
 insert line-bookmark : +20
 insert line-scrolldown : +21 XXXXXX :[29] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :====(0 [(1:29)] (2:7)
 > /window.history.scroll down
 insert lines-empty :8
-insert history_1 66:67 +8
+insert history_1 68:69 +8
 insert line-bookmark : +9
-insert history_1 67:78 +10
+insert history_1 69:80 +10
 insert line-scrolldown : +21 XXXXXX :[18] v
 insert lines-status-prompt-foobarbaz : +22 X123456789X123456789X :====(0 [(1:18)] (2:7)