feat: SEO improvements, invite rebate fixes, upstream sync - #6531
feat: SEO improvements, invite rebate fixes, upstream sync#6531seven7763 wants to merge 38 commits into
Conversation
Document merge-friendly design for invitee top-up rebates into aff_quota with ledger, configurable ratio, and user/admin pages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Step-by-step merge-friendly plan covering ledger, hooks, APIs, and default/classic UIs with TDD-oriented tasks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Options default off; unique topup_id ledger; unit tests for formula, disabled path, no inviter, and idempotent grant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hook Stripe/Creem/Waffo/Pancake/manual complete/epay success paths; add user and admin invite_rebate endpoints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Default and classic themes: stats pages, transfer reuse, settings options for enable flag and basis-point ratio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Skip grant when inviter missing; admin route guard; classic sidebar module mapping; coerce enable switch; transfer loading state; grant log only on first success. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UI already sends aff_code in body; API clients and invite links using only ?aff= now also resolve inviter_id on password register. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add zh/zh-TW translations for rebate pages, admin filters, settings, and transfer dialog labels used by the feature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Overflow-safe rebate math with hard cap - Skip grants for disabled inviters/invitees and user_id mismatch - Treat gorm.ErrDuplicatedKey as idempotent - Reject non-positive aff_transfer amounts - Ignore disabled inviters at register; bound aff code length - Mask invitee username/display on inviter invitee list Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add scheduled system task to re-grant missing rebates for successful top-ups without ledger rows. Reject aff_transfer when account is disabled at both API and model layers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Recover from RecordLog/cache panics so rebate crediting cannot be rolled back or aborted by logging/redis issues. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add root-only POST /api/system-task/invite-rebate-backfill, option InviteTopupRebateBackfillMinutes (1-1440), admin UI trigger button, and settings fields on default/classic themes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Redesign user/admin pages with SectionPageLayout, stats tiles, tabs, invite link copy, and a masked public leaderboard (by rebate/invitees). Add GET /api/user/invite_rebate/leaderboard and classic leaderboard table. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Load summary/logs/invitees independently so a missing leaderboard route does not break the whole page; silence global error toast for that call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Avoid wiping board state when leaderboard request fails; show error/empty state with retry. Keep tab panels mounted for smoother switching. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only expose raw user_id for the viewer's own row; others remain 0 with masked names to reduce cross-user enumeration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge-friendly SPA SEO plan: configurable meta/OG, robots/sitemap, JSON-LD, minimal upstream hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Task breakdown for merge-friendly SPA SEO: options, robots/sitemap, default lib/seo, settings UI, classic light parity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add configurable SEO.* options, expose them via /api/status, and serve crawler-friendly /robots.txt and /sitemap.xml with merge-friendly files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement merge-friendly SPA SEO: SEO.* options, /robots.txt and /sitemap.xml, default lib/seo DOM helpers, homepage JSON-LD, and light classic parity with settings fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix broken import insertion that prevented the SPA bundle from loading, and fetch /api/status on the homepage so SEO meta and JSON-LD apply reliably. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Allow SEO.Title (full) or SEO.TitleSuffix appended to SystemName so homepage titles can include long-tail keywords instead of short brand-only names. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merge origin/main into local main while keeping invite top-up rebate and homepage SEO. - Keep backend rebate/SEO modules and thin hooks - Drop removed classic theme paths (upstream web consolidated to web/src) - Port invite-rebate feature, SEO lib, and routes from web/default to web/src - Preserve robots.txt/sitemap.xml registration with WebAssets router API Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove leftover web/default invite-rebate/SEO paths (live under web/src) - Reapply pending invite rebate and SEO hardening on new layout - Keep OAuth aff binding disabled-inviter guard with upstream affiliateCode API - Sync route-level SEO apply in __root with new auth-session bootstrap Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…CI workflows Brings in 21 upstream commits including: - relaykit module extraction and import path refactoring - trusted_proxies renamed to middleware package - per-channel HTTP transport controls - OIDC custom login display name - Qwen thinking_budget passthrough fix - CI workflow for build/typecheck/test No conflicts with custom features (invite rebate, SEO, etc.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tus cache refresh - Restructure SEO admin settings into dedicated section with proper grid layout - Add RobotsIndex toggle switch to admin UI - Enhance sitemap.xml with lastmod/changefreq/priority metadata - Add /login and /register to sitemap, more Disallow paths to robots.txt - Fix og:site_name to always use brand name instead of full long-tail title - Remove redundant getStatus() call in useHomeSeo (use localStorage cache) - Add SEO keys to STATUS_RELATED_KEYS for immediate cache refresh on save - Add invite_rebate_backfill to system task type labels - Add missing i18n translations for SEO settings across all locales Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mapping
- Add OptionMapRWMutex.RLock/RUnlock around reads of SEOSiteURL,
SEORobotsIndex, and ServerAddress in seoSiteBase() and RobotsTxt()
- XML-escape the site URL in sitemap <loc> elements using html.EscapeString
- Add proper native translations for SEO keys in fr/ja/ru/vi locales
(previously untranslated English placeholders)
- Add missing description keys (Used for meta, Canonical base, etc.)
to fr/ja/ru/vi locales
- Extract nested ternary for language detection into resolveDocumentLang()
helper in web/src/lib/seo/apply.ts
- Use useRouterState pathname selector in __root.tsx (already done)
- Add .url().optional().or(z.literal('')) validation to SEO.SiteURL and
SEO.OGImage fields in the zod schema
- Extract readCachedStatus() helper to web/src/lib/seo/cached-status.ts
and use it in use-home-seo.ts and __root.tsx
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WalkthroughChangesInvite top-up rebate
Homepage SEO
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
controller/topup.go (1)
391-413: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftMake Epay completion and user credit atomic before exposing the order to backfill.
topUp.Update()persists success/completion beforeIncreaseUserQuota. If quota credit fails, backfill later sees an eligible successful order and can reward the inviter even though the invitee received no quota. Commit the status and quota update in one transaction, then grant the rebate post-commit.🤖 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 `@controller/topup.go` around lines 391 - 413, Wrap the topUp.Update and IncreaseUserQuota operations in a single database transaction so the completion status and user quota are committed or rolled back together. Ensure the order is not eligible for backfill until both succeed, then invoke GrantInviteTopupRebate only after the transaction commits; preserve the existing error logging and response behavior for failures.
🟡 Minor comments (15)
docs/superpowers/notes/2026-07-12-invite-topup-rebate-done.md-12-13 (1)
12-13: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUpdate the frontend paths in the completion note.
The documented paths use
web/default/...andweb/classic/..., but the supplied PR file inventory places the implementation underweb/src/.... Replace these stale paths so future maintainers can locate the actual code.🤖 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 `@docs/superpowers/notes/2026-07-12-invite-topup-rebate-done.md` around lines 12 - 13, Update the UI paths in the completion note to reference the implementation under web/src/... instead of the stale web/default/... and web/classic/... locations, while leaving the model and controller paths unchanged.controller/seo.go-88-99 (1)
88-99: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not report sitemap generation time as each page’s modification time.
This marks every URL as changed every day, even when its content did not change. Omit
lastmoduntil a real per-page update time is available.🤖 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 `@controller/seo.go` around lines 88 - 99, Remove the shared lastmod generation and omit the lastmod XML element from the sitemap entries built in the SEO handler. Keep the remaining URL fields unchanged, and only reintroduce lastmod when a real per-page modification timestamp is available.web/src/features/system-settings/general/system-info-section.tsx-115-121 (1)
115-121: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRestore URL validation in the active resolver.
systemInfoSchemaWithI18nis the schema passed tozodResolver, butSEO.SiteURLandSEO.OGImageare onlyz.string().optional()here while_systemInfoSchemarequiresz.string().url().optional().or(z.literal('')). Use the same URL-or-empty constraints; otherwise invalid strings can be submitted and stored.🤖 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 `@web/src/features/system-settings/general/system-info-section.tsx` around lines 115 - 121, Update SEO.SiteURL and SEO.OGImage in systemInfoSchemaWithI18n to use the same optional URL-or-empty validation as _systemInfoSchema, preserving acceptance of empty strings while rejecting invalid URLs. Ensure the active zodResolver schema enforces these constraints.docs/superpowers/notes/2026-07-12-seo-homepage-done.md-20-24 (1)
20-24: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd a blank line before the table.
Markdownlint reports MD058 because the table directly follows its heading.
🤖 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 `@docs/superpowers/notes/2026-07-12-seo-homepage-done.md` around lines 20 - 24, Insert a blank line between the preceding heading and the SEO configuration table in the documentation, leaving the table contents unchanged.Source: Linters/SAST tools
docs/superpowers/plans/2026-07-12-seo-homepage.md-7-8 (1)
7-8: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAlign SEO documentation with the active frontend layout. Both documents refer to
web/default/src/..., while the reviewed implementation is underweb/src/....
docs/superpowers/plans/2026-07-12-seo-homepage.md#L7-L8: replace the obsoleteweb/default/srcpaths throughout the plan.docs/superpowers/notes/2026-07-12-seo-homepage-done.md#L14-L17: record the deliveredweb/src/lib/seo/*path.🤖 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 `@docs/superpowers/plans/2026-07-12-seo-homepage.md` around lines 7 - 8, Update docs/superpowers/plans/2026-07-12-seo-homepage.md throughout to replace obsolete web/default/src paths with web/src paths, including the SEO helper location. Update docs/superpowers/notes/2026-07-12-seo-homepage-done.md at lines 14-17 to record the delivered web/src/lib/seo/* path.docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md-41-42 (1)
41-42: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winSynchronize the rebate design and implementation plan with the final implementation.
docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md#L41-L42: remove backfill from non-goals.docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md#L96-L97: documentgrantedandskippedstatuses.docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md#L116-L119: document permanent skip rows.docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md#L207-L207: update tiny-top-up behavior to record a skip.docs/superpowers/plans/2026-07-12-invite-topup-rebate.md#L148-L153: change the expected 1% result from50to5000.docs/superpowers/plans/2026-07-12-invite-topup-rebate.md#L502-L503: document post-commit best-effort granting.docs/superpowers/plans/2026-07-12-invite-topup-rebate.md#L1021-L1021: remove the claim that transactional paths grant inside the transaction.🤖 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 `@docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md` around lines 41 - 42, Synchronize the documentation with the final implementation: in docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md at lines 41-42 remove backfill from the non-goals, at lines 96-97 document granted and skipped statuses, at lines 116-119 document permanent skip rows, and at line 207 state that tiny top-ups record a skip; in docs/superpowers/plans/2026-07-12-invite-topup-rebate.md at lines 148-153 change the expected 1% result from 50 to 5000, at lines 502-503 document post-commit best-effort granting, and at line 1021 remove the claim that transactional paths grant inside the transaction.model/invite_rebate.go-412-416 (1)
412-416: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPropagate reporting query failures instead of returning fabricated zero values.
These ignored errors make invitee totals appear as zero and can report the viewer as rank 1 when the database query fails. Return the error so controllers can produce an accurate failure response.
Also applies to: 687-723
🤖 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 `@model/invite_rebate.go` around lines 412 - 416, Propagate database errors from the reporting queries that scan invitee rebate totals, including the InviteRebate aggregate query and the related queries in the additional referenced section. Replace ignored Scan(...).Error results with returned errors through the enclosing reporting methods so controllers receive failures instead of fabricated zero totals or incorrect rank data.web/src/i18n/locales/fr.json-5231-5283 (1)
5231-5283: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winInvite-rebate strings left untranslated in fr.json.
Most of the new invite-rebate keys in this range are copied verbatim from English (e.g.
"Your invite program","Invitee","Rebate","Pending rewards","Copy invite link","Top inviters","Failed to load leaderboard", and ~30 more), while the SEO keys added in the same hunk (lines 5285-5294) were properly translated. French-locale users will see the entire new Invite Rebate UI in English.🤖 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 `@web/src/i18n/locales/fr.json` around lines 5231 - 5283, Translate all newly added invite-rebate values in the French locale, including labels, messages, actions, statuses, leaderboard text, and admin settings, while preserving the existing interpolation placeholders such as {{rate}}. Keep the already translated SEO entries unchanged and ensure no invite-rebate value in this section remains identical to its English source unless it is intentionally language-neutral.web/src/i18n/locales/vi.json-5231-5246 (1)
5231-5246: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winTranslate the new invite-rebate strings into Vietnamese.
These newly added user-facing values remain in English, so Vietnamese users will see untranslated rebate, leaderboard, and backfill UI text. Preserve all interpolation placeholders while providing Vietnamese translations.
Also applies to: 5248-5259, 5261-5283
🤖 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 `@web/src/i18n/locales/vi.json` around lines 5231 - 5246, Translate all newly added invite-rebate, leaderboard, and backfill values in vi.json into natural Vietnamese, including the ranges referenced by the comment. Preserve every interpolation placeholder exactly, such as {{rate}}, while leaving the translation keys unchanged.web/src/i18n/locales/vi.json-5294-5294 (1)
5294-5294: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRemove the duplicate
Legalkey.
Legalis already defined at Line 2478. Duplicate JSON keys have parser-dependent overwrite behavior and should not be added here.Proposed fix
- "Legal": "Pháp lý"🤖 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 `@web/src/i18n/locales/vi.json` at line 5294, Remove the duplicate "Legal" entry near the later locale section, keeping the existing "Legal" definition already present earlier in vi.json unchanged.web/src/i18n/locales/zh-TW.json-5309-5309 (1)
5309-5309: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAvoid redefining the
Legalkey.
Legalis already defined at Line 2478. This duplicate relies on last-write-wins parser behavior and changes every existingLegaltranslation from法律to法律條款. Remove this entry, update the original intentionally, or use a distinct key for the SEO label.🤖 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 `@web/src/i18n/locales/zh-TW.json` at line 5309, Remove the duplicate “Legal” entry from the locale object, or rename it to a distinct SEO-specific key and update its callers; if the intended translation is global, modify the existing “Legal” definition near the earlier declaration instead. Ensure the locale contains only one “Legal” key and preserves the intended translations.web/src/i18n/locales/zh.json-5310-5310 (1)
5310-5310: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winRemove the duplicate
Legalkey.
"Legal"already exists at Line 2478. Keeping both keys makes the parsed locale depend on duplicate-key behavior and causes all existingLegalusages to resolve to"法律条款"instead of"法律". Reuse the existing key or introduce a distinct source key for legal terms.🤖 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 `@web/src/i18n/locales/zh.json` at line 5310, Remove the later duplicate "Legal" entry from the Chinese locale and retain the existing "Legal" key so lookups continue resolving to its original translation.Source: Learnings
web/src/features/system-settings/general/quota-settings-section.tsx-59-60 (1)
59-60: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReject fractional basis points and backfill intervals.
Both settings represent integers, but the schemas and inputs accept decimals. Add
.int()andstep={1}so invalid values cannot reach the option API.Proposed fix
- InviteTopupRebateRatioBp: z.coerce.number().min(0).max(10000), - InviteTopupRebateBackfillMinutes: z.coerce.number().min(1).max(1440), + InviteTopupRebateRatioBp: z.coerce.number().int().min(0).max(10000), + InviteTopupRebateBackfillMinutes: z.coerce.number().int().min(1).max(1440),Add to both number inputs:
type='number' + step={1}Also applies to: 289-335
🤖 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 `@web/src/features/system-settings/general/quota-settings-section.tsx` around lines 59 - 60, Update the Zod schemas for InviteTopupRebateRatioBp and InviteTopupRebateBackfillMinutes to require integers with .int(), and set step={1} on both corresponding number inputs in the quota settings section so fractional values are rejected before reaching the option API.web/src/features/wallet/components/affiliate-rewards-card.tsx-77-84 (1)
77-84: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the new details link visible.
The link follows a long sentence inside
line-clamp-1, so it will commonly be truncated and unclickable. Remove the clamp or render the link separately.- <p className='text-muted-foreground line-clamp-1 text-xs'> + <p className='text-muted-foreground text-xs'>🤖 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 `@web/src/features/wallet/components/affiliate-rewards-card.tsx` around lines 77 - 84, Update the paragraph rendering in the affiliate rewards card around the details Link so the “View top-up rebate details” link remains visible and clickable; remove the line-clamp-1 class or render the link outside the clamped sentence while preserving the existing text and styling.web/src/features/invite-rebate/index.tsx-758-758 (1)
758-758: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winTranslate the admin table’s
IDheading.- <TableHead>ID</TableHead> + <TableHead>{t('ID')}</TableHead>As per coding guidelines, “All user-facing React UI text must support i18n.” <coding_guidelines>
🤖 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 `@web/src/features/invite-rebate/index.tsx` at line 758, Update the admin table header near the TableHead containing “ID” to use the existing i18n translation mechanism instead of a hardcoded string, adding the required translation key if one does not already exist while preserving the displayed meaning.Source: Coding guidelines
🧹 Nitpick comments (4)
web/src/features/home/hooks/use-home-seo.ts (1)
12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd the hook’s explicit return type.
Use
export function useHomeSeo(): voidto meet the frontend typing rule.🤖 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 `@web/src/features/home/hooks/use-home-seo.ts` at line 12, Add the explicit void return type to the exported useHomeSeo function declaration, changing its signature to return void while preserving the hook’s existing implementation.Source: Coding guidelines
web/src/lib/seo/dom.ts (1)
1-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeclare the exported helper return types.
All changed helpers have typed parameters but omit their
voidreturn type. Add: voidconsistently.🤖 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 `@web/src/lib/seo/dom.ts` around lines 1 - 55, Declare an explicit void return type on each exported helper: upsertMetaByName, upsertMetaByProperty, upsertLinkRel, upsertJsonLd, and removeJsonLd, without changing their existing behavior.Source: Coding guidelines
web/src/routes/__root.tsx (1)
49-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd an explicit component return type.
RootComponentis changed TSX code but has no return annotation. Add a type-onlyReactElementimport and annotate the function, then verify with the project’s Bun lint/type-check commands.🤖 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 `@web/src/routes/__root.tsx` around lines 49 - 55, Add a type-only ReactElement import and annotate RootComponent with ReactElement as its explicit return type. Preserve the existing component implementation, then run the project’s Bun lint and type-check commands to validate the change.Source: Coding guidelines
web/src/i18n/locales/ja.json (1)
5230-5294: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInvite-rebate strings left untranslated in ja.json and ru.json, while SEO strings in the same block were translated. Both locale files show the same gap: the SEO configuration strings added in this PR (SEO Description, SEO Title Suffix, SEO Keywords, SEO Settings, SEO Site URL, SEO OG Image URL, etc.) are properly localized, but most invite-rebate strings (
Your invite program,Rebate sum,Invite Top-up Rebate,Pending rewards,Top inviters,Backfill queued,Copy invite link, etc.) keep their English text as the translated value.
web/src/i18n/locales/ja.json#L5230-L5294: translate the invite-rebate strings into Japanese to match the SEO strings' translation quality in this same block.web/src/i18n/locales/ru.json#L5230-L5294: translate the invite-rebate strings into Russian to match the SEO strings' translation quality in this same block.🤖 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 `@web/src/i18n/locales/ja.json` around lines 5230 - 5294, Translate all invite-rebate entries in web/src/i18n/locales/ja.json lines 5230-5294 into natural Japanese, replacing English values while preserving keys and interpolation placeholders. Apply the same change to web/src/i18n/locales/ru.json lines 5230-5294 with natural Russian translations; leave the already localized SEO strings unchanged.
🤖 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 `@model/invite_rebate.go`:
- Around line 288-340: Update the rebate transaction around the quota updates
and ledger handling to use the checked quota helpers, propagate their clamp
results, and calculate the amount actually credited after saturation. Persist
that credited amount in InviteRebate rather than the full rebate, including zero
when fully capped, and set granted/log only according to the actual credited
result so RecordLog reports the credited amount and clamp outcome accurately.
- Line 105: Update both skip and grant insert paths in the invite rebate flow to
use GORM’s clause.OnConflict with the topup_id column and DoNothing enabled,
instead of detecting duplicate-key errors after Create. Use
Create(...).RowsAffected to distinguish whether each row was inserted or
skipped, and remove the post-insert unique-violation handling.
- Around line 309-312: Update the zero RowsAffected branch in the invite-rebate
transaction to check the error returned by inner.Delete for the newly inserted
InviteRebate row and return that failure instead of returning nil. Preserve the
existing successful cleanup behavior while ensuring any cleanup failure causes
the transaction to roll back rather than committing an uncredited granted
ledger.
In `@model/option.go`:
- Around line 333-349: The InviteTopupRebateEnabled transition must refresh and
persist the cutoff on every off→on enablement. In the InviteTopupRebateEnabled
case, generate a new timestamp and save it successfully before publishing the
enabled state, update the in-memory value only after persistence succeeds, and
return any DB.Save failure instead of ignoring it.
In `@web/src/features/invite-rebate/index.tsx`:
- Around line 645-674: Separate the invitee and inviter text input state from
the applied filter values used by reload. Keep reload and its useEffect
dependent only on applied filters, and have the Filter button copy the current
draft inputs into those applied values before triggering the request, preventing
requests on every keystroke and stale partial-ID results.
- Around line 125-138: Update the allSettled response handling in the
invite-rebate loading flow to explicitly handle fulfilled business-error
responses where success is false. Ensure failures for summary, logs, invitees,
and affiliate code set appropriate error state or user feedback instead of
silently leaving zero or disabled UI values, while preserving successful
response handling and the existing leaderboard error behavior.
In `@web/src/lib/seo/apply.ts`:
- Around line 154-155: Update the SEO options in applyDocumentSeo so the path
field always preserves the actual path, including for non-marketing pages,
instead of defaulting to '/'. Leave robotsIndex as the sole control for indexing
behavior and preserve the existing publicPath handling.
---
Outside diff comments:
In `@controller/topup.go`:
- Around line 391-413: Wrap the topUp.Update and IncreaseUserQuota operations in
a single database transaction so the completion status and user quota are
committed or rolled back together. Ensure the order is not eligible for backfill
until both succeed, then invoke GrantInviteTopupRebate only after the
transaction commits; preserve the existing error logging and response behavior
for failures.
---
Minor comments:
In `@controller/seo.go`:
- Around line 88-99: Remove the shared lastmod generation and omit the lastmod
XML element from the sitemap entries built in the SEO handler. Keep the
remaining URL fields unchanged, and only reintroduce lastmod when a real
per-page modification timestamp is available.
In `@docs/superpowers/notes/2026-07-12-invite-topup-rebate-done.md`:
- Around line 12-13: Update the UI paths in the completion note to reference the
implementation under web/src/... instead of the stale web/default/... and
web/classic/... locations, while leaving the model and controller paths
unchanged.
In `@docs/superpowers/notes/2026-07-12-seo-homepage-done.md`:
- Around line 20-24: Insert a blank line between the preceding heading and the
SEO configuration table in the documentation, leaving the table contents
unchanged.
In `@docs/superpowers/plans/2026-07-12-seo-homepage.md`:
- Around line 7-8: Update docs/superpowers/plans/2026-07-12-seo-homepage.md
throughout to replace obsolete web/default/src paths with web/src paths,
including the SEO helper location. Update
docs/superpowers/notes/2026-07-12-seo-homepage-done.md at lines 14-17 to record
the delivered web/src/lib/seo/* path.
In `@docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md`:
- Around line 41-42: Synchronize the documentation with the final
implementation: in
docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md at lines 41-42
remove backfill from the non-goals, at lines 96-97 document granted and skipped
statuses, at lines 116-119 document permanent skip rows, and at line 207 state
that tiny top-ups record a skip; in
docs/superpowers/plans/2026-07-12-invite-topup-rebate.md at lines 148-153 change
the expected 1% result from 50 to 5000, at lines 502-503 document post-commit
best-effort granting, and at line 1021 remove the claim that transactional paths
grant inside the transaction.
In `@model/invite_rebate.go`:
- Around line 412-416: Propagate database errors from the reporting queries that
scan invitee rebate totals, including the InviteRebate aggregate query and the
related queries in the additional referenced section. Replace ignored
Scan(...).Error results with returned errors through the enclosing reporting
methods so controllers receive failures instead of fabricated zero totals or
incorrect rank data.
In `@web/src/features/invite-rebate/index.tsx`:
- Line 758: Update the admin table header near the TableHead containing “ID” to
use the existing i18n translation mechanism instead of a hardcoded string,
adding the required translation key if one does not already exist while
preserving the displayed meaning.
In `@web/src/features/system-settings/general/quota-settings-section.tsx`:
- Around line 59-60: Update the Zod schemas for InviteTopupRebateRatioBp and
InviteTopupRebateBackfillMinutes to require integers with .int(), and set
step={1} on both corresponding number inputs in the quota settings section so
fractional values are rejected before reaching the option API.
In `@web/src/features/system-settings/general/system-info-section.tsx`:
- Around line 115-121: Update SEO.SiteURL and SEO.OGImage in
systemInfoSchemaWithI18n to use the same optional URL-or-empty validation as
_systemInfoSchema, preserving acceptance of empty strings while rejecting
invalid URLs. Ensure the active zodResolver schema enforces these constraints.
In `@web/src/features/wallet/components/affiliate-rewards-card.tsx`:
- Around line 77-84: Update the paragraph rendering in the affiliate rewards
card around the details Link so the “View top-up rebate details” link remains
visible and clickable; remove the line-clamp-1 class or render the link outside
the clamped sentence while preserving the existing text and styling.
In `@web/src/i18n/locales/fr.json`:
- Around line 5231-5283: Translate all newly added invite-rebate values in the
French locale, including labels, messages, actions, statuses, leaderboard text,
and admin settings, while preserving the existing interpolation placeholders
such as {{rate}}. Keep the already translated SEO entries unchanged and ensure
no invite-rebate value in this section remains identical to its English source
unless it is intentionally language-neutral.
In `@web/src/i18n/locales/vi.json`:
- Around line 5231-5246: Translate all newly added invite-rebate, leaderboard,
and backfill values in vi.json into natural Vietnamese, including the ranges
referenced by the comment. Preserve every interpolation placeholder exactly,
such as {{rate}}, while leaving the translation keys unchanged.
- Line 5294: Remove the duplicate "Legal" entry near the later locale section,
keeping the existing "Legal" definition already present earlier in vi.json
unchanged.
In `@web/src/i18n/locales/zh-TW.json`:
- Line 5309: Remove the duplicate “Legal” entry from the locale object, or
rename it to a distinct SEO-specific key and update its callers; if the intended
translation is global, modify the existing “Legal” definition near the earlier
declaration instead. Ensure the locale contains only one “Legal” key and
preserves the intended translations.
In `@web/src/i18n/locales/zh.json`:
- Line 5310: Remove the later duplicate "Legal" entry from the Chinese locale
and retain the existing "Legal" key so lookups continue resolving to its
original translation.
---
Nitpick comments:
In `@web/src/features/home/hooks/use-home-seo.ts`:
- Line 12: Add the explicit void return type to the exported useHomeSeo function
declaration, changing its signature to return void while preserving the hook’s
existing implementation.
In `@web/src/i18n/locales/ja.json`:
- Around line 5230-5294: Translate all invite-rebate entries in
web/src/i18n/locales/ja.json lines 5230-5294 into natural Japanese, replacing
English values while preserving keys and interpolation placeholders. Apply the
same change to web/src/i18n/locales/ru.json lines 5230-5294 with natural Russian
translations; leave the already localized SEO strings unchanged.
In `@web/src/lib/seo/dom.ts`:
- Around line 1-55: Declare an explicit void return type on each exported
helper: upsertMetaByName, upsertMetaByProperty, upsertLinkRel, upsertJsonLd, and
removeJsonLd, without changing their existing behavior.
In `@web/src/routes/__root.tsx`:
- Around line 49-55: Add a type-only ReactElement import and annotate
RootComponent with ReactElement as its explicit return type. Preserve the
existing component implementation, then run the project’s Bun lint and
type-check commands to validate the change.
🪄 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 Plus
Run ID: b501ac9a-bdc3-46f9-ba39-fd35eb2f7fe3
📒 Files selected for processing (64)
common/constants.gocommon/seo.gocontroller/invite_rebate.gocontroller/invite_rebate_task.gocontroller/misc.gocontroller/oauth.gocontroller/seo.gocontroller/system_task.gocontroller/system_task_handlers.gocontroller/topup.gocontroller/user.godocs/superpowers/notes/2026-07-12-invite-topup-rebate-done.mddocs/superpowers/notes/2026-07-12-seo-homepage-done.mddocs/superpowers/plans/2026-07-12-invite-topup-rebate.mddocs/superpowers/plans/2026-07-12-seo-homepage.mddocs/superpowers/specs/2026-07-12-invite-topup-rebate-design.mddocs/superpowers/specs/2026-07-12-seo-homepage-design.mdmodel/invite_rebate.gomodel/invite_rebate_test.gomodel/main.gomodel/option.gomodel/system_task.gomodel/task_cas_test.gomodel/topup.gomodel/user.gorouter/api-router.gorouter/main.goweb/index.htmlweb/src/features/home/hooks/use-home-seo.tsweb/src/features/home/index.tsxweb/src/features/invite-rebate/api.tsweb/src/features/invite-rebate/index.tsxweb/src/features/invite-rebate/types.tsweb/src/features/system-info/components/system-tasks-panel.tsxweb/src/features/system-settings/billing/index.tsxweb/src/features/system-settings/billing/section-registry.tsxweb/src/features/system-settings/general/quota-settings-section.tsxweb/src/features/system-settings/general/system-info-section.tsxweb/src/features/system-settings/hooks/use-update-option.tsweb/src/features/system-settings/site/index.tsxweb/src/features/system-settings/site/section-registry.tsxweb/src/features/system-settings/types.tsweb/src/features/wallet/components/affiliate-rewards-card.tsxweb/src/hooks/use-sidebar-config.tsweb/src/hooks/use-sidebar-data.tsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/i18n/locales/zh.jsonweb/src/i18n/static-keys.tsweb/src/lib/seo/apply.tsweb/src/lib/seo/cached-status.tsweb/src/lib/seo/defaults.tsweb/src/lib/seo/dom.tsweb/src/lib/seo/index.tsweb/src/lib/seo/types.tsweb/src/main.tsxweb/src/routeTree.gen.tsweb/src/routes/__root.tsxweb/src/routes/_authenticated/invite-rebate/admin.tsxweb/src/routes/_authenticated/invite-rebate/index.tsx
| robotsIndex: publicPath ? baseRobots : false, | ||
| path: publicPath ? path : '/', |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve the actual route path for non-marketing pages.
Rewriting every non-marketing path to / makes applyDocumentSeo treat console/auth pages as homepages: it appends the homepage long-tail title and emits the homepage canonical/OG URL. Keep path unchanged; use robotsIndex alone to control indexing.
Proposed fix
- path: publicPath ? path : '/',
+ path,📝 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.
| robotsIndex: publicPath ? baseRobots : false, | |
| path: publicPath ? path : '/', | |
| robotsIndex: publicPath ? baseRobots : false, | |
| path, |
🤖 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 `@web/src/lib/seo/apply.ts` around lines 154 - 155, Update the SEO options in
applyDocumentSeo so the path field always preserves the actual path, including
for non-marketing pages, instead of defaulting to '/'. Leave robotsIndex as the
sole control for indexing behavior and preserve the existing publicPath
handling.
…a validation, docs paths - Wrap epay topUp.Update() and quota increase in a single DB transaction so order is not eligible for backfill until both succeed; rebate grant happens only after transaction commits - Remove meaningless lastmod (today's date) from sitemap entries - Add z.string().url() validation to SEO.SiteURL and SEO.OGImage fields - Update web/default/src and web/classic/src paths to web/src in docs - Add blank line before table in SEO done note (MD058) - Sync invite-rebate spec: remove backfill from non-goals, document granted/skipped statuses and permanent skip rows - Fix expected 1% result from 50 to 5000 in rebate plan test Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/features/system-settings/general/system-info-section.tsx (1)
97-99: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the default-enabled robots setting.
When the option is absent, this expression normalizes it to
false, while the browser SEO layer treats a missingseo_robots_indexas enabled. The form therefore displays indexing as disabled for the default configuration.Normalize only an explicit
false/'false'as disabled; treat missing values astrue.🤖 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 `@web/src/features/system-settings/general/system-info-section.tsx` around lines 97 - 99, Update the 'SEO.RobotsIndex' normalization in the system information form to return false only when defaultValues explicitly contains false or 'false'; treat missing or other values as true so the default-enabled robots setting is preserved.
🧹 Nitpick comments (1)
web/src/features/system-settings/general/system-info-section.tsx (1)
91-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the
anycasts from SEO fields.These casts bypass type checking for both default access and every SEO
FormFieldname. Define a typed field-path contract instead, so renames and schema mismatches are caught at compile time.Also applies to: 298-298, 324-324, 350-350, 375-375, 398-398, 422-422, 446-446
🤖 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 `@web/src/features/system-settings/general/system-info-section.tsx` around lines 91 - 96, Replace the any casts used for SEO default-value access and all SEO FormField names in the system info section with a typed field-path contract derived from the form schema. Update the affected SEO fields, including SEO.Title, SEO.TitleSuffix, SEO.Description, SEO.Keywords, SEO.SiteURL, and SEO.OGImage, so both default access and field names are compile-time validated.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 `@controller/topup.go`:
- Around line 402-405: Replace the raw quota Update in the top-up transaction
with a transaction-compatible checked quota helper that enforces the quota/clamp
contract and returns clamp information. Ensure quota-cache synchronization or
invalidation occurs only after the transaction commits, and propagate the
saturation result to call attachQuotaSaturation before writing any consume or
task logs.
- Around line 398-407: The transaction around saving topUp must atomically
update the order only when its current status is pending, rather than using an
unconditional tx.Save. Check the conditional update’s RowsAffected and treat
zero rows as an already-processed callback, ensuring quota crediting and the
log/rebate side effects execute only for the transaction that successfully
transitions the order from pending to success.
In `@web/src/features/system-settings/general/system-info-section.tsx`:
- Around line 119-120: Update the SEO.OGImage schema entry in the system-info
validation definition to accept empty values and paths beginning with “/”,
instead of requiring a full URL. Leave the SEO.SiteURL validation unchanged and
preserve rejection of other invalid OG image values.
---
Outside diff comments:
In `@web/src/features/system-settings/general/system-info-section.tsx`:
- Around line 97-99: Update the 'SEO.RobotsIndex' normalization in the system
information form to return false only when defaultValues explicitly contains
false or 'false'; treat missing or other values as true so the default-enabled
robots setting is preserved.
---
Nitpick comments:
In `@web/src/features/system-settings/general/system-info-section.tsx`:
- Around line 91-96: Replace the any casts used for SEO default-value access and
all SEO FormField names in the system info section with a typed field-path
contract derived from the form schema. Update the affected SEO fields, including
SEO.Title, SEO.TitleSuffix, SEO.Description, SEO.Keywords, SEO.SiteURL, and
SEO.OGImage, so both default access and field names are compile-time validated.
🪄 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 Plus
Run ID: bb31e385-393e-461c-a55d-f0bbfe8c91e5
📒 Files selected for processing (8)
controller/seo.gocontroller/topup.godocs/superpowers/notes/2026-07-12-invite-topup-rebate-done.mddocs/superpowers/notes/2026-07-12-seo-homepage-done.mddocs/superpowers/plans/2026-07-12-invite-topup-rebate.mddocs/superpowers/plans/2026-07-12-seo-homepage.mddocs/superpowers/specs/2026-07-12-invite-topup-rebate-design.mdweb/src/features/system-settings/general/system-info-section.tsx
💤 Files with no reviewable changes (1)
- controller/seo.go
🚧 Files skipped from review as they are similar to previous changes (4)
- docs/superpowers/notes/2026-07-12-invite-topup-rebate-done.md
- docs/superpowers/plans/2026-07-12-invite-topup-rebate.md
- docs/superpowers/plans/2026-07-12-seo-homepage.md
- docs/superpowers/specs/2026-07-12-invite-topup-rebate-design.md
| if txErr := tx.Model(&model.User{}).Where("id = ?", topUp.UserId). | ||
| Update("quota", gorm.Expr("quota + ?", quotaToAdd)).Error; txErr != nil { | ||
| return txErr | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use the checked quota mutation path instead of a raw update.
This bypasses the quota-cache synchronization performed by model.IncreaseUserQuota, so cache-backed balance checks can retain the pre-top-up quota after commit. It also bypasses the required checked quota/clamp contract. Add a transaction-compatible checked helper, then synchronize or invalidate cache only after commit.
As per coding guidelines, “Billing paths must use checked quota helpers, propagate clamp information, and call attachQuotaSaturation before writing consume or task logs so saturation is auditable.”
🤖 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 `@controller/topup.go` around lines 402 - 405, Replace the raw quota Update in
the top-up transaction with a transaction-compatible checked quota helper that
enforces the quota/clamp contract and returns clamp information. Ensure
quota-cache synchronization or invalidation occurs only after the transaction
commits, and propagate the saturation result to call attachQuotaSaturation
before writing any consume or task logs.
Source: Coding guidelines
Upstream now settles epay top-ups atomically via model.RechargeEpay (row lock + in-transaction status check + QuotaFromDecimalStrict + post-commit cache sync), so the branch's hand-rolled transaction in controller/topup.go is dropped in favour of it. The invite rebate hook moves to model.RechargeEpay, fired post-commit like the other providers. Conflicts: - controller/topup.go: take upstream EpayNotify wholesale - model/topup.go: keep rebate hook, adopt upstream int quota type - router/api-router.go: keep upstream aff_transfer rate limit + new routes Co-authored-by: Cursor <cursoragent@cursor.com>
The SEO work carried a fork's product name into upstream defaults (SystemName, default keywords, index.html title/OG/Twitter tags and document-title fallbacks) and flipped the shell to lang="zh-CN". Restore "New API" everywhere and keep the shell English by default; the configurable SEO layer is what operators should use to rebrand. Also drop the static og:url: a relative value is not a valid OG URL and the runtime layer sets the real absolute one. Co-authored-by: Cursor <cursoragent@cursor.com>
- Insert ledger rows with ON CONFLICT (topup_id) DO NOTHING and branch on RowsAffected. Reacting to a unique violation after Create is unusable on PostgreSQL, where the violation aborts the surrounding transaction. - Re-read the inviter under lockForUpdate inside the grant transaction and compute the credit with common.QuotaFromFloatChecked. aff_quota/aff_history are int32 columns, so a rebate near the ceiling lands only in part; the ledger now stores what was really credited (zero, recorded as skipped, when fully capped) and the clamp is logged. - Drop the manual ledger delete when the inviter update affects no rows. Returning an error rolls the whole transaction back instead of committing a granted row the inviter never received, and frees topup_id for a retry. - Reset and persist InviteTopupRebateEnabledAt on every off->on transition, before publishing the enabled flag, and propagate the save failure. Keeping the old stamp let backfill reward top-ups completed while the feature was off; ignoring the failure let nodes disagree on the cutoff. - Propagate errors from the invitee-stats and leaderboard-rank queries instead of reporting fabricated zero totals or rank 1. Co-authored-by: Cursor <cursoragent@cursor.com>
- invite-rebate page: Promise.allSettled reports {success:false} as fulfilled,
so a failed summary silently rendered zero balances and "Rebate disabled".
Business errors are now detected explicitly; a failed summary shows an error
card with retry, and the secondary sections raise one aggregated toast.
- invite-rebate admin: split draft inputs from applied filters so reload no
longer fires on every keystroke, where out-of-order responses could replace
the table with results for an older partial ID. Enter also applies.
- seo/apply.ts: keep the real route path instead of rewriting non-marketing
paths to '/'. Rewriting made console/auth pages emit the homepage
canonical/OG URL; robotsIndex alone controls indexing.
- system-info-section: accept site-root-relative OG image paths (the field's
own placeholder is /logo.png), and treat a missing SEO.RobotsIndex option as
enabled to match the browser SEO layer instead of showing it as disabled.
- Fix a pre-existing frontend typecheck failure: SiteSettings types
SEO.RobotsIndex as boolean|string, which the form props rejected.
Minor review follow-ups: drop the duplicate "Legal" locale key that silently
overrode the existing translation in all seven locales, require integers for
the rebate ratio/backfill inputs, translate the admin ID column, un-clamp the
rebate details link, and add the missing explicit return types.
Co-authored-by: Cursor <cursoragent@cursor.com>
docs/superpowers/{specs,plans,notes} are dated private workflow artifacts
(design scratch, task checklists, a "ready for next feature" log, local
debugging notes). docs/ upstream is user-facing documentation, so these do
not belong here and account for roughly a third of this PR's diff.
This also retires the review comments about stale web/default and web/classic
paths and the markdownlint MD058 warning inside those files.
Co-authored-by: Cursor <cursoragent@cursor.com>
All 43 new invite-rebate keys were shipped with their English source text as the translated value in these four locales, while the SEO keys added in the same block were localized. Interpolation placeholders are preserved. Co-authored-by: Cursor <cursoragent@cursor.com>
Review follow-up: conflicts resolved, findings addressed
New commits: Conflict resolutionThree files conflicted: The important one is
Inline comments
Outside-diff comments
Minor and nitpick comments
Two things I changed on my own initiativeRemoved a fork's branding that had leaked into upstream defaults. Removed Verification
One note: Questions for maintainers
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
web/src/features/invite-rebate/index.tsx (1)
693-700: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winHandle fulfilled API failures in the admin reload path.
Both calls return
ApiResponse. A response withsuccess: falsedoes not entercatch. The page can then retain stale rows or show zero totals without an error. Handle each unsuccessful response and show its message before ending the loading state.🤖 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 `@web/src/features/invite-rebate/index.tsx` around lines 693 - 700, Update the reload flow containing fetchAdminInviteRebateSummary and fetchAdminInviteRebates to handle fulfilled responses with success set to false: display each response’s error message through the page’s existing error-notification mechanism, rather than leaving stale summary or rows. Preserve successful updates, and ensure loading state cleanup still runs after either API failure.web/src/lib/seo/apply.ts (1)
157-170: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winEnforce
noindexafter applyingextra.
...extracan replacerobotsIndex: falsewithtrue. The current condition then preserves that value for non-public routes. For example,applySeoFromStatus(status, { path: '/sign-in', robotsIndex: true })emitsindex,follow, despite the function contract.Set
input.robotsIndex = falsefor every non-public route after the spread.Proposed fix
- if (!isPublicMarketingPath(finalPath) && extra?.robotsIndex === undefined) { + if (!isPublicMarketingPath(finalPath)) { input.robotsIndex = 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 `@web/src/lib/seo/apply.ts` around lines 157 - 170, Update the robotsIndex enforcement in the apply SEO flow after the extra spread so every non-public route unconditionally sets input.robotsIndex to false, removing the extra?.robotsIndex === undefined guard. Preserve the existing public-route behavior.web/src/lib/seo/defaults.ts (2)
30-49: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winOnly suppress the suffix when the title already contains the suffix.
At Line 47,
suffix.includes(name)treats a title contained inside the suffix as a duplicate. For example,name = "API"andsuffix = "AI API Gateway"returns only"API"and drops the suffix.Remove the reverse containment check.
Proposed fix
- if (name.includes(suffix) || suffix.includes(name)) return name + if (name.includes(suffix)) return name🤖 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 `@web/src/lib/seo/defaults.ts` around lines 30 - 49, Update buildDocumentTitle so suffix suppression only occurs when name already contains suffix; remove the reverse suffix.includes(name) check while preserving the existing full-title and normal suffix-appending behavior.
1-15: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winLocalize the default SEO copy.
The default description, keywords, and title suffix support only Chinese and English. The
fr,ru,ja, andvilocales receive English fallback metadata. These values are written into document and social metadata.Use
i18next.twith the requested language in this non-React helper, or add translations for all supported locales.As per coding guidelines, “面向用户的文案必须使用 i18n;React 组件使用
useTranslation()的t(),非 React 代码仅在不需要响应式更新时使用i18next的t。”Also applies to: 17-23
🤖 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 `@web/src/lib/seo/defaults.ts` around lines 1 - 15, Update defaultSeoDescription, defaultSeoKeywords, and the related title-suffix helper to use i18next.t with the requested language instead of hardcoded Chinese/English fallbacks. Add or reuse translation keys covering all supported locales, including fr, ru, ja, and vi, while preserving the existing language selection behavior.Source: Coding guidelines
model/topup.go (1)
224-225: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winRequire exactly one affected user row for every quota update.
Stripe, manual, Creem, Waffo, and Waffo Pancake mark the top-up successful before checking the quota update. A zero-row update can return nil for a missing or soft-deleted user, so the transaction commits without crediting quota. Check both
result.Errorandresult.RowsAffected != 1, includingManualCompleteTopUpatmodel/topup.go:458.🤖 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 `@model/topup.go` around lines 224 - 225, The quota update flows in model/topup.go must require exactly one affected user row: update the Stripe path around the existing User update, the Waffo, Creem, and Waffo Pancake paths at model/topup.go lines 545, 607, and 670, and ManualCompleteTopUp around line 458 to capture the update result, return an error when result.Error is non-nil or result.RowsAffected is not 1, and only allow the top-up success transaction to proceed after this validation.
🧹 Nitpick comments (1)
model/topup.go (1)
554-554: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the bare quota cast.
quotaalready has typeint. Pass it directly toGrantInviteTopupRebate. This keeps the billing path free of direct conversions.- GrantInviteTopupRebate(nil, topUp.UserId, int(quota), topUp) + GrantInviteTopupRebate(nil, topUp.UserId, quota, topUp)As per coding guidelines: “Do not use bare integer casts.”
🤖 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 `@model/topup.go` at line 554, Update the GrantInviteTopupRebate call in the top-up billing path to pass quota directly, removing the unnecessary int conversion while preserving the existing error handling.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 `@model/invite_rebate_test.go`:
- Around line 212-214: Update the expectation in the invite top-up rebate test
to convert the decimal quota through common.QuotaFromDecimalStrict, matching
BackfillMissingInviteTopupRebates instead of using a bare int cast. Preserve the
existing CalculateInviteTopupRebate inputs and handle the helper’s rejection
behavior consistently with production.
In `@web/src/features/invite-rebate/index.tsx`:
- Around line 713-715: Update applyFilters to validate inviterIdDraft and
inviteeIdDraft before calling setFilters: allow blank values, but only accept
positive safe integers for nonblank input. Reject invalid text, NaN, zero,
negatives, decimals, and unsafe integers, and preserve the existing filters when
validation fails so summary and list requests remain consistent.
---
Outside diff comments:
In `@model/topup.go`:
- Around line 224-225: The quota update flows in model/topup.go must require
exactly one affected user row: update the Stripe path around the existing User
update, the Waffo, Creem, and Waffo Pancake paths at model/topup.go lines 545,
607, and 670, and ManualCompleteTopUp around line 458 to capture the update
result, return an error when result.Error is non-nil or result.RowsAffected is
not 1, and only allow the top-up success transaction to proceed after this
validation.
In `@web/src/features/invite-rebate/index.tsx`:
- Around line 693-700: Update the reload flow containing
fetchAdminInviteRebateSummary and fetchAdminInviteRebates to handle fulfilled
responses with success set to false: display each response’s error message
through the page’s existing error-notification mechanism, rather than leaving
stale summary or rows. Preserve successful updates, and ensure loading state
cleanup still runs after either API failure.
In `@web/src/lib/seo/apply.ts`:
- Around line 157-170: Update the robotsIndex enforcement in the apply SEO flow
after the extra spread so every non-public route unconditionally sets
input.robotsIndex to false, removing the extra?.robotsIndex === undefined guard.
Preserve the existing public-route behavior.
In `@web/src/lib/seo/defaults.ts`:
- Around line 30-49: Update buildDocumentTitle so suffix suppression only occurs
when name already contains suffix; remove the reverse suffix.includes(name)
check while preserving the existing full-title and normal suffix-appending
behavior.
- Around line 1-15: Update defaultSeoDescription, defaultSeoKeywords, and the
related title-suffix helper to use i18next.t with the requested language instead
of hardcoded Chinese/English fallbacks. Add or reuse translation keys covering
all supported locales, including fr, ru, ja, and vi, while preserving the
existing language selection behavior.
---
Nitpick comments:
In `@model/topup.go`:
- Line 554: Update the GrantInviteTopupRebate call in the top-up billing path to
pass quota directly, removing the unnecessary int conversion while preserving
the existing error handling.
🪄 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: 5b1018c8-5765-4228-bef5-ce218fa41231
📒 Files selected for processing (34)
common/constants.gocommon/seo.gocontroller/invite_rebate.gocontroller/misc.gocontroller/oauth.gocontroller/system_task.gocontroller/user.gomodel/invite_rebate.gomodel/invite_rebate_test.gomodel/option.gomodel/system_task.gomodel/topup.gomodel/user.gorouter/api-router.goweb/index.htmlweb/src/features/home/hooks/use-home-seo.tsweb/src/features/invite-rebate/index.tsxweb/src/features/system-settings/billing/index.tsxweb/src/features/system-settings/billing/section-registry.tsxweb/src/features/system-settings/general/quota-settings-section.tsxweb/src/features/system-settings/general/system-info-section.tsxweb/src/features/system-settings/types.tsweb/src/features/wallet/components/affiliate-rewards-card.tsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/i18n/locales/zh.jsonweb/src/lib/seo/apply.tsweb/src/lib/seo/defaults.tsweb/src/lib/seo/dom.tsweb/src/routes/__root.tsx
💤 Files with no reviewable changes (2)
- controller/system_task.go
- controller/invite_rebate.go
🚧 Files skipped from review as they are similar to previous changes (24)
- controller/misc.go
- controller/oauth.go
- router/api-router.go
- web/src/features/system-settings/billing/index.tsx
- model/system_task.go
- web/src/features/wallet/components/affiliate-rewards-card.tsx
- controller/user.go
- web/src/features/system-settings/billing/section-registry.tsx
- model/user.go
- web/src/i18n/locales/fr.json
- web/src/routes/__root.tsx
- web/src/i18n/locales/en.json
- web/src/i18n/locales/vi.json
- web/src/features/system-settings/general/quota-settings-section.tsx
- web/src/i18n/locales/zh-TW.json
- common/seo.go
- web/src/features/system-settings/types.ts
- web/src/i18n/locales/ja.json
- web/src/features/home/hooks/use-home-seo.ts
- web/src/i18n/locales/ru.json
- web/src/features/system-settings/general/system-info-section.tsx
- model/option.go
- model/invite_rebate.go
- web/src/lib/seo/dom.ts
| // Amount 10 * QuotaPerUnit * 1% | ||
| expect := CalculateInviteTopupRebate(int(float64(10)*common.QuotaPerUnit), 100) | ||
| assert.Equal(t, expect, inv.AffQuota) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use the production quota conversion contract.
Line 213 truncates through int(float64(...)). BackfillMissingInviteTopupRebates uses common.QuotaFromDecimalStrict, which rounds and rejects saturated values. The test can pass while the backfill result differs when common.QuotaPerUnit has a fractional value.
Proposed fix
- expect := CalculateInviteTopupRebate(int(float64(10)*common.QuotaPerUnit), 100)
+ topupQuota, err := common.QuotaFromDecimalStrict(
+ decimal.NewFromInt(topUp.Amount).Mul(decimal.NewFromFloat(common.QuotaPerUnit)),
+ )
+ require.NoError(t, err)
+ expect := CalculateInviteTopupRebate(topupQuota, 100)As per coding guidelines: “Use centralized quota conversion helpers in common/quota_math.go” and “Do not use bare integer casts.”
📝 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.
| // Amount 10 * QuotaPerUnit * 1% | |
| expect := CalculateInviteTopupRebate(int(float64(10)*common.QuotaPerUnit), 100) | |
| assert.Equal(t, expect, inv.AffQuota) | |
| // Amount 10 * QuotaPerUnit * 1% | |
| topupQuota, err := common.QuotaFromDecimalStrict( | |
| decimal.NewFromInt(topUp.Amount).Mul(decimal.NewFromFloat(common.QuotaPerUnit)), | |
| ) | |
| require.NoError(t, err) | |
| expect := CalculateInviteTopupRebate(topupQuota, 100) | |
| assert.Equal(t, expect, inv.AffQuota) |
🤖 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 `@model/invite_rebate_test.go` around lines 212 - 214, Update the expectation
in the invite top-up rebate test to convert the decimal quota through
common.QuotaFromDecimalStrict, matching BackfillMissingInviteTopupRebates
instead of using a bare int cast. Preserve the existing
CalculateInviteTopupRebate inputs and handle the helper’s rejection behavior
consistently with production.
Source: Coding guidelines
| const applyFilters = useCallback(() => { | ||
| setFilters({ inviterId: inviterIdDraft, inviteeId: inviteeIdDraft }) | ||
| }, [inviteeIdDraft, inviterIdDraft]) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validate ID filters before applying them.
Input accepts arbitrary text. Number('abc') becomes NaN. The summary client treats that value as an absent filter, while the list request receives inviter_id: NaN. This can show an unfiltered summary with different list results. Accept only blank values or positive safe integers before updating filters.
🤖 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 `@web/src/features/invite-rebate/index.tsx` around lines 713 - 715, Update
applyFilters to validate inviterIdDraft and inviteeIdDraft before calling
setFilters: allow blank values, but only accept positive safe integers for
nonblank input. Reject invalid text, NaN, zero, negatives, decimals, and unsafe
integers, and preserve the existing filters when validation fails so summary and
list requests remain consistent.
📝 变更描述 / Description
本次合并包含以下内容:
Upstream 同步
SEO 功能完善
邀请返利维护
文档站优化
i18n
🚀 变更类型 / Type of change
✅ 提交前检查项 / Checklist
此 PR 由 AI 辅助生成代码。
🤖 Generated with Claude Code
Summary by CodeRabbit