Skip to content

quests: refresh after the backend credits an action, and warn on short replies - #1394

Merged
feruzm merged 4 commits into
developfrom
bugfix/quests-refresh-and-short-reply-hint
Aug 11, 2026
Merged

quests: refresh after the backend credits an action, and warn on short replies#1394
feruzm merged 4 commits into
developfrom
bugfix/quests-refresh-and-short-reply-hint

Conversation

@feruzm

@feruzm feruzm commented Aug 11, 2026

Copy link
Copy Markdown
Member

Closes #1391. Closes #1392.

Both are the client half of the same complaint: people do something, the quest counter does not move, and nothing explains why.

Refresh timing (#1391)

scheduleQuestsRefresh invalidated the quests query 4 seconds after a points-earning action. A chain action is not credited that fast: it has to be verified against the chain and processed first, which lands a little over a minute later. So the refetch read the pre-action numbers and then marked them fresh for the query's staleTime, which actively prevented the real update from being picked up. The navbar streak pill and the /perks tiles then sat on stale values until something remounted them much later.

The delay now matches the backend, extracted as a named constant with the reasoning next to it. The coalescing behaviour and the deliberate exclusion of the vote path are unchanged.

Silent rejection (#1392)

The points backend drops a comment whose body is at or under a minimum length once URLs are stripped out, so "Thank you", an emoji, or an image-only reply earns nothing and never counts toward the daily comment quest. Deliberate anti-spam rule, completely invisible in the product, and the single biggest source of these reports.

  • QUEST_MIN_CONTENT_LENGTH, measureQuestContentLength and earnsQuestContentCredit go in the shared quest catalog, mirroring the backend constant the same way STREAK_FREEZE_PRICE already does. Both clients need the rule and it should not be typed twice.
  • The reply composer shows a quiet inline hint when the signed-in user's draft is below the threshold. It does not block submitting, and it stays quiet for logged-out users, on an empty composer, and in edit mode (an edit never earns either, the original already claimed the reward).

Leaderboard badge label

The quests-done tooltip now says the day is UTC. The badge is wiped at 00:00 UTC while the board itself ranks a rolling 24 hours, so people lose it mid-afternoon in Asia with nothing explaining it. See ecency/esync-py#26: keeping the boundary and naming it is the honest fix, because the user's own quest card resets on the same boundary and making the badge roll instead would have desynced the two.

Needs the patch:sdk label

apps/web resolves @ecency/sdk through the committed dist/*.d.ts, so typecheck will be red until the label is applied and the auto-changeset bot pushes the version bump plus the rebuilt dist to this branch. Not adding the label here, that is a manual call.

Verified locally with a throwaway pnpm --filter @ecency/sdk build (discarded before committing, no dist in this diff):

apps/web typecheck$ tsc --noEmit   -> clean

Verification

packages/sdk   Test Files 49 passed (49)    Tests 694 passed (694)
apps/web       Test Files 267 passed (267)  Tests 2564 passed (2564)
pnpm lint      clean (pre-existing a11y warnings only)

New tests:

  • the SDK content rule against the exact backend cases: link-only bodies measure as empty, "Thank you" and emoji-only are rejected, the comparison is strictly greater than the minimum, a normal reply containing a link still passes
  • scheduleQuestsRefresh does not fire at 10s, does fire after the real delay, coalesces a burst into one request, strips a leading @, and no-ops without a username
  • the composer shows the hint for a short reply and for a link-only reply, drops it once the body is long enough, and stays quiet on an empty box

Two mocks needed the new exports. The global @ecency/sdk mock in setup-any-spec.ts gained the real pure implementations plus a minimal QueryKeys (it had none, so anything importing it already failed), and comment.spec.tsx has its own factory which vitest 4 does not merge, so it needed them too.

Follow-up

The app side is tracked separately in ecency/vision-mobile#3471 and #3473, and #3473 waits on this SDK release.

Summary by CodeRabbit

  • New Features

    • Added quest-content validation that excludes links when determining reply length.
    • Replies must exceed 25 non-link characters to earn quest credit, including emoji characters.
    • Added a localized composer hint for authenticated users whose replies are too short.
    • Clarified that daily quest leaderboard times are shown in UTC.
  • Bug Fixes

    • Quest status refreshes now occur after a 70-second delay and handle rapid updates more reliably.

…t replies

Two client-side halves of the same complaint: people do something, the quest
counter does not move, and nothing explains why.

scheduleQuestsRefresh invalidated the quests query 4 seconds after a
points-earning action. A chain action is not credited that fast: it has to be
verified against the chain and processed first, which lands a little over a
minute later. So the refetch read the pre-action numbers and then marked them
fresh for the query's staleTime, which actively prevented the real update from
being picked up. The delay now matches the backend.

The other half is silent rejection. The points backend drops a comment whose
body is at or under a minimum length once URLs are stripped, so 'Thank you', an
emoji, or an image-only reply earns nothing and never counts toward the daily
comment quest. That is a deliberate anti-spam rule that was invisible in the
product. The minimum and the URL-stripping measurement now live in the shared
quest catalog, mirroring the backend constant the same way STREAK_FREEZE_PRICE
does, and the reply composer says so before the user submits. It does not block
submitting, and it stays quiet for logged-out users and in edit mode.

The leaderboard badge tooltip now says the day is UTC. The badge is wiped at
00:00 UTC while the board itself ranks a rolling 24 hours, so people lose it
mid-afternoon in Asia with no explanation. Keeping the boundary and naming it
is the honest fix: the user's own quest card resets on the same boundary, so
making the badge roll instead would have desynced the two.

Refs #1391, #1392, ecency/esync-py#26
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b231d2c-ab79-4438-96b0-984f3a699b5c

📥 Commits

Reviewing files that changed from the base of the PR and between e27bac3 and fbbd296.

⛔ Files ignored due to path filters (7)
  • packages/sdk/dist/browser/index.d.ts is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.cjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.mjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (11)
  • apps/web/src/features/shared/comment/index.tsx
  • apps/web/src/specs/features/shared/comment.spec.tsx
  • apps/web/src/specs/setup-any-spec.ts
  • apps/web/src/specs/utils/refresh-quests.spec.ts
  • apps/web/src/utils/refresh-quests.ts
  • packages/sdk/CHANGELOG.md
  • packages/sdk/package.json
  • packages/sdk/src/modules/quests/catalog.spec.ts
  • packages/sdk/src/modules/quests/catalog.ts
  • packages/wallets/CHANGELOG.md
  • packages/wallets/package.json

📝 Walkthrough

Walkthrough

The PR adds shared quest-content eligibility helpers, short-reply feedback in the comment composer, tests for both behaviors, and a 70-second quest refresh delay. It also clarifies link exclusion and UTC quest completion messaging.

Changes

Quest behavior

Layer / File(s) Summary
Quest content eligibility
packages/sdk/src/modules/quests/catalog.ts, packages/sdk/src/modules/quests/catalog.spec.ts
The SDK removes HTTP(S) URLs before measuring content. Quest credit requires content longer than 25 characters.
Composer eligibility feedback
apps/web/src/features/shared/comment/index.tsx, apps/web/src/features/i18n/locales/en-US.json, apps/web/src/specs/features/shared/comment.spec.tsx, apps/web/src/specs/setup-any-spec.ts
The composer shows a localized hint for authenticated users writing non-empty, non-edit replies that do not qualify for quest credit. Tests and mocks cover the warning conditions.
Quest refresh scheduling
apps/web/src/utils/refresh-quests.ts, apps/web/src/specs/utils/refresh-quests.spec.ts, apps/web/src/features/i18n/locales/en-US.json
Quest invalidation uses a 70-second debounce delay. Tests cover timing and coalescing. The daily-quest message identifies UTC dates.
Quest package release metadata
packages/sdk/package.json, packages/sdk/CHANGELOG.md, packages/wallets/package.json, packages/wallets/CHANGELOG.md
The SDK and wallets packages receive patch version updates and release notes for the quest changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Poem

I’m a rabbit checking replies in the queue,
Short links alone earn no points due.
Twenty-five is not enough to pass,
Longer words can earn credit at last.
Quests refresh when the chain work is through.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 two primary changes: delayed quest refreshes and warnings for short replies.
Linked Issues check ✅ Passed The changes satisfy refresh timing and coalescing requirements [#1391] and add the shared threshold, URL stripping, and scoped composer hint [#1392].
Out of Scope Changes check ✅ Passed The localization, SDK helpers, composer changes, refresh logic, and tests support the linked issue objectives.
✨ 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 bugfix/quests-refresh-and-short-reply-hint

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

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

🧹 Nitpick comments (1)
apps/web/src/specs/utils/refresh-quests.spec.ts (1)

42-49: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the complete quest cache key.

The current assertions only check key membership. An incorrect key such as ["quests", "other", "alice"] could still pass.

Proposed fix
+import { QueryKeys } from "`@ecency/sdk`";
+
-    const [{ queryKey }] = invalidateQueries.mock.calls[0];
-    expect(queryKey).toContain("alice");
-    expect(queryKey).not.toContain("`@alice`");
+    expect(invalidateQueries).toHaveBeenCalledWith({
+      queryKey: QueryKeys.quests.status("alice")
+    });

As per coding guidelines, use QueryKeys from @ecency/sdk as the single source of truth for cache keys.

🤖 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 `@apps/web/src/specs/utils/refresh-quests.spec.ts` around lines 42 - 49, Update
the test for scheduleQuestsRefresh to assert the complete queryKey against the
expected quest cache key generated via QueryKeys from `@ecency/sdk`, rather than
checking only containment. Preserve verification that the leading @ is removed
from the account value and ensure no unrelated key segments are accepted.

Source: Coding guidelines

🤖 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 `@apps/web/src/features/shared/comment/index.tsx`:
- Around line 336-340: Add role="status" to the comment-short-reply-hint element
in apps/web/src/features/shared/comment/index.tsx (lines 336-340), then update
the related tests in apps/web/src/specs/features/shared/comment.spec.tsx (lines
139-168) to use renderWithQueryClient, screen.getByRole("status"),
screen.queryByRole("status"), and screen.getByRole("textbox") instead of CSS
selectors and getByTestId.

In `@apps/web/src/specs/utils/refresh-quests.spec.ts`:
- Around line 18-29: Strengthen the test around scheduleQuestsRefresh to assert
the refresh does not occur throughout the required 60–75 second window,
including at 60 seconds, and only occurs once the intended delay has elapsed.
Replace the current 10-second-only assertion with boundary checks that reject
early timers while preserving the single invalidateQueries call.
- Around line 4-5: Replace the untyped queryClient test double in refresh-quests
tests with a typed QueryClient approach: instantiate a real QueryClient and spy
on invalidateQueries, or type the dependency as Pick<QueryClient,
"invalidateQueries">. Preserve the existing invalidateQueries mock behavior
while removing the as any cast.

In `@packages/sdk/src/modules/quests/catalog.ts`:
- Around line 60-61: Update measureQuestContentLength in
packages/sdk/src/modules/quests/catalog.ts:60-61 to apply Array.from(...).length
after removing URLs, so length counts Unicode code points. Add a boundary test
with 13 astral characters in
packages/sdk/src/modules/quests/catalog.spec.ts:39-45. Update the SDK mocks in
apps/web/src/specs/features/shared/comment.spec.tsx:67-71 and
apps/web/src/specs/setup-any-spec.ts:104-109 to match the revised behavior.

---

Nitpick comments:
In `@apps/web/src/specs/utils/refresh-quests.spec.ts`:
- Around line 42-49: Update the test for scheduleQuestsRefresh to assert the
complete queryKey against the expected quest cache key generated via QueryKeys
from `@ecency/sdk`, rather than checking only containment. Preserve verification
that the leading @ is removed from the account value and ensure no unrelated key
segments are accepted.
🪄 Autofix

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 Plus

Run ID: 5593e624-eff3-4fc5-80a2-74fcadf69c07

📥 Commits

Reviewing files that changed from the base of the PR and between 6dc71f1 and e27bac3.

📒 Files selected for processing (8)
  • apps/web/src/features/i18n/locales/en-US.json
  • apps/web/src/features/shared/comment/index.tsx
  • apps/web/src/specs/features/shared/comment.spec.tsx
  • apps/web/src/specs/setup-any-spec.ts
  • apps/web/src/specs/utils/refresh-quests.spec.ts
  • apps/web/src/utils/refresh-quests.ts
  • packages/sdk/src/modules/quests/catalog.spec.ts
  • packages/sdk/src/modules/quests/catalog.ts

Comment thread apps/web/src/features/shared/comment/index.tsx
Comment thread apps/web/src/specs/utils/refresh-quests.spec.ts Outdated
Comment thread apps/web/src/specs/utils/refresh-quests.spec.ts
Comment thread packages/sdk/src/modules/quests/catalog.ts Outdated
feruzm added 2 commits August 11, 2026 11:12
A single shared timer let a second account cancel the first one's only scheduled
invalidation. It was survivable at the old 4 second debounce; at the delay this
PR moves it to, the window is wide enough for an account switch to land inside
it, so pending refreshes are now keyed by username.

Same finding as ecency/vision-mobile#3478, raised there by review.
The content length check was measuring UTF-16 code units while the backend
measures with Python len() on a str. A reply of 13 astral emoji scored 13 there
and 26 here, so the composer would have stayed silent and promised points that
were then refused: the exact confusion the hint exists to end. Measuring code
points makes the two agree.

The hint is a live region now (role=status), so it is announced rather than only
seen, and its tests query by role instead of a class name.

Two spec fixes: the refresh timing test allowed anything under 10 seconds to
pass, which a regression to a 15 second delay would have survived, so the lower
bound is now just under a minute. And the QueryClient double was cast through
any, which would outlive a signature change, so it is a real client with a spy.
@feruzm feruzm added the patch Bug fixes and patches (1.0.0 → 1.0.1) label Aug 11, 2026
@feruzm
feruzm merged commit b996332 into develop Aug 11, 2026
3 of 4 checks passed
@feruzm
feruzm deleted the bugfix/quests-refresh-and-short-reply-hint branch August 11, 2026 11:27
feruzm added a commit to ecency/vision-mobile that referenced this pull request Aug 11, 2026
Picks up the shared quest content rule (QUEST_MIN_CONTENT_LENGTH plus the
URL-stripping, code-point-counting measurement) that the website released in
ecency/vision-web#1394, which the composer hint in #3473 needs.

Nothing in this PR uses those exports yet; the bump lands here so the dependency
is in place ahead of it. The two versions in between are patches: 2.3.77 moved
useAccountUpdateRecovery to the AuthContextV2 adapter, which this app does not
call, and 2.3.78 was a documentation guard.

Verified against the published package rather than the older copy in the shared
install: all 143 named imports this app takes from @ecency/sdk still resolve,
and typecheck, lint and the unit suite pass against 2.3.79.
feruzm added a commit that referenced this pull request Aug 11, 2026
Resolves the version collision introduced by the label-triggered changeset run:
both this branch and #1394 on develop bumped @ecency/sdk to 2.3.79, and 2.3.79
is already published to npm, so merging as-is would have failed `publish:sdk`.

- packages/sdk/CHANGELOG.md: keep develop's 2.3.79 entry, move this PR's entry to
  a new 2.3.80 section.
- @ecency/sdk 2.3.79 -> 2.3.80, @ecency/wallets 5.0.79 -> 5.0.80 (dependent bump),
  with the matching wallets CHANGELOG entry.
- packages/sdk/dist: regenerated from the merged source rather than hand-merged.
  Verified the bundle carries both sides — this branch's account-history changes
  and develop's quests catalog.

@ecency/sdk test 706 passed, @ecency/web test 2590 passed, apps/web tsc --noEmit
0 errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Bug fixes and patches (1.0.0 → 1.0.1)

Projects

None yet

1 participant