镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

mobile: persist user profiles so names and avatars render on launch - #9116

Merged
brow merged 3 commits into
mainfrom
tomb/mobile-profile-cache
Nov 9, 2026
Merged

brow merged 3 commits into
mainfrom
tomb/mobile-profile-cache

Conversation

@brow

@brow brow commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

On a slow connection, mobile shows npubs and blank avatars on every cold launch and community switch, because user profiles live only in memory and are refetched from the relay each time.

This persists the verified profiles the app has already seen, per identity and relay, so names and avatar URLs render on launch before the relay answers.

Behavior

  • Profiles are stored as a disposable cache in the same shape as the existing channel list snapshot: one SharedPreferences entry per identity and relay, versioned, capped at 2000 profiles and 2 MB (oversized inline avatars are skipped), deleted on sign-out and by orphan cleanup.
  • On launch the cache restores the snapshot synchronously. Each restored profile is still revalidated against the relay on its first lookup that session. get and preload return the restored data right away instead of waiting on the network, and a failed revalidation retries on the next lookup.
  • Identity-sensitive callers keep their guarantees: resolve() and refresh() treat restored profiles as unconfirmed and query the relay, including while a background revalidation is still in flight.
  • Writes are debounced (2 s) and flushed when the community scope changes.

Not in this PR: avatar image bytes still use only Flutter's in-memory image cache, so avatar images still download after a cold launch (names and URLs no longer wait). The NSE keeps its own app-group profile cache, which this doesn't share yet.

Demo

Cold relaunch into a synthetic Solo chat ("QA demo"), iPhone 17 Pro simulator, same clip timing on both builds. To make the gap visible on a fast connection, both builds had a local-only 5 s delay on profile fetches (not part of this PR). The channel's system lines show the member name once its profile is known and a shortened npub until then.

Before (main): "npub1...created this channel" for about 5 s until the relay answers, and the agent avatar starts as a letter placeholder.

before.mp4

After (this branch): "tomb created this channel" and the agent avatar render from the first frame.

after.mp4

Validation

  • Added tests: a journey across three simulated launches (relay reachable, offline, reachable with a rename) covering restore, non-blocking revalidation, retry after failure, a concurrent resolve() during revalidation, and sign-out removal; plus storage scoping, bounds, writer retirement and corrupt-data handling. The revalidation and in-flight guards were each removed once to confirm the journey fails.
  • Full mobile check and test suite pass locally.
  • iOS simulator (iPhone 17 Pro, Remote Agents Pilot as Tom's Builderlab account): after browsing, the snapshot held 16 verified profiles (6.4 KB), and cold relaunch rendered normally. Offline cold launch was not exercised on the simulator.
  • iOS simulator, synthetic Solo chat, main vs this branch (see Demo): with profile fetches artificially delayed 5 s, main showed shortened npubs in the channel's system lines until the relay answered, and this branch showed the restored names on the first frame.

🤖 Generated with Claude Code

square-tomb and others added 2 commits November 5, 2026 09:45
…tars

Restore verified kind:0 profiles from a per-identity, per-relay snapshot on
launch so names and avatar URLs render before the relay answers, then
revalidate each restored profile once per session without blocking callers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
A revalidation in flight no longer marks its restored profile as confirmed,
so a concurrent resolve() still queries the relay. Repeat lookups skip
pubkeys already on the wire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tom Brow <tomb@squareup.com>
@brow

brow commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-08T21:57:14.095428Z 94e82dd Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94e82dd64c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (!_isCurrent(generation)) {
throw StateError('Profile request belongs to a retired cache');
}
_unrevalidated.removeAll(toFetch);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Evict restored profiles absent from revalidation

When a profile restored from disk has since been deleted from the relay, a successful empty history response reaches this line and merely marks the old entry as revalidated; state and _profileEventOrders still retain and later re-persist its stale name and ownerPubkey. Consequently, resolve()/refresh() report success while identity-sensitive DM logic can continue treating a deleted agent profile as authoritative. Remove requested restored entries not represented by a successful batch response before clearing _unrevalidated.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Evict absent restored profiles before marking them confirmed

There’s a concrete downstream effect here: the DM view refreshes participant profiles, then uses cached ownerPubkey to classify agent DMs and hide Huddle. An empty successful response leaves the restored owner hint in place while marking it confirmed, so that classification can remain stale. Could successful revalidation remove absent restored entries and their ordering metadata, and persist that removal?

Posted by Codex (gpt-6.1-sol) on behalf of kylez

Comment on lines +85 to +90
.then(
(saved) {
if (!saved) debugPrint('[UserCache] snapshot was not saved');
},
onError: (Object error, StackTrace stack) {
debugPrint('[UserCache] snapshot write failed: $error');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain failed snapshot writes for retry

When SharedPreferences.setString returns false or throws, this continuation only logs the failure after the debounce timer has already been cleared. If no later profile mutation happens, the cache remains dirty only in memory and the next cold launch loses every profile learned in that session; preserve a retry record and retry with bounded backoff or propagate the failure instead of abandoning the write.

AGENTS.md reference: AGENTS.md:L220-L227

Useful? React with 👍 / 👎.

);
_unrevalidated.add(key);
}
return _latest = restored;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 P2 · Restored picker profiles never start verified-name discovery

Restoring entries into state makes watchIdentityNames’ presence check skip preload for those displayed identities. These selector-only consumers therefore never reach _scheduleFetch or _trackVerified; _unrevalidated alone does not initiate either operation. Agents and recipient pickers are affected even with a healthy relay.

User/system effect: After a cold launch, Agents can keep showing an old cached name/avatar over fresh directory information. A snapshotted recipient with a different available NIP-FI verified name keeps their self-declared name in search results and selected Flutter chips/native rows until an unrelated lookup registers them. Uncached recipients do start that discovery.

Test: Persist a recipient’s kind:0 profile, restart, then search for and select them without another profile lookup. Return a different verified name from the relay and check that it is requested and replaces the cached label while cached content renders immediately.

@brow
brow marked this pull request as ready for review November 8, 2026 21:53
@brow
brow requested a review from a team as a code owner November 8, 2026 21:53
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 8, 2026
@github-actions

github-actions Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated, security-focused review generated by Codex.
Use it as a supplement to human review; false positives are possible.

Scope

  • Exact PR diff: 8cce12f591988f29cc2beab5ff0c89d51862f31b...bdbd4a48c130a12a6a8816dde3adba13194fcb9d
  • Model: gpt-6.1-sol

💡 Click "edited" above to see earlier reviews for this PR.


Review Summary

Overall Risk: NONE

No concrete security, correctness, or reliability regressions found in the authorized PR range.

Findings

No concrete security, correctness, or reliability findings were identified.

Notes

  • Review used read-only source and history inspection. Builds and tests were not executed, as requested.

Generated by Codex Security Review |
Requested by: @brow |
Workflow run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94e82dd64c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

);
_unrevalidated.add(key);
}
return _latest = restored;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Trigger revalidation for direct cache consumers

When the app cold-launches directly into a surface such as Agents, watchIdentityNames treats every restored key as present and only preloads missing keys (identity_names_provider.dart:67-75), so it never calls get or preload for these entries. Returning the restored map here without scheduling its entries therefore lets an old name, avatar, or owner attribution remain in use for the entire session unless an unrelated surface later requests that pubkey; direct provider-state consumers need to schedule first-use revalidation too.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 8, 2026
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 8, 2026

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kyle reviewed the PR and the Nexus review-tracker notes and approved with the non-blocking comment above.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@tlongwell-block tlongwell-block left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Meli, Tyler’s AI agent, posting on his explicit behalf.

Approved as directed by Tyler.

Full mobile suite passed (4549 main, 67 skipped, 4 push, 27 crypto). Repeated cold-offline Home captures corroborate persisted display-name restoration. They do not establish sub-five-second restoration, timeline hydration or avatar bitmap persistence. An earlier intermittent test failure did not recur in 40 alternating main/PR runs; its cause remains unattributed.

Reviewed head: bdbd4a48c130a12a6a8816dde3adba13194fcb9d.

@brow
brow merged commit 733d164 into main Nov 9, 2026
53 checks passed
@brow
brow deleted the tomb/mobile-profile-cache branch November 9, 2026 14:58
bostonaholic added a commit that referenced this pull request Nov 9, 2026
…ction-p

* origin/main: (111 commits)
  feat(mobile): tag the reacted-to message author on reactions (#9141)
  fix(mobile): steady New DM picker rows and animate picks (#9249)
  perf(mobile): keep remote images on disk and show small ones as thumbnails (#9158)
  fix(relay): deliver slow cross-node events late instead of closing their recipients (#9235)
  fix(mobile): read only shown events from filtered Activity rows (#9215)
  fix(mobile): show the channel name as the notification subtitle (#9173)
  fix(mobile): keep Home's header clean under sheets (#9152)
  fix(mobile): show already-loaded custom emoji in the iOS reactions sheet (#9150)
  mobile: add follow and remind actions to inbox row menu (#9142)
  fix(mobile): start no threads in Solo chats (#9123)
  mobile: persist user profiles so names and avatars render on launch (#9116)
  fix(mobile): keep a sent thread reply above the composer (#9026)
  [buzz] Recover database pool capacity after stalled connections (#9267)
  feat: send a User-Agent on relay WebSocket upgrades (#9246)
  Record why relay connections close and quiet close warnings (#9245)
  fix(relay): say "channel owner or admin" in the call_webhook save refusal (#9169)
  feat(mobile): consolidate Solo chat settings in one sheet (#9255)
  fix(cli): preserve attachments across successive edits (#9226)
  fix(mobile): swipe left to reply so swipe-back keeps rightward drags (#9127)
  mobile: long-press a message link for open, copy and share (#9135)
  ...

Signed-off-by: Matthew Boston <mboston@squareup.com>
fsola-sq added a commit that referenced this pull request Nov 9, 2026
…-profile

* origin/main: (307 commits)
  fix: inline ARM64 atomics in Sprig image build (#9291)
  fix(mobile): show observed thread replies in Activity (#9286)
  Enable mobile huddles with Codex agents (#9269)
  fix(mobile): read mentions taller than the screen (#9243)
  test(relay): give every media e2e upload its own blob hash (#9272)
  Add invite preview video and fallback poster (#9282)
  NEXUS-4336: Wait for desktop-provider socket ownership proof (#9270)
  fix: unblock image audit with patched MCP SDK (#9287)
  feat(ci): allow admin dispatch of desktop candidate validation (#9288)
  feat(mobile): tag the reacted-to message author on reactions (#9141)
  fix(mobile): steady New DM picker rows and animate picks (#9249)
  perf(mobile): keep remote images on disk and show small ones as thumbnails (#9158)
  fix(relay): deliver slow cross-node events late instead of closing their recipients (#9235)
  fix(mobile): read only shown events from filtered Activity rows (#9215)
  fix(mobile): show the channel name as the notification subtitle (#9173)
  fix(mobile): keep Home's header clean under sheets (#9152)
  fix(mobile): show already-loaded custom emoji in the iOS reactions sheet (#9150)
  mobile: add follow and remind actions to inbox row menu (#9142)
  fix(mobile): start no threads in Solo chats (#9123)
  mobile: persist user profiles so names and avatars render on launch (#9116)
  ...

Signed-off-by: Franco Sola <fsola@squareup.com>

# Conflicts:
#	docs/nips/NIP-FA.md
#	docs/nips/NIP-FI.md

This branch was previously deployed

1 inactive deployment
codex-review — bdbd4a48 Deployed Nov 8, 2026 by brow via Run Codex Security Review #13899
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex-security-review-current The posted Codex security review matches its recorded range.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants