Files
Alex Jbanca 2fb1589483 perf: mobile section extras (#21975)
* perf: port mobile section extras onto the virtualized chat stack

Ported from perf/section-loading-skeletons (the mobile mega-branch) onto
perf/chat-list-virtualization, keeping the stack's reworked chat-list
virtualization and section skeletons where the two overlapped:

- message row diet: lazy tooltips, header slimming, async avatar,
  clock-free rows, lazy sender lookup
- per-message mention resolution (MentionResolver)
- context menus built on first use (chat list and message rows)
- long-press opens the message context menu over text
- deferred first chats build until the section transition settles
  (seaqt QTimer fallback + QML settle notification)
- MessagesListHeader extracted and shared with the section skeleton
- chat/community section skeleton composition in AppMain
- StatusBaseButton emoji created on demand
- emoji and stickers popups loaded asynchronously
- day-granularity counter on StatusSharedUpdateTimer
- icon badge count from section + activity-center notifications
- QR capture, onboarding AOT-friendliness and misc row fixes

revert(navbar): drop the async PrimaryNavSidebar content and its benchmark

Back to the synchronous contentItem; the slideAnimationRunning alias stays
for AppMain's transition-settled notification.

chore(tests): drop tst_ChatListVirtualization, covered by the virtualization PR's suites

fix(chat): hide incubating panels behind their skeletons

- ChatView: left and center panel loaders stay invisible until Ready so
  the section skeleton isn't overdrawn by half-built panels. No gate on
  the right panel loader — it would hide the nested membersPanelSkeleton.
- ChatContentView: the message-rows skeleton moves out of the gated
  loader (a child would inherit the invisibility) and the messages view
  is gated the same way.
- tst_CommunityChatLoaderSection: gestures wait for a full render pass
  after the panels flip visible; drop the flaky empty-area right-click
  test.

chore: update translations

revert timestamp and long press

fix: add StatusLazyToolTip to qrc

undo DnD

* fix(chat): restore first-channel fallback on the deferred section build

If the persisted last-open chat no longer exists, buildChatSectionUI
selects nothing and the deferred path had no repair step (the loaded
path in onActiveSectionChange has one). Re-check after the build and
fall back to the first channel, before completeActiveSectionChange so
the active-chat notification carries the repaired id. Runs inside the
deferred tick — the tap/skeleton/animation path is untouched.

* fix(statusq): don't restyle detached subtrees through the Light fallback

Unparenting an item re-links its Theme attached node to the engine-level
fallback node, which is Light by default. attachedParentChange inherited
that style immediately, so every LayoutItemProxy handoff (section
switches hand panels between proxies) round-tripped the whole subtree
through Light and back in a dark app, re-firing every Theme.palette
binding — 224 message rich-text re-renders and a measured 2.3s
GUI-thread freeze per section switch on device (light theme: 96ms).

Defer the inherit while the item has no window; attachWindowReadySync
already re-inherits at window landing, before anything renders, so the
anti-flash intent of the previous order is preserved and first-attach
of incubated panels still gets exactly one style application.

* test: provide the hidden role in the ported chat-list mocks

The chat list filters rows on the model-computed "hidden" role
(PR #21943); rows without it are filtered out by design. The mocks
introduced by the mobile port predate the role, so their lists rendered
empty in CI (tst_ChatLoaderSection count 0/300, tst_ChatListContextMenu
right-click found no row). Add the role to the row builders and mirror
the backend recompute in CommunitySectionMock.setCategoryOpened.

* fix(chat): tolerate optional/lazy dependencies in the chat views

Cleans up the TypeErrors the loader test suites surfaced, three of
which are real product races:

- ChatLayout projected community stores off a null newCommunityStore —
  always null for the personal chat section and transiently while
  CommunityChatLoader swaps sections
- MessageView dereferenced the emoji popup for the message context
  menu and the add-reaction path, but the popup loads lazily after
  startup and either can run first
- ChatColumnView.restoreInputState is scheduled via Qt.callLater and
  can fire while the view tears down, after the dying context dropped
  its functions

Plus the tst_ChatLoaderSection mock gaps (clearLinkPreviewCache,
pinnedMessagesModel) behind the remaining warnings.

* chore: update translations

* test(chat): retry the synthesized drag gesture in the reorder tests

The automatic drag's nested loop occasionally misses the synthesized
press-move-release on a loaded machine (~1/35 locally, seen on CI's
Xvfb), killing the gesture mid-flight and leaving no reorder to assert.
Both reorder tests now share a dragRowVertically helper that re-runs
the whole gesture up to three times, re-reading the row's live position
and letting the rows settle between attempts. The correctness asserts
are unchanged.

* chore: update translations

* perf: tweak the incubation controller

boost mode will freeze until completion

* fix(chat): don't raise the soft keyboard on first chat open

The mention suggestion box restores focus to the composer whenever it
becomes invisible. SuggestionBoxPanel is a Control, so `visible` starts
true and the binding resolving it to false runs during construction —
firing the handler once per StatusChatInput, before the chat is on
screen, and raising the Android soft keyboard.

That branch exists only to undo the `listView.forceActiveFocus()` grab
three lines below it, which is already guarded desktop-only. On mobile it
can therefore only ever be a no-op (the user was already typing) or this
bug; the one mobile path that does need focus back — tapping a
suggestion — restores it itself in selectItem().

Reachable because the centre panel now loads asynchronously: the existing
guard in ChatColumnView only arms on a `visible -> false` edge, which no
longer occurs now that the column is born effectively invisible.

Device-verified on a Redmi A5: showSoftInput() calls during a cold start
plus first chat open went from 4 to 1, the survivor being the login
password field.

* perf: don't build a Home page before the active section is known

On cold start a complete HomePage was constructed synchronously on the
GUI thread and immediately discarded. Two independent causes, both
needed:

SectionType's zero value is HomePage, so the default-initialised
SectionDetails read as a real destination for the whole of Module.load()
— from the moment mainModule is exposed to QML until setActiveSection
resolves. Seed it with LoadingSection instead, which is already what
load() falls back to when no section resolves.

HomePageLoader keyed its `active` off appView.currentIndex, and
StackLayout initialises currentIndex to 0 — the home page's own index —
before its own binding first evaluates. Since that loader is
synchronous, the transient was enough to build the whole page. Key it
off the section type instead, which has no such default. Only this
loader was affected; nothing else sits at index 0.

Also restores the startup overlay's opaque backgrounds. The skeleton
panels that superseded ChatLayoutLoading paint tiles only, so the
overlay had become fully transparent and whatever the stack layout was
showing underneath bled through — which is how the discarded Home page
was visible in the first place.

Device-verified: runtime HomePageLoader warnings went from 3 to 0, and
no HomePage type is instantiated during startup.

* perf(chat): release the section skeleton before the messages view builds

Two fixes to what the centre panel waits for and what it builds.

The section skeleton — the one that draws its own input bar — covered
the entire message build instead of handing over to the per-chat
skeleton. centerPanelReady is `centerPanelLoader.status === Loader.Ready`,
and that loader did not resolve until the messages view had finished,
even though chatMessagesLoader is asynchronous: incubated alongside the
column, it is what the column's own Loader ends up waiting on. Gate
chatMessagesLoader on the centre panel having reported Ready, so the
shell appears first and the messages fill in behind the per-chat
skeleton — the behaviour the surrounding comments already described.

Measured on a Redmi A5, from the chat tap: the repeater's loader used to
sit 2553ms after ChatContentView had already been created; it now takes
15ms. Section skeleton retires at ~950ms instead of 3546ms. Note this
re-orders work rather than removing it — time to messages on screen is
roughly unchanged; what changes is that the user gets real chrome and a
usable composer about 2.5s earlier.

Second, a chat was given a content view without ever having been shown.
setActiveItem latches loaderActive, and the section build calls it for
the restored last-open chat — which in portrait is never displayed,
because the centre panel is lazy. Opening a different chat therefore
built two content views, and the one nobody was looking at held the GUI
thread for 2183ms after the tapped chat was already ready. Build on
"has been the active chat while this column existed" instead; the
backend latch still governs lifetime from there.

Kept in QML rather than changing the Nim latch so desktop is unaffected:
there the column exists from section load with the restored chat active,
so it is marked shown immediately.

* fix: match the startup overlay to the shape the section opens on

In portrait the chat section lands on the left panel — the chat list —
but the startup overlay drew the centre panel: chat header, message rows
and an input bar. The layout therefore changed under the user the moment
the real section arrived. Show the chat-list skeleton in portrait and
keep the three-panel arrangement for landscape.

Also corrects the panel colours. They were taken from the ChatLayoutLoading
view this overlay replaced, which used secondaryMenuBackground; the chrome
it now stands in for uses baseColor4 for the side panels and the section
background for the centre (StatusSectionLayoutPortrait, Landscape).

* fix: don't assert a section title in the startup overlay

The startup overlay paints before the backend has said which section is
active, but it drew MessagesListHeader — which hardcodes the title
"Messages" and offers invite / start-chat / search. None of that is right
for a section that may turn out to be a community, and in that mode the
header's signals are not connected, so the two enabled buttons rendered
as tappable and did nothing.

Give MessagesListSkeleton a `sectionKnown` flag. When false it draws a
placeholder row of the same shape — headline left, three round buttons
right — sized from the same theme values so it follows the font-size
setting. ChatLoader keeps the default and the real, working header.

`createChatOpened` stops being an alias: the header now sits behind a
Loader, and an alias into a deferred subtree does not work.

* test(chat): pick channel rows the view has actually placed

The reorder and click tests picked their rows by scanning the list's
contentItem for StatusChatListItem and sorting by scene y. A ListView
incubates its cache-buffer delegates asynchronously, and an incubating
delegate is already parented, visible and sized but not yet positioned,
so it sits at y 0 and ties with the real top row. Under load it won the
sort, the upward drag then started on the real top row and dragged it
out of the list into no drop target, and no reorder ever landed.

Select rows through itemAtIndex instead, which only ever returns placed
delegates, and settle on those too. The gesture retry added for the same
red is gone: it was treating a symptom, and the drag lands first try in
90 loaded offscreen runs.
2026-08-22 08:13:54 +03:00
..