fix(gui): channel grid randomly blank after adding a post

Adding a post to a channel sometimes left the grid empty; switching to
another channel and back made the posts reappear. The model data was
always correct, but the *view* was corrupted by malformed Qt model
signals emitted on the incremental single-post path.

RsGxsChannelPostsModel::updateSinglePost() emitted its own
layoutAboutToBeChanged() and then called updateFilter(), which emitted a
second layoutAboutToBeChanged()/layoutChanged() pair. That left one
layoutAboutToBeChanged() unmatched, so the attached grid view stayed in a
"layout changing" state. On top of that, updateFilter() faked a full
beginInsertRows(0, rowCount()-1)/endInsertRows() over rows that already
existed, lying to the view about the model structure. Whether this
blanked the viewport depended on grid geometry (post count modulo the
column count) and async/repaint timing, hence the intermittent behaviour.
A full model reset (beginResetModel in initEmptyHierarchy, triggered by
switching channels) always rebuilt the view correctly, which is why the
workaround worked.

Fix: emit a single balanced layout-change (preMods()/postMods()) and drop
the bogus beginInsertRows(). layoutChanged() is the correct signal for the
row<->post reflow that std::sort() produces.

Also reset the active filter in initEmptyHierarchy() (run by setPosts() on
every full reload): the widget clears the search box silently (whileBlocking)
without notifying the model, so a filter left over from a previously viewed
channel was otherwise re-applied by updateFilter() on the next incoming post
and could hide every post. Placing the reset in initEmptyHierarchy() keeps
the fix self-contained and independent of how setPosts() is implemented.

Boards (Posted) carried the same beginInsertRows() anti-pattern in
setFilter() and setPosts(); the lie-to-the-view is removed there too for
correctness and consistency. setPostsInterval() (a legitimate paginated
insert/remove) is left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jolavillette 2026-07-20 00:07:07 +02:00
parent 6160b39dab
commit 978b30fcee
2 changed files with 26 additions and 19 deletions

View File

@ -223,12 +223,10 @@ void RsPostedPostsModel::setFilter(const QStringList& strings, uint32_t& count)
std::cerr << "After filtering: " << count << " posts remain." << std::endl;
if (rowCount()>0)
{
beginInsertRows(QModelIndex(),0,rowCount()-1);
endInsertRows();
}
// No beginInsertRows() here: the filtered set is rebuilt from scratch above, so
// the surrounding reset + postMods() (layoutChanged) refresh the view. Faking a
// row insertion with a mismatched range is a lie to the view (same anti-pattern
// that could blank the channels grid) and is not needed.
postMods();
}
@ -572,12 +570,9 @@ void RsPostedPostsModel::setPosts(const RsPostedGroup& group, std::vector<RsPost
mDisplayedNbPosts = std::min((uint32_t)mFilteredPosts.size(),mDefaultDisplayedNbPosts);
mDisplayedStartIndex = 0;
if (rowCount()>0)
{
beginInsertRows(QModelIndex(),0,rowCount()-1);
endInsertRows();
}
// No beginInsertRows() here: setFilter() above already reset and repopulated the
// model, so postMods() (layoutChanged) is enough. Faking a row insertion with a
// mismatched range is the same anti-pattern that could blank the channels grid.
postMods();
emit boardPostsLoaded();

View File

@ -75,6 +75,15 @@ void RsGxsChannelPostsModel::initEmptyHierarchy()
mPosts.clear();
mFilteredPosts.clear();
// Reset the active filter on every (re)load. initEmptyHierarchy() is run by
// setPosts(), and the widget clears the search box silently (whileBlocking)
// without notifying the model; a filter left over from a previously viewed
// channel would otherwise be re-applied by updateFilter() on the next incoming
// post and could hide every post. Doing it here keeps the fix independent of
// how setPosts() is implemented.
mFilteredStrings.clear();
mFilterUnread = false;
endResetModel();
}
@ -147,12 +156,12 @@ void RsGxsChannelPostsModel::updateFilter(uint32_t& count)
count = mFilteredPosts.size();
if (rowCount()>0)
{
beginInsertRows(QModelIndex(),0,rowCount()-1);
endInsertRows();
}
// The row<->post mapping is fully rebuilt above (posts may have been added,
// removed or reordered by the caller), so we signal a layout change through the
// preMods()/postMods() pair rather than faking a row insertion. Emitting
// beginInsertRows() with a range that does not match a real insertion left the
// attached view inconsistent and could randomly blank the grid right after a
// post was added (only a full channel reload fixed it).
postMods();
}
@ -461,7 +470,10 @@ void RsGxsChannelPostsModel::updateSinglePost(const RsGxsChannelPost& post,std::
added_files.clear();
removed_files.clear();
emit layoutAboutToBeChanged();
// NB: no emit layoutAboutToBeChanged() here. The single balanced layout-change
// signal is emitted by updateFilter() below (preMods()/postMods()). Opening a
// second, unmatched layoutAboutToBeChanged() here left the view stuck in a
// "layout changing" state and could randomly blank the grid after a new post.
// linear search. Not good at all, but normally this is just for a single post.