Skip to content

fix(payout): show payout on content pages, comments and waves (match web)#3282

Merged
feruzm merged 2 commits into
developmentfrom
fix/content-payout-display
Jun 21, 2026
Merged

fix(payout): show payout on content pages, comments and waves (match web)#3282
feruzm merged 2 commits into
developmentfrom
fix/content-payout-display

Conversation

@feruzm

@feruzm feruzm commented Jun 21, 2026

Copy link
Copy Markdown
Member

Problem

Pending/total payout was not showing on post (content) detail pages in the app. Reported via a device screenshot.

Root cause: the waves card fix in 763b73fc37 added a _hasPayoutToShow gate to the shared UpvoteButton, which renders the payout chip only when the computed total_payout > 0 (or payout is declined). But UpvoteButton is reused in four places, not just waves cards:

  • postCard/children/postCardActionsPanel.tsx (feed cards)
  • comment/view/commentView.tsx (comments and waves)
  • postView/view/postDisplayView.tsx (post/content detail action bar)

So on any entry whose computed payout was 0, the chip disappeared entirely instead of showing $0.000. This diverges from the web client, where entry-payout always renders the amount (posts, comments, waves alike: entry-payout/index.tsx, discussion-item.tsx, wave-actions.tsx with showPayout defaulting to true).

Note: real wave/comment payouts were never missing from the data. bridge.get_discussion returns pending_payout_value as a proper "X.XXX HBD" string (verified live waves at 0.001 to 0.034 HBD), and parsePost already parses them correctly. The gate only blanked the chip when the value was exactly 0.000.

Changes

  1. Drop the _hasPayoutToShow gate in UpvoteButton so the payout renders consistently for posts, comments and waves, including $0.000, matching web. NaN/undefined is still coerced to 0 so FormattedCurrency never gets undefined.

  2. Numeric payout fallback in parsePost and parseComment: some entries (search-API / RPC shapes) expose a numeric payout and lack the asset-string fields, so parseAsset would yield 0 and hide a real payout. Fall back to the numeric payout when the asset-string fields are absent (detected by missing max_accepted_payout), mirroring web's entry-payout isSearchResult branch.

Tests

Added regression tests to postParser.test.ts for both parsePost and parseComment:

  • numeric-payout fallback when asset-string fields are absent
  • asset-string fields preferred when both present
  • genuinely zero-payout entry stays at 0

postParser.test.ts: 58 passed. Lint: 0 errors on changed files.

Follow-up (not in this PR)

The original waves report also had a stale vote count (3 vs real 29). That is a wave-card data-freshness issue (query staleTime/refetch), separate from display, and the right fix for the live count/payout staleness. Tracking separately.

Summary by CodeRabbit

  • Bug Fixes

    • Updated the payout chip behavior so it reliably renders whenever payout display is enabled, including $0.000 cases that were previously suppressed.
    • Improved payout total parsing to correctly handle “numeric-only” payout inputs, falling back to numeric payout/total payout when asset-based fields are missing (including cross-post scenarios).
  • Tests

    • Added/expanded coverage for payout computation for both posts and comments across multiple input shapes and edge cases (including true zero payouts).

…llback

The shared UpvoteButton hid the payout chip whenever computed total_payout
was 0. That also blanked the value on post/content detail pages and on
waves/comments, diverging from the web client where entry-payout always
renders the amount (including $0.000).

- Drop the _hasPayoutToShow gate so posts, comments and waves render the
  payout consistently, matching web.
- parsePost/parseComment: fall back to a numeric `payout` when the
  asset-string fields are absent (search-API / RPC shapes), mirroring web's
  entry-payout, so a real payout is not shown as 0.

Adds regression tests for both fallbacks.

@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: 3b7df3f0fd

ℹ️ 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".

Comment thread src/utils/postParser.tsx Outdated
Comment on lines +110 to +113
const _isNumericPayoutOnly = typeof post.payout === 'number' && !post.max_accepted_payout;

const totalPayout = _isNumericPayoutOnly
? post.payout

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 Preserve numeric search-result payouts

When a post is opened from search, postsResultsContainer primes the post query cache with the search item, and the SDK SearchResult shape exposes the numeric amount as total_payout, not payout. In that cached-entry path the asset-string fields are absent, but this check stays false and the following parseAsset(...) sum overwrites the existing nonzero total_payout with 0, so the content page can still show $0.000 until a fresh entry response replaces the cache. Include the existing numeric total_payout in this fallback before recomputing it from missing asset strings.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jun 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a regression where the payout chip was hidden on post detail pages, comment views, and waves whenever total_payout computed to exactly 0. The fix removes the _hasPayoutToShow gate from UpvoteButton so the chip always renders (including $0.000), matching the web client's behaviour, and adds a numeric-payout fallback in parsePost/parseComment for search-API and RPC-shaped entries that carry payout as a numeric field instead of the normal HBD asset strings.

  • upvoteButton.tsx: removes _hasPayoutToShow; isShowPayoutValue remains the only gate, and _payoutValue is still coerced to 0 to avoid NaN in FormattedCurrency.
  • postParser.tsx: adds _isNumericPayoutOnly branch in both parsePost and parseComment, guarded by !max_accepted_payout to avoid misidentifying normal Hive responses; cross-post paths now also forward orig.payout / orig.total_payout so the fallback operates on the original entry's data.
  • postParser.test.ts: regression tests added for both parsers covering the numeric fallback, asset-string preference, genuine-zero, and cross-post shapes.

Confidence Score: 5/5

Safe to merge — the changes are narrowly scoped, well-tested, and the removed gate was causing real display regressions.

The UpvoteButton change is a one-line removal of an over-eager display guard with no downstream side effects. The numeric-payout fallback in parsePost/parseComment is gated on !max_accepted_payout, which is a reliable discriminator for the search-API/RPC-only shapes. Nine new regression tests cover both parsers across all relevant shapes. No existing tests were removed or weakened.

No files require special attention.

Important Files Changed

Filename Overview
src/components/postCard/children/upvoteButton.tsx Removes _hasPayoutToShow gate; payout chip now renders unconditionally when isShowPayoutValue is true, including $0.000 entries. NaN coercion via Number()
src/utils/postParser.tsx Adds numeric-payout fallback (_isNumericPayoutOnly) in both parsePost and parseComment; cross-post block now forwards orig.payout/total_payout. Logic is correct and the max_accepted_payout guard prevents misfires on normal Hive API shapes.
src/utils/postParser.test.ts Adds 9 new regression tests for parsePost and parseComment covering numeric fallback, asset-string preference, genuine-zero, and cross-post shapes. Coverage is comprehensive and symmetric across both parsers.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Raw API / RPC response] --> B{has max_accepted_payout?}
    B -- Yes\nnormal Hive post/comment --> C[Asset-string sum\npending + author + curator]
    B -- No\nsearch-API / numeric-only shape --> D{has numeric payout?}
    D -- post.payout is number --> E[Use post.payout]
    D -- post.total_payout is number --> F[Use post.total_payout]
    D -- neither --> G[parseAsset sum to 0]
    C --> H[post.total_payout]
    E --> H
    F --> H
    G --> H
    H --> I[UpvoteButton]
    I --> J{isShowPayoutValue?}
    J -- false --> K[chip hidden]
    J -- true --> L[FormattedCurrency, Number total_payout OR 0, always renders incl dollar 0.000]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Raw API / RPC response] --> B{has max_accepted_payout?}
    B -- Yes\nnormal Hive post/comment --> C[Asset-string sum\npending + author + curator]
    B -- No\nsearch-API / numeric-only shape --> D{has numeric payout?}
    D -- post.payout is number --> E[Use post.payout]
    D -- post.total_payout is number --> F[Use post.total_payout]
    D -- neither --> G[parseAsset sum to 0]
    C --> H[post.total_payout]
    E --> H
    F --> H
    G --> H
    H --> I[UpvoteButton]
    I --> J{isShowPayoutValue?}
    J -- false --> K[chip hidden]
    J -- true --> L[FormattedCurrency, Number total_payout OR 0, always renders incl dollar 0.000]
Loading

Reviews (2): Last reviewed commit: "fix(payout): handle search-API total_pay..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88991cb8-bbf0-4af4-b3fc-4c6d55232ab3

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7df3f and c47e640.

📒 Files selected for processing (2)
  • src/utils/postParser.test.ts
  • src/utils/postParser.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/postParser.tsx

📝 Walkthrough

Walkthrough

parsePost and parseComment in postParser.tsx now fall back to the raw numeric payout field for total_payout when asset-string payout fields are absent. Cross-post swapping preserves numeric payout fields for fallback handling. In UpvoteButton, the _hasPayoutToShow gate is removed so the payout chip renders whenever isShowPayoutValue is true, including for $0.000. Tests cover the parser fallback across multiple entry shapes.

Changes

Payout Numeric Fallback and Zero Display

Layer / File(s) Summary
Numeric payout fallback in parsePost and parseComment
src/utils/postParser.tsx, src/utils/postParser.test.ts
Both parser functions conditionally set totalPayout from the numeric payout or total_payout field when max_accepted_payout is absent, falling back to the existing asset-string sum otherwise. Cross-post field swapping now preserves numeric payout and total_payout to enable fallback handling on swapped data. Four new test suites cover the fallback, asset-string precedence, search-API shape, cross-post/original entry fallback, and zero-payout preservation.
UpvoteButton always renders payout chip
src/components/postCard/children/upvoteButton.tsx
Removed _hasPayoutToShow and its positive-payout/declined-payout guard; the chip now renders solely on isShowPayoutValue, with payout coerced to 0 when absent or NaN.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ecency/ecency-mobile#3270: Directly inverts the behavior changed here — that PR added the _hasPayoutToShow gating in UpvoteButton to suppress zero-payout chips, which this PR removes.
  • ecency/ecency-mobile#3180: Both PRs modify parsePost and parseComment payout handling to support different entry shapes (numeric-only vs. asset-string payout fields) based on max_accepted_payout presence.

Poem

🐇 Hoppity-hop through the payout field,
No more hiding what zero might yield!
A number, a string, or naught at all—
The chip shows up for each and all.
$0.000 stands proud on the screen,
The cleanest payout display I've seen! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing payout display on content pages, comments, and waves to match web behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/content-payout-display

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Comment thread src/utils/postParser.test.ts
Comment thread src/utils/postParser.tsx Outdated
// asset-string payout fields, so parseAsset would yield 0 and hide a real payout.
// Mirror the web client's entry-payout: fall back to the numeric payout when the
// asset-string fields are absent (detected by the missing max_accepted_payout).
const _isNumericPayoutOnly = typeof post.payout === 'number' && !post.max_accepted_payout;

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 Detection heuristic breaks when max_accepted_payout is absent but asset-string fields are valid

The condition !post.max_accepted_payout is used as a proxy for "search-API / RPC-shaped entry without asset-string fields." If any API path returns a post that has real pending_payout_value / author_payout_value / curator_payout_value strings but omits max_accepted_payout, and the object also carries a numeric payout field, the fallback would silently use post.payout (which could differ from the true asset-string sum). The same logic exists in parseComment at line 286. This is a reasonable heuristic given the Hive API always includes max_accepted_payout for normal posts, but it's worth a brief comment noting the assumption so future callers don't inadvertently create this shape.

Fix in Claude Code

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/postParser.tsx (1)

41-74: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cross-post numeric payout fallback misses original_entry.payout.

Line 41-74 normalizes payout fields from post.original_entry, but it never copies orig.payout.
Then at Line 110, the new fallback checks post.payout, not orig.payout, so cross-posts with numeric-only payouts can still end up with post.total_payout = 0.

💡 Proposed fix
   if (post.original_entry) {
     const orig = post.original_entry;
@@
     post.pending_payout_value = orig.pending_payout_value;
     post.author_payout_value = orig.author_payout_value;
     post.curator_payout_value = orig.curator_payout_value;
     post.max_accepted_payout = orig.max_accepted_payout;
+    post.payout = orig.payout;
     post.active_votes = orig.active_votes;
     post.children = orig.children;
     post.stats = orig.stats;
   }

Also applies to: 106-116

🤖 Prompt for 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.

In `@src/utils/postParser.tsx` around lines 41 - 74, In the cross-post
normalization block where post.original_entry fields are being copied to post
(including pending_payout_value, author_payout_value, curator_payout_value,
max_accepted_payout, etc.), add a line to also copy orig.payout to post.payout.
This ensures that when the later fallback logic checks post.payout to calculate
total_payout, it will have the correct numeric payout value from the original
entry instead of remaining undefined or zero for cross-posts.
🧹 Nitpick comments (1)
src/utils/postParser.test.ts (1)

159-191: ⚡ Quick win

Add a regression test for cross-post numeric-only payout.

Current new cases validate direct post/comment shapes, but they don’t cover parsePost’s original_entry remap path. A test where post.original_entry has { payout: number, max_accepted_payout: undefined } would catch the payout-loss path before it regresses again.

Also applies to: 411-421

🤖 Prompt for 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.

In `@src/utils/postParser.test.ts` around lines 159 - 191, The current test cases
validate direct post and comment payout handling but do not cover the parsePost
function's original_entry remap path, which can be a source of payout-loss
regressions. Add a new test case that creates a post where the original_entry
object contains a numeric payout value with max_accepted_payout set to
undefined, then verify that parsePost correctly preserves and processes that
payout value. This regression test should be similar in structure to the
existing test cases (using makePost to set up the scenario and checking
result.total_payout) but specifically exercises the original_entry remap code
path.
🤖 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.

Outside diff comments:
In `@src/utils/postParser.tsx`:
- Around line 41-74: In the cross-post normalization block where
post.original_entry fields are being copied to post (including
pending_payout_value, author_payout_value, curator_payout_value,
max_accepted_payout, etc.), add a line to also copy orig.payout to post.payout.
This ensures that when the later fallback logic checks post.payout to calculate
total_payout, it will have the correct numeric payout value from the original
entry instead of remaining undefined or zero for cross-posts.

---

Nitpick comments:
In `@src/utils/postParser.test.ts`:
- Around line 159-191: The current test cases validate direct post and comment
payout handling but do not cover the parsePost function's original_entry remap
path, which can be a source of payout-loss regressions. Add a new test case that
creates a post where the original_entry object contains a numeric payout value
with max_accepted_payout set to undefined, then verify that parsePost correctly
preserves and processes that payout value. This regression test should be
similar in structure to the existing test cases (using makePost to set up the
scenario and checking result.total_payout) but specifically exercises the
original_entry remap code path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb23760c-66b3-4b05-8fb3-f89d9c54b61f

📥 Commits

Reviewing files that changed from the base of the PR and between b6421bd and 3b7df3f.

📒 Files selected for processing (3)
  • src/components/postCard/children/upvoteButton.tsx
  • src/utils/postParser.test.ts
  • src/utils/postParser.tsx

Address PR review feedback:
- parsePost/parseComment fall back to numeric `total_payout` as well as
  `payout` when the asset-string fields are absent. The SDK SearchResult shape
  exposes the amount as `total_payout` (not `payout`), so the cached
  search -> detail path no longer overwrites the real value with 0.
- Cross-post swap also copies orig.payout / orig.total_payout so the fallback
  uses the original entry's value.
- Add symmetrical parseComment tests (asset-string preference, genuine zero)
  plus search-shape and cross-post regression tests for parsePost.
@feruzm feruzm merged commit e1c27f5 into development Jun 21, 2026
7 checks passed
@feruzm feruzm deleted the fix/content-payout-display branch June 21, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant