Notification record identity and removal
Keep collection identity separate from view identity. Notification decorators compare their type and entity ID; NotificationsCollection rejects a duplicate with the same pair instead of replacing its data. Use the model's update path when an existing record changes. A numeric ID alone does not identify a record across notification types.
BaseNotificationView allocates its own Flash IDs for notification-info tuples. The list click handler translates the UI ID back before handing the action to MVC. Do not store a view's Flash ID as a durable notification handle.
The collection bounds each type's retained list. Appending beyond its limit removes the oldest inserted record, captures its removal metadata, clears the decorator and emits a removal event. Clearing a decorator unsubscribes it, sets its entity ID to zero and clears its view data. A retained Python reference can therefore point to an invalidated record.
For single-record removal, NotificationsModel.removeNotification captures group/counting metadata before calling the collection, then emits the model's removal event. Calling collection.removeItem directly clears the record without that model event. Preserve the owning model's operation rather than treating direct collection mutation as an equivalent UI/counter update.
Unread counters are separate from stored cards
Notification counters store sets of (typeID, entityID, countOnlyOnce) entries per group. A repeated increment of the same entry does not add another count, and previously seen count-once entries are excluded. The badge is not the length of the notification collection.
Source excerpt from _GroupCounter:
def count(self):
return len(self.__notifications - self.__seenNotifications)
The list view builds messages for its current group, submits that list and resets the group's notified count. That reset leaves the stored cards in place; it is a UI acknowledgement boundary, not proof that a person inspected each card. INFO resets also forward to the service-channel unread owner; INVITE resets forward to the prebattle invite owner when available. Resetting is therefore not a passive query. Counts for other groups follow their separate receive/update policies. In popup mode the viewer also applies notification, group and priority rules before changing counts, so a receive event is not an unconditional badge increment.
Resetting a group first remembers the current count-once entries, then clears its pending counter set. Removing an entry or clearing unread state does not itself erase that seen history. NotificationMVC normally reuses the counter while replacing the model; only its explicit resetCounter branch constructs a new counter. A mod should not assume that recreating the list/model makes a stable count-once ID new again.
These are version-sensitive Python collection and consumer contracts verified against current public source. They do not establish actual popup display, a reader's attention, native rendering timing or server-side delivery.