Skip to content

Reweight trending toward recency, RO topic→categorie, delete-account modal - #58

Merged
flvvius merged 1 commit into
mainfrom
feat/trending-recency-i18n-delete-modal
Jul 14, 2026
Merged

Reweight trending toward recency, RO topic→categorie, delete-account modal#58
flvvius merged 1 commit into
mainfrom
feat/trending-recency-i18n-delete-modal

Conversation

@flvvius

@flvvius flvvius commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Three changes:

1. Trending ranking — weight recency more, drop stale events

computeTrendingScore (packages/backend/convex/lib/publicEventPreviews.ts):

  • Recency weight 6× higher: 1 point per 10 min (was 1 pt/hour) — recency now dominates coverage.
  • Coverage bonus (source×10 + article×3) capped at 288 (~48h of recency), so a heavily-covered but stale event floats at most ~2 days above fresher ones. Nothing older than ~3–4 days holds a top slot; actively-updated stories keep a fresh lastUpdatedAt and are unaffected.
  • Score stays built from an absolute timestamp (indexed by by_trending_score, never recomputed by a cron) so the stored ordering can't silently rot.

⚠️ Deploy step: existing rows hold old-formula scores. New internal mutation events.rescorePublicPreviews re-runs the normal idempotent preview write path for every preview (recomputing the score) and rebuilds the anonymous snapshot on the final page. Must be run in prod after deploy (paginate continueCursor until isDone).

2. Romanian i18n: topiccategorie

Replaced the anglicism topic/topicuri with categorie/categorii (feminine agreement) across the feed filter (feed.topic.*, feed.preferredTopics) and admin diagnostics. Keys and category display names (Politică, Economie…) unchanged; the legitimate word subiect left alone. Added a /\btopicur/i ban + pinned-value test to the enforcement suite.

3. Account deletion → confirmation modal

Replaced the two-tap "arm" pattern (which swapped the entire warning sentence onto the button label) with a proper Dialog: trigger button + modal with title, permanence warning, Cancel, and destructive Delete. Reworded profile.deleteConfirm; added profile.deleteDialogTitle / profile.deleteCancel in RO + EN.

Testing

  • Backend: vitest 277 pass / 4 skipped; tsc clean
  • Web: vitest 84 pass; tsc clean
  • Biome clean on changed files

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a confirmation dialog for account deletion, including cancel and destructive confirmation actions.
    • Improved trending event rankings by balancing freshness with a capped coverage bonus.
  • Localization

    • Updated Romanian wording from “topics” to “categories” across activity, feed, and profile areas.
    • Added account-deletion confirmation text in English and Romanian.
  • Bug Fixes

    • Prevented heavily covered older events from outranking substantially fresher events indefinitely.

…delete-account modal

Trending: computeTrendingScore now weights recency 6x higher (1 pt per 10 min)
and caps the coverage bonus at 288 (~48h), so heavily-covered but stale events
no longer hold top trending slots past ~3-4 days. Score stays built from an
absolute timestamp (indexed, never recomputed by cron) so ordering can't rot.
Adds events.rescorePublicPreviews: a one-shot, idempotent migration that
re-syncs every existing preview so stored old-formula scores get recomputed,
then rebuilds the anonymous feed snapshot.

i18n (RO): replace the anglicism "topic/topicuri" with "categorie/categorii"
across the feed filter and admin diagnostics (keys and category names
unchanged); ban the term in the enforcement test.

Profile: replace the two-tap "arm" delete pattern (which crammed the full
warning sentence onto the button label) with a proper confirmation Dialog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
news Error Error Jul 14, 2026 3:54pm
news-web Ready Ready Preview, Comment Jul 14, 2026 3:54pm

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR replaces account deletion’s two-step confirmation with a dialog, adjusts public event preview trending scores and adds paginated rescoring, and updates Romanian localization terminology with regression coverage.

Changes

Account deletion confirmation

Layer / File(s) Summary
Dialog-based deletion flow
apps/web/src/components/profile/AuthenticatedProfile.tsx, packages/i18n/src/strings.ts
Account deletion now uses a controlled confirmation dialog with cancel and destructive confirmation actions, supported by English and Romanian dialog copy.

Public preview ranking

Layer / File(s) Summary
Trending score calculation and regression coverage
packages/backend/convex/lib/publicEventPreviews.ts, packages/backend/convex/publicEventPreviews.test.ts
Trending scores use capped coverage and finer recency weighting, with updated formula assertions and a stale-event ranking regression test.
Paginated preview resynchronization
packages/backend/convex/events.ts
The new internal mutation resynchronizes paginated public previews and rebuilds anonymous feed snapshots after the final page.

Romanian category terminology

Layer / File(s) Summary
Category wording and localization safeguards
packages/i18n/src/strings.ts, apps/web/src/lib/i18n/strings.test.ts
Romanian diagnostics and feed labels use category terminology, while tests ban the deprecated wording and verify approved translations.

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

Sequence Diagram(s)

sequenceDiagram
  participant RescoreMutation
  participant PublicEventPreviews
  participant PublicFeedSnapshots
  RescoreMutation->>PublicEventPreviews: paginate and resync previews
  PublicEventPreviews-->>RescoreMutation: return page progress
  RescoreMutation->>PublicFeedSnapshots: rebuild after final page
Loading

Possibly related PRs

  • flvvius/news#33: Adds related public event preview and feed snapshot resynchronization infrastructure.
🚥 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 is specific, concise, and covers the three main changes in the PR.
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.
✨ 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 feat/trending-recency-i18n-delete-modal

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.

@flvvius
flvvius merged commit 659bd4c into main Jul 14, 2026
3 of 5 checks passed
@flvvius
flvvius deleted the feat/trending-recency-i18n-delete-modal branch July 14, 2026 15:55

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

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/components/profile/AuthenticatedProfile.tsx (1)

107-123: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent false "deletion failed" toasts from sign-out errors.

The inner try/finally block for authClient.signOut({}) does not catch errors. If the sign-out request fails (e.g., due to a brief network interruption, or because the session was just wiped out by deleteMyAccount), the error propagates to the outer catch block.

When this happens, the finally block schedules a page redirect, but the outer catch executes immediately, showing a false profile.deleteFailed error toast despite the user's account having been successfully deleted. Adding an inner catch swallows this benign error and ensures the success state remains intact before the page unloads. As per path instructions, focus on web performance, proper error handling, and runtime errors.

💡 Proposed fix
   const handleDeleteAccount = async () => {
     setIsDeleting(true);
     try {
       await deleteMyAccount({});
       toast.success(t("profile.deleteDone"));
       try {
         await authClient.signOut({});
+      } catch (signOutError) {
+        console.error("Sign-out after deletion failed:", signOutError);
       } finally {
         location.href = "/";
       }
     } catch (error) {
       console.error("Account deletion failed:", error);
       toast.error(t("profile.deleteFailed"));
       setIsDeleting(false);
       setDeleteDialogOpen(false);
     }
   };
🤖 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/components/profile/AuthenticatedProfile.tsx` around lines 107 -
123, Update handleDeleteAccount so errors from authClient.signOut({}) are caught
and swallowed within the inner sign-out flow, while retaining the finally
redirect to "/". Ensure sign-out failures do not reach the outer catch or
trigger profile.deleteFailed after deleteMyAccount succeeds.

Source: Path instructions

🤖 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/components/profile/AuthenticatedProfile.tsx`:
- Around line 315-360: Update the DialogContent in the delete-account flow
around handleDeleteAccount so it cannot close while isDeleting is true: hide the
default close button with showCloseButton and prevent both outside-pointer and
Escape-key dismissal during the mutation via the appropriate event handlers.
Preserve normal dialog dismissal when isDeleting is false.

---

Outside diff comments:
In `@apps/web/src/components/profile/AuthenticatedProfile.tsx`:
- Around line 107-123: Update handleDeleteAccount so errors from
authClient.signOut({}) are caught and swallowed within the inner sign-out flow,
while retaining the finally redirect to "/". Ensure sign-out failures do not
reach the outer catch or trigger profile.deleteFailed after deleteMyAccount
succeeds.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e25fbae2-5517-4bb8-99d5-cac4a4c2f894

📥 Commits

Reviewing files that changed from the base of the PR and between e24c456 and 71ef797.

📒 Files selected for processing (6)
  • apps/web/src/components/profile/AuthenticatedProfile.tsx
  • apps/web/src/lib/i18n/strings.test.ts
  • packages/backend/convex/events.ts
  • packages/backend/convex/lib/publicEventPreviews.ts
  • packages/backend/convex/publicEventPreviews.test.ts
  • packages/i18n/src/strings.ts

Comment on lines +315 to +360
<Dialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
>
<span>
{deleteArmed
? t("profile.deleteConfirm")
: t("profile.deleteAccount")}
</span>
<Trash2 className="size-4" />
</Button>
<DialogTrigger asChild>
<Button
type="button"
variant="outline"
className="w-full justify-between border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive"
disabled={isDeleting}
>
<span>{t("profile.deleteAccount")}</span>
<Trash2 className="size-4" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
{t("profile.deleteDialogTitle")}
</DialogTitle>
<DialogDescription>
{t("profile.deleteConfirm")}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button
type="button"
variant="outline"
disabled={isDeleting}
>
{t("profile.deleteCancel")}
</Button>
</DialogClose>
<Button
type="button"
variant="destructive"
disabled={isDeleting}
onClick={() => void handleDeleteAccount()}
>
<Trash2 className="size-4" />
<span>{t("profile.deleteAccount")}</span>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

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.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prevent dialog closure while deletion is in progress.

While isDeleting is true, the buttons inside the dialog are disabled, but the user can still close the modal by clicking the backdrop, pressing the Escape key, or clicking the default top-right close button. This causes the UI to abruptly disappear while the background deletion request is still pending.

Consider passing the appropriate event handlers and showCloseButton prop to DialogContent to lock the modal open during the mutation. This gives the user clear, continuous visual feedback until the redirect occurs.

✨ Proposed optional refactor
-                    <DialogContent>
+                    <DialogContent
+                      onInteractOutside={(e) => {
+                        if (isDeleting) e.preventDefault();
+                      }}
+                      onEscapeKeyDown={(e) => {
+                        if (isDeleting) e.preventDefault();
+                      }}
+                      showCloseButton={!isDeleting}
+                    >
                       <DialogHeader>
                         <DialogTitle>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Dialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
>
<span>
{deleteArmed
? t("profile.deleteConfirm")
: t("profile.deleteAccount")}
</span>
<Trash2 className="size-4" />
</Button>
<DialogTrigger asChild>
<Button
type="button"
variant="outline"
className="w-full justify-between border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive"
disabled={isDeleting}
>
<span>{t("profile.deleteAccount")}</span>
<Trash2 className="size-4" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
{t("profile.deleteDialogTitle")}
</DialogTitle>
<DialogDescription>
{t("profile.deleteConfirm")}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button
type="button"
variant="outline"
disabled={isDeleting}
>
{t("profile.deleteCancel")}
</Button>
</DialogClose>
<Button
type="button"
variant="destructive"
disabled={isDeleting}
onClick={() => void handleDeleteAccount()}
>
<Trash2 className="size-4" />
<span>{t("profile.deleteAccount")}</span>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog
open={deleteDialogOpen}
onOpenChange={setDeleteDialogOpen}
>
<DialogTrigger asChild>
<Button
type="button"
variant="outline"
className="w-full justify-between border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive"
disabled={isDeleting}
>
<span>{t("profile.deleteAccount")}</span>
<Trash2 className="size-4" />
</Button>
</DialogTrigger>
<DialogContent
onInteractOutside={(e) => {
if (isDeleting) e.preventDefault();
}}
onEscapeKeyDown={(e) => {
if (isDeleting) e.preventDefault();
}}
showCloseButton={!isDeleting}
>
<DialogHeader>
<DialogTitle>
{t("profile.deleteDialogTitle")}
</DialogTitle>
<DialogDescription>
{t("profile.deleteConfirm")}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button
type="button"
variant="outline"
disabled={isDeleting}
>
{t("profile.deleteCancel")}
</Button>
</DialogClose>
<Button
type="button"
variant="destructive"
disabled={isDeleting}
onClick={() => void handleDeleteAccount()}
>
<Trash2 className="size-4" />
<span>{t("profile.deleteAccount")}</span>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
🤖 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/components/profile/AuthenticatedProfile.tsx` around lines 315 -
360, Update the DialogContent in the delete-account flow around
handleDeleteAccount so it cannot close while isDeleting is true: hide the
default close button with showCloseButton and prevent both outside-pointer and
Escape-key dismissal during the mutation via the appropriate event handlers.
Preserve normal dialog dismissal when isDeleting is false.

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