6 Commits
Author SHA1 Message Date
Alex Jbanca 68eb973c84 perf(chat): lazy, async member loading for user list and mention suggstions (#21920)
* perf(chat): lazy, async member loading for user list and mention suggestions

The members panel builds on first show only (latched), asynchronously
behind MembersListSkeleton — it must never delay a chat switch. Once
built, UserListPanel binds its users model only while the right panel is
actually visible, and it adopts the shared MembersPanelHeader introduced
with the skeletons. The mention suggestions adaptor stays detached from
the members model until the first mention entry (short wire delay,
loading skeleton in the suggestion box meanwhile; the latch drops on
usersModel swap so chat switches never pay for sorting the members
model). Together these were ~1.5s of the chat-switch freeze on large
communities.

* fixup! perf(chat): lazy, async member loading for user list and mention suggestions

The members panel is shared across the section's chats, so once built it
stayed visible through a chat switch: it showed the previous chat's
members while sorting the incoming model on the switch frame. A latch
drops it back behind MembersListSkeleton on every active-chat change and
re-arms on a short delay, the same shape as the mention suggestions
wiring. rightPanelReady now follows the loader it was named after.

* fixup! perf(chat): lazy, async member loading for user list and mention suggestions

Keying the latch on the active chat id dropped it on every switch, but a
community channel without permissions hands the view the *same* members
model object — so the panel was torn down and fully re-sorted for nothing,
on the dominant path. Key it on the model identity instead: same object,
same members, no work; different object, the protection the latch is for.

rightPanelReady must fold the latch in, or the chrome retires its members
skeleton exactly while the panel is showing one. membersWireDelay moves
into `d` (tests reach it through the view's private object), and the
panel's own skeleton is released like the others rather than kept alive
and hidden.

The switch test now targets the permissioned channel, whose model really
differs, and a new test pins that the shared-model switch costs nothing.

* fixup! perf(chat): lazy, async member loading for user list and mention suggestions

Extract the fully-wired CommunityChatLoader test harness (mock stack,
loader wiring, type-prefix finders) into a shared helpers component.
The members-skeleton suite is the first consumer; the community section
suite above adopts it at the next stack rebase, removing ~90 duplicated
lines per suite.

* chore: update translations

* perf: sorted users_model and PR comments

* perf: Avoid swapping skeleton for the real view when swipeview animations are running

* chore: update translations

* chore: update translations
2026-08-22 08:13:52 +03:00
Alex Jbanca 4483d7eb2d fix(ios): pause/resume services with the app lifecycle and re-point media URLs on rebind (#21687)
* fix(media): re-point cached media URLs when the media server restarts (#47)

iOS suspends the app and kills status-go's local media server; on resume
it rebinds on a NEW ephemeral port and emits mediaserver.started, which
the Nim side never handled — every cached https://localhost:<oldport>/
image URL (chat images, avatars, stickers, link previews, community
icons) went permanently stale.

Key decisions:
- New MediaServerStartedSignal decoded in signals_manager (the enum
  member already existed, so the cheap-triage scan needs no change);
  null-event envelopes decode to port 0 and every rewrite helper treats
  port <= 0 as a no-op.
- One shared helper, withMediaServerPort (app_service/common/
  media_server_url.nim): rewrites the port ONLY for http(s)://
  localhost|127.0.0.1|0.0.0.0:<port> URLs, splicing around the authority
  so path/query stay byte-identical; everything else (remote URLs, data
  URIs, qrc/file paths, port-less URLs) passes through untouched, so
  callers apply it blindly. Desktop stays a no-op: the signal never
  fires there, and an unchanged port rewrites nothing.
- Subscribers rewrite in place + notify: message models (chat + pinned)
  re-emit image-carrying roles via dataChanged; link-preview thumbnails
  are QObjects and emit urlChanged directly; section model refreshes
  community image/banner/icon; contacts service rewrites its
  ContactDetails cache and re-emits SIGNAL_CONTACT_UPDATED per changed
  contact, plus the user-profile singleton's own avatar (its setters
  already no-op on equal values).
- Dropped the contacts service's imageServerUrl field: it was write-only
  dead code (fetched once, never read).

Files: src/app/core/signals/{signals_manager,types}.nim,
src/app/core/signals/remote_signals/mediaserver.nim (new),
src/app_service/common/media_server_url.nim (new),
src/app_service/service/contacts/service.nim,
src/app_service/service/message/dto/{link_preview,link_preview_thumbnail}.nim,
src/app/modules/shared_models/{message_item,message_model,link_preview_model,section_model}.nim,
chat_section chat_content + messages + main module/controller/io_interface
wiring, test/nim/{media_server_url_test (new),message_model_test,
signals_manager_test}.nim

Verified: media_server_url_test 9/9 OK, signals_manager_test 6/6 OK
(incl. 2 new mediaserver.started tests) via make nim-test-run USE_SYSTEM_NIM=1.
message_model_test does NOT link in this arm64 container — pre-existing
nimqml/LTO link failure, reproduced identically on clean HEAD with a
fresh nimcache; the 3 new model tests compile but need CI/another host
to run. Device repro (background 10+ min, resume, images reload) remains
for the human pass.

* fix(build): normalize two 'import Nimqml' casings to the module's real name

discord_message_item and message_transaction_parameters_item imported
Nimqml (capital N) while the vendored module file is nimqml.nim. On a
case-insensitive checkout (Docker-on-Mac bind mount) Nim treats the two
spellings as distinct modules whose nimcache artifacts collide on one
file, so any test pulling in these items (e.g. message_model_test) fails
to link with undefined nimqml symbols. Found while verifying #47.

* refactor(media): deduplicate the media-URL refresh template and drop dead code

Review follow-up to ca5e5c92c, no behavior change:
- Extract the thrice-duplicated compare-rewrite-flag template into
  refreshMediaServerUrl in media_server_url.nim; message_item,
  section_model and contacts/service now share one definition.
- Drop the why-comment copy-pasted verbatim into three controllers;
  the explanation lives on MediaServerStartedSignal and the helper module.
- Remove backend getImageServerURL, dead since its only caller
  (setImageServerUrl) was deleted on this branch.
- Whitespace: trailing newline in signals/types.nim, blank line before
  the appended suite in message_model_test.nim.

Verified: media_server_url_test, message_model_test and
signals_manager_test all pass (nim-test-run recipe); app wiring
compile-checked via app/modules/main/module + contacts service +
section_model with --compileOnly.

* fix(ios): drive PauseServices/ResumeServices from the app lifecycle (#51)

On iOS status-go runs in-process and nothing drove the pausable-services
lifecycle: services never paused on backgrounding (battery cost) and never
resumed on foregrounding, so the media server's listening socket iOS kills
during suspension stayed dead and every cached localhost media URL failed
until app restart (device-confirmed: post-resume image loads get Connection
refused while in-process RPC still works). Resuming now re-runs the full
recovery chain: ResumeServices -> ServiceRegistry -> mediaserver
ToForeground() rebind -> mediaserver.started -> #47/#49's URL refresh.

Key decisions:
- Lifecycle source is StatusQ's UrlSchemeEvent applicationStateChanged
  watcher (already the iOS foreground seam): new appBackgrounded signal
  emitted ONLY on Qt::ApplicationSuspended — Inactive dips from share
  sheets/system alerts never pause; appForegrounded fires on every return
  to Active, so ServicesPauseBridge latches (`paused`) and resumes only
  when it actually paused. Connections are synchronous (AutoConnection,
  same thread): iOS freezes the process right after the state change, a
  queued pause slot might never run.
- Bridge wired only under `when defined(ios)`: Android's service process
  already drives pause/resume from binder visibility (UI process must not
  double-drive), desktop is never suspended.
- Same contract as StatusGoService.java: fetch the service list from
  PausableServices() at each transition (late-registered services picked
  up; empty list — node not running, e.g. login screen — drives nothing),
  then PauseServices/ResumeServices with the JSON name array. The three
  libstatus C exports are bound in new backend/pausable_services.nim
  (vendor/nim-status-go doesn't cover them); errors logged, not fatal.
- Backend calls injected into the bridge as a seam
  (PausableServicesCalls), so the Nim test drives the real StatusQ
  signal emitters against recorders.

Files: src/app/core/services_pause_bridge.nim (new),
src/backend/pausable_services.nim (new), src/nim_status_client.nim,
src/statusq_bridge.nim, ui/StatusQ/{include/StatusQ/urlschemeevent.h,
src/{urlschemeevent,externc}.cpp}, Makefile (test target),
test/nim/services_pause_bridge_test.nim (new)

Verified: services_pause_bridge_test 15/15 OK; full tests-nim-linux 464
OK / 0 failed (chat_section_model_test fails to compile identically on
the clean base — pre-existing, unrelated); nim_status_client Linux path
compile-checked (--compileOnly, exit 0); StatusQ recompiles clean;
libstatus.so exports PausableServices/PauseServices/ResumeServices
(linked by the test). Device criteria (iPhone: photo -> background 10+
min -> resume -> images recover; Android S21: exactly one service-driven
pause/resume, no duplicates) remain for the human pass — iOS paths can't
run in this container.

* fix(tests): reset the fetch counter per case in services_pause_bridge

unittest setup vars are module-scope globals; the int's constant
initializer runs once, so fetches leaked across cases and the two
absolute-count assertions saw the running total.

* fix: Potential fix for pull request finding

* fix(statusq): handle the full application-state enum in watchApplicationState

Inactive is an expected transient dip and stays a no-op; anything else
unhandled now logs a qWarning.

* fix(tests): empty pausable set no longer latches paused, so foregrounding skips the fetch
2026-08-05 14:18:26 +03:00
Alex Jbanca 5487b67d71 fix(wallet): stable per-key nested token submodels
The 'tokens' role of token_groups_model and token_lists_model created a new
TokensModel on every data() read, overwriting a single slot shared by all rows
and consumers. Each read dropped the previously handed-out submodel while QML
still referenced it, and the data source captured the row index, so a
surviving submodel could read another row's tokens after rows shifted.

Cache one submodel per group key and resolve the row by key on demand. The
submodel follows its row's lifetime: reset when its content changes, deleted
after the parent's remove/reset signals when its row is gone.
2026-08-03 18:16:18 +03:00
Alex Jbanca 716de1e2f5 test(bench): trim size sweeps to assert-relevant sizes on CI
The bench execution alone runs ~13 minutes on CI. BENCH_QUICK=1 (set by
tests-nim-linux) keeps only the sizes the gates actually reference:
benches whose asserts pin a size keep that size, benches with purely
structural (size-independent) gates keep the smallest. Local runs keep
the full sweeps.
2026-07-30 14:07:32 +03:00
Alex Jbanca ba0eb3a223 test(bench): make wall-clock perf gates report-only on CI
Shared CI executors are too noisy for frame-budget and relative-timing
assertions (e.g. open_seed maxStall 33.87ms vs the 32ms budget). Route
them through perfAssert, downgraded to report-only when BENCH_ASSERTS=0
-- which tests-nim-linux now sets. Structural gates (resets, row churn,
counts, instantiation errors) stay hard doAsserts, so the benches keep
protecting against rot on CI.
2026-07-30 14:07:32 +03:00
Alex Jbanca 9a3d1c3a6c chore: organise nim test targets
- move the nim test targets to a dedicated nim-tests.mk file
- split tests and benchmarks in two different targets, but keep the benchmarks as part of the nim-tests-run so that it doesn't rot over time
- group tests/benchmarks that will need statusQ or QT_MODEL_SPY
2026-07-30 14:07:32 +03:00