Description
I'm writing this one up near the end of the day, at the start of a holiday, just so it's written down and I/we don't forget. Annoyingly I can't recreate the problem in isolation, only in the code I'm working on at the moment.
It is possible to get into a situation where an attempt to mount
a widget with an ID that already exists amongst its would-be siblings results in a NoScreen
error being thrown from somewhere deep in the compositor code rather than the DuplicateIds
, which is what you'd expect.
The error I was seeing is follows at the end of here.
Quick testing suggests that NodeList._append
and NodeList._insert
are the issue here. Making a change akin to this:
@@ -74,12 +74,12 @@ class NodeList(Sequence["Widget"]):
widget: A widget.
"""
if widget not in self._nodes_set:
- self._nodes.append(widget)
- self._nodes_set.add(widget)
widget_id = widget.id
if widget_id is not None:
self._ensure_unique_id(widget_id)
self._nodes_by_id[widget_id] = widget
+ self._nodes.append(widget)
+ self._nodes_set.add(widget)
self._updates += 1
def _insert(self, index: int, widget: Widget) -> None:
seems to address the issue.
If I can work out an isolated reproduction of this I'll add it here, but the above change does seem to make some sense I think.
...
│ │
│ /Users/davep/develop/python/textual/src/textual/dom.py:477 in screen │
│ │
│ 474 │ │ while node is not None and not isinstance(node, Screen): │
│ 475 │ │ │ node = node._parent │
│ 476 │ │ if not isinstance(node, Screen): │
│ ❱ 477 │ │ │ raise NoScreen("node has no screen") │
│ 478 │ │ return node │
│ 479 │ │
│ 480 │ @property │
│ │
│ ╭───────────────────────────────────── locals ──────────────────────────────────────╮ │
│ │ node = None │ │
│ │ Screen = <class 'textual.screen.Screen'> │ │
│ │ self = RightAlignToast(id='textual-toast-91b49506-d61a-4698-ac8b-5bc491fdd5c9') │ │
│ ╰───────────────────────────────────────────────────────────────────────────────────╯ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
NoScreen: node has no screen