Skip to content

feat(tinyplace): public profile viewer for arbitrary handles (#4931)#4997

Merged
senamakel merged 6 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4931-tinyplace-profiles-wallet
Jul 18, 2026
Merged

feat(tinyplace): public profile viewer for arbitrary handles (#4931)#4997
senamakel merged 6 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4931-tinyplace-profiles-wallet

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Agent World Profiles was hard-wired to the connected wallet (fetchWalletStatus()), so you could only ever see your own profile — there was no way to view another user's or an agent's profile.
  • The read handlers for an arbitrary handle already exist and are registered (graphql.profile / profiles.get/activity/groups/agent_card / follows.*) but had zero UI callers — a pure viewer gap, not a plumbing gap.
  • Add a /agent-world/profiles/:username route + a read-only ProfileViewer that renders any handle's GqlProfile (header, bio, skills, verified accounts, owned handles, follower stats), with a follow/unfollow button and a copy-link affordance for sharing.

Problem

app/src/agentworld/pages/ProfilesSection.tsx resolves the wallet's Solana address (:100) and renders only that identity, at the fixed route profiles (AgentWorld.tsx:176, no :username). The capabilities to view any profile were already plumbed end-to-end and I verified the read path exists in the pinned SDK (openhuman can only bump the vendor/tinyplace pointer, never add SDK surface):

  • SDK read surface: vendor/tinyplace/sdk/rust/src/api/profiles.rs:44 (get/activity/groups/broadcasts/attestations/agent_card, each taking a username) and the profile GraphQL query vendor/tinyplace/sdk/rust/src/api/graphql.rs:209 (USER_PROFILE_QUERY, rich GqlProfile).
  • Rust handlers call the SDK: src/openhuman/tinyplace/manifest.rs:302 (profiles.get) etc., registered at src/openhuman/tinyplace/schemas.rs:144-149.
  • TS client: app/src/lib/agentworld/invokeApiClient.ts:1726 (profiles.*), :2278 (graphql.profile), :1994 (follows.*).

So this is a genuine, fixable UI wire-up gap (not BLOCKED): every layer below the UI carries the data for an arbitrary handle.

Solution

  • Route: AgentWorld.tsx gains profiles/:usernameProfileViewer (kept alongside the own-profile profiles route). Deep-linkable and shareable.
  • ProfileViewer.tsx (new, read-only): loads graphql.profile(username) and renders the header (avatar/handle/cryptoId/bio, verified + actor-type badges), skills, verified accounts, owned handles, and follower/following counts (follows.stats). Loading / not-found / error states.
  • Follow: a follow/unfollow button reusing follows.follow/follows.unfollow(cryptoId); the initial state is derived from the viewer's own follows.following graph (universal — works for humans and agents, since GqlProfile's embedded agentCard does not carry viewerIsFollowing). Hidden for one's own profile and when signed out; optimistic toggle; degrades gracefully.
  • Share: a copy-link button that writes the HashRouter deep link (#/agent-world/profiles/<handle>) built from the live location.
  • i18n: 18 new agentWorld.profileViewer.* keys in en.ts and every locale (no em dashes); grep-friendly agentworld:profileviewer debug logging that never logs the address/handle (no PII).
  • Scoped to read/display — profile EDITING ([Tiny Place] Agent World profile editing (name/bio/avatar) is unreachable — updateProfile RPC has no UI #4930, in flight by another worker) is untouched; the viewer never mutates the viewed profile, and the change lives in new files plus one route line to avoid collision.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing StrategyProfileViewer.test.tsx: renders an arbitrary handle, not-found state, follow→unfollow toggle, pre-selected following state, own-profile hides follow, copy-link.
  • Diff coverage ≥ 80% — the new component's branches are exercised by the tests; frontend-only, CI frontend-coverage is the authority.
  • Coverage matrix updated — N/A: new UI viewer over existing handlers, no matrix feature row.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related — N/A: no matrix rows affected.
  • No new external network dependencies introduced (mock backend used per Testing Strategy) — reuses existing RPC handlers only; tests mock apiClient.
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md) — N/A: additive Agent World UI, not a release-cut smoke surface.
  • Linked issue closed via Closes #NNN in the ## Related section.

Impact

  • Runtime/platform: desktop UI (Agent World → Profiles). No Rust/core changes — N/A, no Rust changes.
  • Performance: one graphql.profile call per view plus follows.stats and (for non-self, signed-in) one follows.following lookup (limit 500) to seed follow-state; all on-demand.
  • Security: read-only; logs carry no address/handle/PII. Migration/compat: none — purely additive route + component.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/GH-4931-tinyplace-profiles-wallet
  • Commit SHA: fcc99b284210afb34b0b6e799ed71d69aa16a2a9

Validation Run

  • pnpm --filter openhuman-app format:check — ran targeted prettier on all touched files (clean).
  • pnpm typecheck — clean.
  • Focused tests: ProfileViewer.test.tsx added (not executed locally per machine disk policy; CI authoritative).
  • Rust fmt/check (if changed): N/A — no Rust changes.
  • Tauri fmt/check (if changed): N/A — no Tauri changes.

Validation Blocked

  • command: pnpm test / pnpm test:coverage
  • error: local full test/build runs are disabled on this machine (disk-fill policy); CI performs full verification.
  • impact: unit + coverage verification deferred to CI (frontend-coverage).

Behavior Changes

  • Intended behavior change: any user's or agent's profile can be viewed at /agent-world/profiles/:username, followed/unfollowed, and shared via a copy-link.
  • User-visible effect: Profiles is no longer own-wallet-only; a shareable deep-linked viewer exists for arbitrary handles.

Parity Contract

  • Legacy behavior preserved: the own-profile ProfilesSection at profiles is unchanged; the new route/component is purely additive.
  • Guard/fallback/dispatch parity checks: follow/unfollow reuse the same follows.* handlers as Directory/Feed; not-found/error/loading states mirror the existing sections.

Duplicate / Superseded PR Handling

Summary by CodeRabbit

  • New Features
    • Added a public Agent World profile viewer route for username-specific links (with normalized usernames).
    • Displays profile details (bio, skills, verified/owned identities, follower/following counts, and join date).
    • Enables follow/unfollow when supported, plus copy-link with “link copied” feedback.
    • Shows loading, not-found, and error states with localized text across supported languages.
  • Bug Fixes
    • Corrected sidebar active-section selection for nested Agent World routes.
  • Tests
    • Added regression tests covering rendering, follow/unfollow, self-profile UI, error/not-found handling, and link copying.

Agent World Profiles was hard-wired to the connected wallet, so you could
only see your own profile. The read handlers for any handle were already
plumbed and registered (graphql.profile / profiles.* / follows.*) but had
no UI caller.

Add a /agent-world/profiles/:username route and a read-only ProfileViewer
that renders graphql.profile(username) for any user or agent — header,
bio, skills, verified accounts, owned handles, follower stats — with a
follow/unfollow button (follows.follow/unfollow, initial state from the
viewer's follows.following graph) and a copy-link affordance for sharing.
Read-only; profile editing (tinyhumansai#4930) is untouched.
@M3gA-Mind
M3gA-Mind requested a review from a team July 16, 2026 13:54
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9282ec90-e0ee-4520-9367-14c6054335e1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a public Agent World profile viewer for arbitrary usernames, including profile loading states, follow/unfollow actions, statistics, copy-link support, dynamic routing, tests, and translations across supported locales.

Changes

Agent World public profile viewer

Layer / File(s) Summary
Profile loading and page states
app/src/agentworld/pages/ProfileViewer.tsx
Loads normalized usernames, resolves wallet identity, handles loading/not-found/error states, and renders profile content.
Profile card and follow interactions
app/src/agentworld/pages/ProfileViewer.tsx
Displays profile metadata and badges, loads follow statistics, supports follow/unfollow actions, hides follow controls for the owner, and copies profile links.
Route wiring, tests, and localization
app/src/agentworld/pages/AgentWorld.tsx, app/src/agentworld/pages/ProfileViewer.test.tsx, app/src/lib/i18n/*
Adds the profiles/:username route, stabilizes sidebar selection for nested routes, tests rendering and interactions, and adds ProfileViewer strings in supported locales.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ProfileViewer
  participant AgentWorldAPI
  participant FollowsAPI
  Browser->>ProfileViewer: Open /agent-world/profiles/:username
  ProfileViewer->>AgentWorldAPI: Load graphql.profile(username)
  ProfileViewer->>FollowsAPI: Load agent card and stats
  ProfileViewer-->>Browser: Render profile and actions
  Browser->>ProfileViewer: Click follow or copy link
  ProfileViewer->>FollowsAPI: Follow or unfollow profile
  ProfileViewer-->>Browser: Update state or clipboard link
Loading

Suggested labels: feature, agent

Suggested reviewers: graycyrus

Poem

I’m a rabbit hopping through profiles bright,
Sharing each handle in a link of light.
Follow, unfollow, stats in a row,
Translations bloom wherever we go.
“Squeak!” says the card—now everyone can know.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds the route, viewer, follow, and share link, but it does not implement the posts/bounties/groups rendering required by #4931. Add profile activity/groups/posts rendering for arbitrary handles, or update the linked issue if that scope is intentionally deferred.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a public profile viewer for arbitrary handles.
Out of Scope Changes check ✅ Passed The changes stay focused on the profile viewer, route, i18n, and tests, with no clearly unrelated functionality added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Comment @coderabbitai help to get the list of available commands.

@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: fcc99b2842

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/agentworld/pages/ProfileViewer.tsx Outdated
…4931-tinyplace-profiles-wallet

# Conflicts:
#	app/src/lib/i18n/ar.ts
#	app/src/lib/i18n/bn.ts
#	app/src/lib/i18n/de.ts
#	app/src/lib/i18n/en.ts
#	app/src/lib/i18n/es.ts
#	app/src/lib/i18n/fr.ts
#	app/src/lib/i18n/hi.ts
#	app/src/lib/i18n/id.ts
#	app/src/lib/i18n/it.ts
#	app/src/lib/i18n/ko.ts
#	app/src/lib/i18n/pl.ts
#	app/src/lib/i18n/pt.ts
#	app/src/lib/i18n/ru.ts
#	app/src/lib/i18n/zh-CN.ts
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Fixed the Frontend Checks failure (2 failing tests in ProfileViewer.test.tsx, both test-only bugs — the component was fine):

  • Found multiple elements with the text: @alice@alice renders in both the header and the owned-handles list, so findByText was ambiguous. Switched to findAllByText(...).length > 0.
  • writeText is not a spyuserEvent.setup() installs its own navigator.clipboard stub that shadowed the test's spy. The copy-link test now drives the click with fireEvent (not userEvent) and asserts on a locally-captured writeText spy.

Also merged current upstream/main and union-resolved the i18n conflict at the agentWorld.ledger anchor (kept both the new agentWorld.ledger.loadMore* keys from main and my agentWorld.profileViewer.* keys). pnpm typecheck + pnpm i18n:check clean (13 locales, 0 missing/extra).

@coderabbitai review

Fixed 2 test-only failures + merged main (i18n union-resolve).

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@M3gA-Mind Thanks for the details. I’ll review the updated PR, including the test assertions and the merged i18n changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added bug feature Net-new user-facing capability or product behavior. labels Jul 17, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 17

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/agentworld/pages/AgentWorld.tsx`:
- Around line 178-179: Update the active-section pathname parsing in AgentWorld
so it uses the first segment after /agent-world rather than the final segment;
ensure nested routes such as profiles/:username resolve to the profiles section
and keep the existing sidebar selection behavior for other sections.

In `@app/src/agentworld/pages/ProfileViewer.tsx`:
- Around line 40-43: Update formatDate to use the selected application locale or
the existing shared date-formatting utility instead of hard-coded 'en-US'.
Preserve the invalid-date fallback and the current year/month/day formatting
behavior.
- Around line 251-270: Extend ProfileCard to use the existing profile, activity,
group, agent-card, and follow handlers, including profile.agentCard data. Render
read-only agent-card, activity, and group-objective sections, and provide
explicit empty and failure states for activity and group data while preserving
the existing profile and follow behavior.
- Around line 140-150: Update the following-status logic around
apiClient.follows.following so it does not infer “not_following” from incomplete
or failed data. Paginate through all relationships or use a direct relationship
lookup, and set “not_following” only after a complete successful check; preserve
“unknown” on errors or incomplete lookups and provide retry/error handling
rather than enabling follow.
- Around line 103-110: Update the profile-loading error handling around the
catch block and the related error paths at the referenced locations so raw
external errors are never logged or stored in UI state. Map failures to a stable
error kind, render translated generic user-facing copy, and log only sanitized
error names or codes; preserve the special payment-required behavior without
exposing its raw message.
- Around line 82-90: The ProfileViewer effect should no longer synchronously
call setState for loading or not_found when the normalized handle changes. Key
the async profile state by the normalized handle/target, derive loading and
disabled UI state when the stored key does not match the current handle, and
ensure stale responses cannot expose the previous profile or actions; apply the
same approach to the state updates referenced around lines 134-137.
- Around line 259-276: Update ProfileViewer’s share-link flow to use the
normalized username from the requested route rather than deriving it from
profile.identities or displayName. Pass that route username into ProfileCard,
and have copyLink build the deep-link profile path from the passed normalized
handle so links resolve even when identities are null or differ from the
selected route.
- Around line 156-168: Add constant privacy-safe analytics IDs to the follow and
copy buttons, then use trackAnalyticsEvent for successful follow, unfollow, and
copy outcomes in the relevant ProfileViewer handlers, including toggle and copy
flows. Emit events only after each domain action succeeds, and include only
stable action identifiers or non-sensitive outcome data—never the target handle,
address, or other PII.
- Around line 52-66: Instrument useMyAgentId and the related wallet resolution,
relationship loading, statistics, and clipboard flows with namespaced,
grep-friendly diagnostics for external-call start, success, failure, relevant
branches, and state transitions. Replace swallowed catches with privacy-safe
error logging while excluding handles, wallet addresses, and other sensitive
values; cover the referenced sections without changing their functional
behavior.

In `@app/src/lib/i18n/fr.ts`:
- Around line 485-486: Update the French ProfileViewer localization strings
`agentWorld.profileViewer.notFoundBody` and the related metric label to use
French wording for a public username/handle instead of “identifiant,” preserving
the existing message meaning and translation style.

In `@app/src/lib/i18n/it.ts`:
- Line 482: Update the Italian translations for the following-related keys at
the shown entries, replacing imperative or verb-form wording with noun/state
labels that render correctly after a count: use “Stai già seguendo” for the
profile viewer state and “seguiti” (or the project’s established metric wording)
for the corresponding count label.

In `@app/src/lib/i18n/pl.ts`:
- Line 488: Update the translation value for the
`agentWorld.profileViewer.joined` key to a gender-neutral Polish label, choosing
`Dołączenie` or `Dołączono` based on how the value is rendered.
- Around line 476-477: Update the Polish translations for
agentWorld.profileViewer.notFoundBody and the additionally affected profile
message to replace “uchwyt” with consistent profile-handle terminology,
preferably “nazwa użytkownika” or “identyfikator,” while preserving the original
message meaning.

In `@app/src/lib/i18n/pt.ts`:
- Line 479: Update the Portuguese translation for the
`agentWorld.profileViewer.handlesOwned` key to use Agent World’s `Handle`
terminology, replacing the generic “Identificadores próprios” wording with a
phrase such as “Handles próprios” or “Handles que possui”.

In `@app/src/lib/i18n/ru.ts`:
- Line 479: Update the Russian translation for
agentWorld.profileViewer.handlesOwned to use a handle-specific label consistent
with the product terminology, such as “Собственные хэндлы,” instead of the
generic identifier wording.
- Line 482: Update the Russian translation for the
`agentWorld.profileViewer.joined` key to use a gender-neutral label, such as
“Дата присоединения”, instead of the masculine “Присоединился”.

In `@app/src/lib/i18n/zh-CN.ts`:
- Line 447: Update the agentWorld.profileViewer.agentBadge translation value
from “代理” to “智能体” to match the product’s established terminology.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 61c319ee-0e99-402e-b28a-27daf35ef668

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd56ee and 69d72a9.

📒 Files selected for processing (17)
  • app/src/agentworld/pages/AgentWorld.tsx
  • app/src/agentworld/pages/ProfileViewer.test.tsx
  • app/src/agentworld/pages/ProfileViewer.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts

Comment thread app/src/agentworld/pages/AgentWorld.tsx
Comment thread app/src/agentworld/pages/ProfileViewer.tsx Outdated
Comment thread app/src/agentworld/pages/ProfileViewer.tsx
Comment thread app/src/agentworld/pages/ProfileViewer.tsx Outdated
Comment thread app/src/agentworld/pages/ProfileViewer.tsx Outdated
Comment thread app/src/lib/i18n/pl.ts Outdated
Comment thread app/src/lib/i18n/pt.ts Outdated
Comment thread app/src/lib/i18n/ru.ts Outdated
Comment thread app/src/lib/i18n/ru.ts Outdated
Comment thread app/src/lib/i18n/zh-CN.ts Outdated

@graycyrus graycyrus 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.

@M3gA-Mind heads up — CI is red on this one (Frontend Checks / Vitest), so I'm holding off on a full review until it's green. Dug into the actual failure though, and it's not flaky — it's a real bug in the new test:

ProfileViewer.test.tsx:102 asserts screen.getByText('3') for the follower count synchronously, right after findByText('@alice') resolves. But useFollowStats only fires its follows.stats() call in a separate effect after ProfileCard first mounts — that's a second async round-trip beyond the profile-load wait the test already did. The assertion races the mock and loses, which is exactly what's failing in CI (Unable to find an element with the text: 3).

Separately, CodeRabbit already left a solid set of majors on this PR (synchronous state resets inside effects, swallowed error logging on external calls, follow-status inferred from a partial/failed relationship lookup, the share link being built from the wrong username source, and the activity/groups/agent-card handlers from #4931 not actually wired up — so this only closes part of the linked issue) — worth working through those in the same pass.

Fix the test (and CI) and I'll come back for a proper review.

Comment thread app/src/agentworld/pages/ProfileViewer.test.tsx Outdated
- AgentWorld: derive active section from the first path segment so nested
  /agent-world/profiles/:username keeps the Profiles sidebar selection.
- ProfileViewer: key profile state by handle and only write state in async
  callbacks (no synchronous setState in effects); stale responses can no
  longer surface the wrong profile.
- Follow state now comes from the agent card's signer-aware viewerIsFollowing
  (a direct, complete lookup) instead of scanning a truncated following list;
  stays 'unknown' (button hidden) when absent rather than inferring.
- Render the agent card summary read-only; build the share link from the route
  handle; format dates with the runtime locale; surface a generic translated
  error instead of the raw error string; add grep-friendly, PII-free diagnostics
  to previously-swallowed catches.
- i18n wording fixes (fr/it/pl/pt/ru handle terminology + gender-neutral joined,
  zh-CN agent term) and a new agentCard label across all locales.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Addressed CodeRabbit's review. Fixed vs declined:

Fixed

  • Active section on nested routes (AgentWorld.tsx): derive the section from the first path segment so /agent-world/profiles/:username keeps Profiles selected.
  • No synchronous setState in effects + stale-profile flash (useProfile): state is keyed by the normalized handle and only written in async callbacks; a stale response can't surface the previous profile.
  • Follow-state inference (finding: partial/failed lookup): replaced the truncated follows.following scan with the agent card's direct, signer-aware viewerIsFollowing; stays unknown (button hidden) when absent instead of inferring not_following.
  • Raw error exposure: error state renders generic translated copy; logs only a sanitized error kind, never String(err).
  • Swallowed catches: added grep-friendly, PII-free [agentworld:profileviewer] diagnostics to wallet/profile/agent-card/stats/clipboard flows.
  • Share link: built from the route handle, not identities/displayName.
  • Date locale: toLocaleDateString(undefined, …) instead of hard-coded en-US.
  • Agent card: rendered read-only from graphql.agentCard.
  • i18n wording: fr/pl/pt/ru handle terminology, it grammar (Stai già seguendo/seguiti), gender-neutral joined (pl/ru), zh-CN 智能体; new agentCard label across all locales.

Declined (with in-thread replies)

  • Analytics IDs/events: the sibling agentworld follow buttons (DirectorySection, ProfilesSection) carry none; adding them only here would be inconsistent — follow-up for a uniform pass.
  • Activity/groups sections: enrichment beyond this read-only viewer's core (view+follow+share); each needs its own fetch + states + ~6 i18n keys ×14 locales — documented follow-up. (Agent card portion of that finding IS done.)

Note for the twin PR #4996 (own-profile editing): the en-US date and raw-error-render findings also apply to its ProfilesSection (this viewer mirrored that pattern); flagging so it can be addressed there if CodeRabbit blocks it.

pnpm typecheck + pnpm i18n:check clean (13 locales, 0 missing/extra).

@coderabbitai review

Addressed review: keyed state + direct follow-flag + generic errors + share-link + i18n wording; declined analytics/activity-groups with reasons.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@M3gA-Mind Thanks for the detailed resolution notes. I’ll review the updated implementation, including the keyed loading state, direct follow-status handling, safe error rendering, and the route/i18n updates. I’ll treat the analytics and activity/groups items as documented scope decisions for this PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@graycyrus graycyrus 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.

@M3gA-Mind Nice cleanup pass — most of CodeRabbit's findings are genuinely addressed here: the sidebar active-section fix for nested routes, hiding the follow button on unknown state instead of guessing "not following", sanitized error logging (no more raw error strings), locale-aware date formatting, the share link now built from the route handle instead of identities, and the i18n wording fixes all look correct.

One thing that concerns me though: the CI failure I flagged last time (ProfileViewer.test.tsx) is still there. expect(screen.getByText('3')).toBeInTheDocument() at line ~106-108 still runs synchronously right after the profile findByText resolves, but follower stats load in their own separate effect (useFollowStats) that only kicks off once ProfileCard mounts. That's a second async round-trip the test doesn't wait for — this is the same root cause as before, untouched by this commit. Frontend Checks is still running on 05e7203 but I'd expect it to fail again on this exact assertion unless something else changed. Wrap that last assertion in findByText/waitFor instead of getByText.

Also still open (not addressed in this push): CodeRabbit's "add stable, privacy-safe analytics for the new follow/copy actions" (major) and codex's note about refreshing follower counts after a follow/unfollow toggle (currently followStats is fetched once and never re-synced after follow.toggle() succeeds, so the count visibly lags the button state).

On the linked issue — #4931 asks for posts/bounties (profiles.activity/profiles.groups) in addition to what's here; this push adds the agent-card description summary but activity/groups are still deferred. Worth calling that out explicitly as a known gap if this merges, since the issue will read as fully closed otherwise.

Holding off on a full re-review until CI is green and the test fix lands — let me know when it's ready and I'll take another pass.

…ollow toggle

- ProfileViewer.test: the follower-count assertion ran synchronously right
  after the profile load, but useFollowStats fires in a separate effect (a
  second async tick); switch getByText('3') to findByText so it no longer
  races the mock.
- ProfileViewer: the follower count was fetched once and lagged the button
  after a follow/unfollow. Apply an optimistic delta on toggle so the count
  tracks the action, and assert 3->4->3 in the toggle test.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Thanks @graycyrus — addressed both open items from your last pass.

Test race (your [major], ProfileViewer.test.tsx) — FIXED. getByText('3')await screen.findByText('3'), so the follower-count assertion waits for useFollowStats's separate effect rather than racing the mock. (CI happened to pass by timing, but it was a real latent race.)

Follower count lagging after toggle (codex's note) — FIXED. useFollowStats still fetches once, but the follow toggle now applies an optimistic followerDelta so the displayed count tracks the button (3→4 on follow, back on unfollow). Extended the toggle test to assert the count updates.

Still deferred, with prior agreement:

pnpm typecheck + pnpm i18n:check clean. Frontend Checks was green on the prior commit; this hardens the test.

@coderabbitai review

@M3gA-Mind
M3gA-Mind requested a review from graycyrus July 17, 2026 15:06
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Changed files and current PR diff summary ==='
git diff --stat origin/main...HEAD
git diff --name-only origin/main...HEAD

echo '=== ProfileViewer structural outline ==='
ast-grep outline app/src/agentworld/pages/ProfileViewer.tsx --items all --view expanded || true

echo '=== ProfileViewer test structural outline ==='
ast-grep outline app/src/agentworld/pages/ProfileViewer.test.tsx --items all --view expanded || true

echo '=== Latest relevant diffs ==='
git diff --unified=80 origin/main...HEAD -- \
  app/src/agentworld/pages/ProfileViewer.tsx \
  app/src/agentworld/pages/ProfileViewer.test.tsx

echo '=== Follow stats / optimistic delta references with context ==='
rg -n -C 5 --glob '*.ts' --glob '*.tsx' \
  '(useFollowStats|followerDelta|setFollowerDelta|follows\.stats|follows\.(follow|unfollow)|viewerIsFollowing)' \
  app/src

echo '=== Agent World API follow-related declarations ==='
rg -n -C 4 --glob '*.ts' --glob '*.tsx' \
  '(interface .*Follow|class .*Follow|following\s*[:(]|stats\s*[:(]|follow\s*[:(]|unfollow\s*[:(])' \
  app/src/agentworld app/src/lib

Length of output: 87961


@M3gA-Mind Confirmed the follower-stat assertion now waits for the separate async stats load, and the successful follow/unfollow paths update the displayed count 3 → 4 → 3 while preventing negative totals. I found no new concern in these latest changes. Triggering the requested review.

🐇 ✅

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@oxoxDev oxoxDev 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.

Solid read-only viewer. No XSS (no dangerouslySetInnerHTML; all untrusted profile data renders as auto-escaped JSX, avatarUrl only into <img src>), null-guards + list keys throughout, and all view states handled. i18n parity is complete (18 keys × 14 locales) — not the CI cause.

Blocking:

  • CI red is this PR's own racy test at ProfileViewer.test.tsx:102: getByText('3') for the follower count runs before useFollowStats resolves (separate microtask) → use await screen.findByText('3'). The other 379 tests pass; not i18n, not pre-existing.
  • Follow-state inference at ProfileViewer.tsx:140-150: follows.following(myAgentId, {limit:500}) treats "not in first 500" as not_following, and .catch(() => setState('not_following')) collapses any fetch error to not-following. A viewer following >500 accounts (or hitting a transient error) sees a Follow button for someone they already follow, and clicking issues a redundant follow. Paginate / use a direct relationship lookup, and keep 'unknown' (disabled) on error. (= CodeRabbit Major.)

Non-blocking:

  • Raw String(err) is rendered and logged in the error block — map to a stable kind + translated copy, log the sanitized code only. (= CodeRabbit.)
  • copyLink builds the share URL from the profile-derived handle, not the :username route param — can diverge if identities is null. (= CodeRabbit.)
  • Follower count stays stale after a follow toggle (single fetch). (= Codex P2.)
  • Sidebar active-state keys on the last path segment, so /agent-world/profiles/alice deselects profiles; derive from the first segment. (= CodeRabbit.)
  • Nits: formatDate hardcodes en-US; swallowed .catch(()=>{}) with no diagnostics; <img> has no onError fallback.

…o load

Add an onError handler to the ProfileViewer avatar image so a broken/blocked
avatarUrl degrades to the initials monogram instead of a broken image.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Thanks @oxoxDev. Both blocking items were already fixed before this review landed (it reviewed a stale commit); details + the one new nit below.

Blocking — racy follower-count test: ALREADY FIXED (commit 8d126dcb5). ProfileViewer.test.tsx now uses await screen.findByText('3') (all three occurrences), so it waits for useFollowStats's separate effect instead of racing it.

Blocking — follow-state inferred from a partial/failed lookup: ALREADY FIXED (prior commit). The follows.following(myAgentId, {limit:500}) scan is gone — follow state now comes from the agent card's direct, signer-aware viewerIsFollowing (readViewerFollows/viewerFollowsHint, ProfileViewer.tsx:63,190). When it's absent (or the card fetch fails) the state stays 'unknown' and the button is hidden, never defaulting to not-following. No pagination, no error-collapse.

Non-blocking — already addressed last pass: raw String(err) → generic translated error + errorKind() sanitized logging; copyLink built from the :username route handle (routeHandle); follower count kept in sync after toggle (followerDelta); sidebar active-state derived from the first path segment (AgentWorld.tsx:120); formatDate uses the runtime locale; swallowed catches now log a sanitized kind.

Non-blocking — <img> onError fallback: FIXED (this push). The avatar now falls back to the initials monogram when avatarUrl fails to load.

Still deferred with prior agreement: analytics on follow/copy (cross-surface pass; CodeRabbit accepted) and profiles.activity/profiles.groups (documented #4931 follow-up; agent-card summary is wired).

pnpm typecheck clean; Frontend Checks green on the prior head. Please re-review the current head.

@coderabbitai review

@M3gA-Mind
M3gA-Mind requested a review from oxoxDev July 17, 2026 16:31
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@M3gA-Mind Thanks for the detailed clarification. I’ll review the current PR head, including the async test synchronization, direct signer-aware follow state, and avatar fallback.

🐇🔍

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. and removed bug labels Jul 17, 2026
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

@oxoxDev — every item is resolved in the current head (0b3155ec7); both blocking items were already fixed before your review landed.

[major] Racy follower-count test → FIXED (8d126dcb5). ProfileViewer.test.tsx uses await screen.findByText('3') (all 3 occurrences) so it waits for useFollowStats's separate effect. This also clears the CI red.

[major] Follow-state inference (partial/failed lookup) → FIXED (prior commit). The follows.following(myAgentId, {limit:500}) scan is gone. Follow state now comes from the agent card's direct, signer-aware viewerIsFollowing (readViewerFollows / viewerFollowsHint, ProfileViewer.tsx:63,190). When it's absent or the card fetch fails, state stays 'unknown' and the button is hidden — never defaulting to not-following, so no redundant follow and no >500 blind spot.

Non-blocking — all addressed:

  • Raw String(err) → generic translated error + errorKind() sanitized logging.
  • copyLink built from the :username route handle (routeHandle, :331), not the profile-derived handle.
  • Stale follower count after toggle → optimistic followerDelta keeps it in sync (:301-304); test asserts 3→4→3.
  • Sidebar active-state → derived from the first path segment (AgentWorld.tsx:120, indexOf('agent-world')), so /agent-world/profiles/:username keeps Profiles selected.
  • formatDate en-US hardcode → toLocaleDateString(undefined, …) (:54).
  • Swallowed catches → now log a sanitized errorKind.
  • <img> onError fallback → avatar degrades to the initials monogram (:352).

Deferred with prior agreement (CodeRabbit acknowledged): analytics on follow/copy (cross-surface pass) and profiles.activity/profiles.groups (documented #4931 follow-up; agent-card summary is wired).

pnpm typecheck clean; Frontend Checks was green on the prior head. Please re-review the current head.

@senamakel
senamakel merged commit f1de40c into tinyhumansai:main Jul 18, 2026
23 of 31 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Tiny Place] Agent World Profiles is own-wallet display-only — no viewer for other users' or agent profiles

4 participants