Skip to content

Fix chart empty states, prediction note sanitization, and reconciliation/retention coverage - #1910

Merged
Olowodarey merged 4 commits into
Arena1X:mainfrom
ElizabethChi:fix/insightarena-assigned-batch
Sep 28, 2026
Merged

Olowodarey merged 4 commits into
Arena1X:mainfrom
ElizabethChi:fix/insightarena-assigned-batch

Conversation

@ElizabethChi

Copy link
Copy Markdown

Summary

This batches four assigned issues.

#1578: Interactive chart empty and loading states

interactive-chart.tsx had no loading or empty-data handling and rendered a misleading empty plot. Added an explicit loading skeleton (reusing the existing Skeleton component), an empty state for no-data series (reusing the existing EmptyState component), a guard against single-point/all-zero series breaking percentage-based scaling, and a visually hidden accessible summary of each series for screen readers.

Closes #1578

#1624: Prediction note length and sanitization

UpdatePredictionNoteDto.note already had @MaxLength(1000), but nothing sanitized the content before it was persisted, and there is no global ValidationPipe wired up in main.ts, so DTO decorators alone are not a reliable enforcement point. Added a sanitizePlainText utility (strips HTML/script markup and control characters) and enforced it directly in PredictionsService.updateNote, throwing a new NoteTooLongException (NOTE_TOO_LONG, 400) when the sanitized note is still over the limit. The DTO also gets a @Transform using the same utility as a first line of defense for any call path that does go through a validation pipe.

I deliberately left the note column's migration/type as-is (unbounded text) since the issue's ask is about API-layer bounding and sanitization; changing the column to a bounded type would be a separate, higher-risk schema change.

Closes #1624

#1854: Season distribution reconciliation after partial payout failure

reconcileSeasonDistribution only summed SUCCEEDED ledger rows against the pool total and returned a boolean match. Extended it to also return missingRecipients: the ledger rows that are not yet SUCCEEDED (i.e. still PENDING or FAILED). A partial failure now identifies exactly which recipients are still owed instead of only reporting a pool-level mismatch, and a season whose ledger is fully SUCCEEDED reports an empty missingRecipients list (no-op). The existing per-recipient ledger idempotency in computeSeasonRewards (unique (season, recipient) row, already tested) is what prevents re-paying a succeeded recipient on rerun; added a test exercising that guarantee through reconcileSeasonDistribution directly.

Closes #1854

#1855: Analytics retention computation for cohorts with no return visits

computeRetention already guards totalUsers > 0 before dividing, so it already returns 0 (not NaN) for a cohort with signups but no returns, and a defined empty result for zero signups, but none of this had test coverage. Added a dedicated spec covering: zero return visits reports 0%, zero signups returns a defined empty result rather than throwing, and full retention reports 100%.

Closes #1855

Test plan

  • backend: npx jest (full suite) - all failures are pre-existing and unrelated to this diff (missing entity modules in account/achievements specs, etc.); every file touched in this PR passes in full.
  • backend: npx tsc --noEmit - no new type errors introduced (baseline pre-existing errors unchanged, confirmed by diffing against a stash of these changes).
  • backend: npx eslint "src/**/*.ts" - no new errors or warnings in touched files.
  • backend: npx prettier --check on all touched files.
  • frontend: npx vitest run (full suite) - all failures are pre-existing and unrelated (wallet modal/hook tests); interactive-chart.test.tsx passes in full (9/9).
  • frontend: npx tsc --noEmit - no errors in touched files.

Guard interactive-chart against misleading empty axes by adding an
explicit loading skeleton and a no-data empty state, reusing the
existing Skeleton and EmptyState components. Also guards scaling
against single-point series and all-zero values, and adds a visually
hidden accessible summary of each series for screen readers.

Closes Arena1X#1578
Strip HTML/script markup and control characters from prediction notes
before persisting, and reject a sanitized note that is still over the
1000 character limit with a clear NOTE_TOO_LONG validation error.
Enforcement lives in PredictionsService.updateNote (defense in depth
alongside the DTO's own MaxLength/Transform), since the app has no
global ValidationPipe wired up and this method is also called directly
elsewhere.

Closes Arena1X#1624
Add dedicated tests for AnalyticsService.getRetention confirming a
cohort with signups but zero return visits reports 0% (not NaN), a
period with zero signups returns a defined empty result instead of
throwing, and a cohort where every user returned reports 100%. The
existing totalUsers > 0 guard in computeRetention already produces
this behavior; this closes the missing coverage called out in the
issue and pins the behavior against regressions.

Closes Arena1X#1855
Extend reconcileSeasonDistribution to return the ledger rows still
missing a confirmed (SUCCEEDED) payout, so a partial payout failure is
identified precisely instead of re-flagging the whole season as
undistributed. A season whose ledger is fully SUCCEEDED and matches
the pool now reports an empty missingRecipients list, a no-op needing
no further action. Existing per-recipient ledger idempotency in
computeSeasonRewards already prevents re-paying a succeeded recipient
on rerun; added a test exercising that path through reconciliation
directly.

Closes Arena1X#1854
@drips-wave

drips-wave Bot commented Sep 27, 2026

Copy link
Copy Markdown

@ElizabethChi Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
insight-arena-4rll Error Error Sep 27, 2026 2:33pm UTC

@Olowodarey
Olowodarey merged commit 540760a into Arena1X:main Sep 28, 2026
1 of 2 checks passed

This branch had an error being deployed

1 failed deployment
Preview — bb81913e Deployed Sep 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants