Skip to content

feat: SEO improvements, invite rebate fixes, upstream sync - #6531

Open
seven7763 wants to merge 38 commits into
QuantumNous:mainfrom
seven7763:feat/daoxe-customizations
Open

feat: SEO improvements, invite rebate fixes, upstream sync#6531
seven7763 wants to merge 38 commits into
QuantumNous:mainfrom
seven7763:feat/daoxe-customizations

Conversation

@seven7763

@seven7763 seven7763 commented Jul 29, 2026

Copy link
Copy Markdown

📝 变更描述 / Description

本次合并包含以下内容:

Upstream 同步

  • 合并上游 21 个 commit(relaykit 模块拆分、OIDC 自定义名称、CI workflows、per-channel HTTP transport 等)

SEO 功能完善

  • 管理后台 SEO 设置重构为独立 section + RobotsIndex 开关
  • sitemap.xml 增加 lastmod/changefreq/priority + 更多路由
  • robots.txt 增加更多 Disallow 路径(/token /topup /log /setting 等)
  • og:site_name 修复为始终使用品牌短名
  • 保存 SEO 设置后立即刷新前端 status 缓存
  • controller/seo.go 加 mutex 并发保护 + XML 转义
  • 新增 readCachedStatus 共享 helper,去除重复 localStorage 读取

邀请返利维护

  • 系统任务面板增加 invite_rebate_backfill 类型标签和中文翻译

文档站优化

  • 配色统一对齐主站(neutral + 蓝色 primary)
  • 导航栏与主站完全一致(滚动前透明、滚动后胶囊)
  • Hero 组件升级(装饰元素、渐变、更大间距)
  • 卡片 hover 效果增强
  • 代码块行高优化、表格斑马纹
  • 文档助手增加 fenced code block 语法高亮
  • DeepChat URL 修正为 deepchat.thinkinai.xyz
  • 系统公告 Markdown 渲染修复
  • 首页面包屑隐藏

i18n

  • 7 种语言补齐 SEO/任务相关翻译

🚀 变更类型 / Type of change

  • ✨ 新功能 (New feature)
  • 🐛 Bug 修复 (Bug fix)
  • ⚡ 性能优化 / 重构 (Refactor)

✅ 提交前检查项 / Checklist

  • Go build 通过
  • Frontend build 通过
  • 所有测试通过
  • 已部署验证(服务器 152.53.242.23 运行正常)

此 PR 由 AI 辅助生成代码。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added invite top-up rebates with configurable enablement, reward ratios, reporting, leaderboards, logs, and quota transfers.
    • Added user and admin rebate pages, navigation links, and scheduled or on-demand backfill tools.
    • Added configurable SEO settings, dynamic page metadata, social previews, robots.txt, and sitemap.xml support.
  • Bug Fixes
    • Improved affiliate-code handling by trimming, limiting length, and rejecting invalid or disabled inviters.
    • Prevented disabled accounts and invalid amounts from transferring affiliate quota.
  • Localization
    • Added translations for invite rebates and SEO settings across supported languages.

hei and others added 30 commits July 12, 2026 17:35
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>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Invite top-up rebate

Layer / File(s) Summary
Rebate domain and persistence
common/constants.go, model/invite_rebate.go, model/option.go, model/main.go, model/invite_rebate_test.go
Adds configurable rebate settings, cutoff-based eligibility, capped calculations, idempotent ledger processing, summaries, backfill, leaderboards, migrations, and coverage tests.
Top-up and inviter validation
model/topup.go, model/user.go, controller/oauth.go, controller/user.go
Grants rebates after successful top-ups and validates affiliate codes, inviter status, transfer amounts, and account status.
Rebate APIs and tasks
controller/invite_rebate.go, controller/invite_rebate_task.go, controller/system_task.go, router/api-router.go, controller/system_task_handlers.go, model/system_task.go
Adds user/admin endpoints, on-demand task creation, scheduled backfill execution, and task registration.
Rebate frontend
web/src/features/invite-rebate/*, web/src/routes/_authenticated/invite-rebate/*, web/src/routeTree.gen.ts, web/src/hooks/*, web/src/features/wallet/components/affiliate-rewards-card.tsx, web/src/i18n/*
Adds typed API access, user and admin pages, navigation, route guards, settings, task labels, and translations.

Homepage SEO

Layer / File(s) Summary
SEO configuration and crawl endpoints
common/seo.go, controller/misc.go, controller/seo.go, router/main.go, model/option.go
Adds configurable SEO status fields, language-aware defaults, robots.txt, sitemap.xml, and public crawl routes.
Browser SEO utilities and integration
web/src/lib/seo/*, web/src/main.tsx, web/src/routes/__root.tsx, web/src/features/home/*, web/index.html
Adds route-aware document metadata, canonical links, social metadata, JSON-LD, cached-status handling, and homepage SEO lifecycle integration.
SEO administration
web/src/features/system-settings/general/system-info-section.tsx, web/src/features/system-settings/site/*, web/src/features/system-settings/types.ts, web/src/features/system-settings/hooks/use-update-option.ts, web/src/i18n/locales/*
Adds SEO form fields, validation, defaults, status-cache invalidation, and localized labels.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: calcium-ion

Poem

I hop through ledgers, neat and bright,
Rebates bloom from top-ups right.
SEO stars adorn the page,
Crawlers find each public stage.
Settings guide the trail anew—
A rabbit cheers this change for you!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main changes: SEO improvements, invite-rebate updates, and upstream synchronization.
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 unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 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 lift

Make Epay completion and user credit atomic before exposing the order to backfill.

topUp.Update() persists success/completion before IncreaseUserQuota. 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 win

Update the frontend paths in the completion note.

The documented paths use web/default/... and web/classic/..., but the supplied PR file inventory places the implementation under web/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 win

Do 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 lastmod until 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 win

Restore URL validation in the active resolver.

systemInfoSchemaWithI18n is the schema passed to zodResolver, but SEO.SiteURL and SEO.OGImage are only z.string().optional() here while _systemInfoSchema requires z.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 win

Add 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 win

Align SEO documentation with the active frontend layout. Both documents refer to web/default/src/..., while the reviewed implementation is under web/src/....

  • docs/superpowers/plans/2026-07-12-seo-homepage.md#L7-L8: replace the obsolete web/default/src paths throughout the plan.
  • docs/superpowers/notes/2026-07-12-seo-homepage-done.md#L14-L17: record the delivered web/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 win

Synchronize 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: document granted and skipped statuses.
  • 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 from 50 to 5000.
  • 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 win

Propagate 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 win

Invite-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 win

Translate 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 win

Remove the duplicate Legal key.

Legal is 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 win

Avoid redefining the Legal key.

Legal is already defined at Line 2478. This duplicate relies on last-write-wins parser behavior and changes every existing Legal translation 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 win

Remove the duplicate Legal key.

"Legal" already exists at Line 2478. Keeping both keys makes the parsed locale depend on duplicate-key behavior and causes all existing Legal usages 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 win

Reject fractional basis points and backfill intervals.

Both settings represent integers, but the schemas and inputs accept decimals. Add .int() and step={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 win

Keep 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 win

Translate the admin table’s ID heading.

-                      <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 win

Add the hook’s explicit return type.

Use export function useHomeSeo(): void to 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 win

Declare the exported helper return types.

All changed helpers have typed parameters but omit their void return type. Add : void consistently.

🤖 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 win

Add an explicit component return type.

RootComponent is changed TSX code but has no return annotation. Add a type-only ReactElement import 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 win

Invite-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

📥 Commits

Reviewing files that changed from the base of the PR and between 66ee6b8 and 840eff4.

📒 Files selected for processing (64)
  • common/constants.go
  • common/seo.go
  • controller/invite_rebate.go
  • controller/invite_rebate_task.go
  • controller/misc.go
  • controller/oauth.go
  • controller/seo.go
  • controller/system_task.go
  • controller/system_task_handlers.go
  • controller/topup.go
  • controller/user.go
  • docs/superpowers/notes/2026-07-12-invite-topup-rebate-done.md
  • docs/superpowers/notes/2026-07-12-seo-homepage-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
  • docs/superpowers/specs/2026-07-12-seo-homepage-design.md
  • model/invite_rebate.go
  • model/invite_rebate_test.go
  • model/main.go
  • model/option.go
  • model/system_task.go
  • model/task_cas_test.go
  • model/topup.go
  • model/user.go
  • router/api-router.go
  • router/main.go
  • web/index.html
  • web/src/features/home/hooks/use-home-seo.ts
  • web/src/features/home/index.tsx
  • web/src/features/invite-rebate/api.ts
  • web/src/features/invite-rebate/index.tsx
  • web/src/features/invite-rebate/types.ts
  • web/src/features/system-info/components/system-tasks-panel.tsx
  • web/src/features/system-settings/billing/index.tsx
  • web/src/features/system-settings/billing/section-registry.tsx
  • web/src/features/system-settings/general/quota-settings-section.tsx
  • web/src/features/system-settings/general/system-info-section.tsx
  • web/src/features/system-settings/hooks/use-update-option.ts
  • web/src/features/system-settings/site/index.tsx
  • web/src/features/system-settings/site/section-registry.tsx
  • web/src/features/system-settings/types.ts
  • web/src/features/wallet/components/affiliate-rewards-card.tsx
  • web/src/hooks/use-sidebar-config.ts
  • web/src/hooks/use-sidebar-data.ts
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/i18n/locales/zh.json
  • web/src/i18n/static-keys.ts
  • web/src/lib/seo/apply.ts
  • web/src/lib/seo/cached-status.ts
  • web/src/lib/seo/defaults.ts
  • web/src/lib/seo/dom.ts
  • web/src/lib/seo/index.ts
  • web/src/lib/seo/types.ts
  • web/src/main.tsx
  • web/src/routeTree.gen.ts
  • web/src/routes/__root.tsx
  • web/src/routes/_authenticated/invite-rebate/admin.tsx
  • web/src/routes/_authenticated/invite-rebate/index.tsx

Comment thread model/invite_rebate.go Outdated
Comment thread model/invite_rebate.go Outdated
Comment thread model/invite_rebate.go Outdated
Comment thread model/option.go
Comment thread web/src/features/invite-rebate/index.tsx Outdated
Comment thread web/src/features/invite-rebate/index.tsx
Comment thread web/src/lib/seo/apply.ts Outdated
Comment on lines +154 to +155
robotsIndex: publicPath ? baseRobots : false,
path: publicPath ? path : '/',

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.

🎯 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.

Suggested change
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>

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

Preserve the default-enabled robots setting.

When the option is absent, this expression normalizes it to false, while the browser SEO layer treats a missing seo_robots_index as enabled. The form therefore displays indexing as disabled for the default configuration.

Normalize only an explicit false/'false' as disabled; treat missing values as true.

🤖 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 win

Remove the any casts from SEO fields.

These casts bypass type checking for both default access and every SEO FormField name. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 840eff4 and d7e496d.

📒 Files selected for processing (8)
  • controller/seo.go
  • controller/topup.go
  • docs/superpowers/notes/2026-07-12-invite-topup-rebate-done.md
  • docs/superpowers/notes/2026-07-12-seo-homepage-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
  • web/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

Comment thread controller/topup.go Outdated
Comment thread controller/topup.go Outdated
Comment on lines +402 to +405
if txErr := tx.Model(&model.User{}).Where("id = ?", topUp.UserId).
Update("quota", gorm.Expr("quota + ?", quotaToAdd)).Error; txErr != nil {
return txErr
}

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.

🗄️ 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

Comment thread web/src/features/system-settings/general/system-info-section.tsx Outdated
seven7763 and others added 6 commits August 13, 2026 02:02
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>
@seven7763

Copy link
Copy Markdown
Author

Review follow-up: conflicts resolved, findings addressed

main has been merged in (no rebase, so no force-push), the branch is mergeable again, and every CodeRabbit finding is either fixed or answered below. The PR also shrank from 64 files / +6098 to 57 files / +4229 — details at the end.

New commits: f674bbf (merge) · 578b4c2 (branding) · ea93d87 (rebate accounting) · 99eb9b5 (frontend) · 146a066 (docs removal) · 1949a2a (i18n).

Conflict resolution

Three files conflicted: controller/topup.go, model/topup.go, router/api-router.go.

The important one is controller/topup.go. Since this branch was cut, upstream landed fix(topup): settle recharge orders atomically (50e5377) and fix: harden concurrent quota and status updates (ccd535e), which introduce model.RechargeEpay: row lock, in-transaction pending-status check with RowsAffected, common.QuotaFromDecimalStrict, and syncCreditUserQuotaCache after commit. I dropped this branch's hand-rolled transaction entirely and took upstream's version, then moved the rebate hook into model.RechargeEpay right after the commit, matching how the Stripe/Creem/Waffo paths already call it.

model/topup.go keeps the rebate hook and adopts upstream's int quota type; router/api-router.go keeps upstream's UserCriticalRateLimit("aff-transfer") alongside the new routes.

Inline comments

# Finding Resolution
1 model/invite_rebate.go:105 — use conflict-ignore insertion Fixed. Both the skip and grant inserts now go through insertInviteRebateLedger, which uses clause.OnConflict{Columns: topup_id, DoNothing: true} and branches on RowsAffected. isDuplicateKeyError is deleted. You were right about PostgreSQL: the violation aborts the transaction, so the credit that follows could never have run.
2 model/invite_rebate.go:288-340 — record the amount actually credited Fixed. The grant transaction now re-reads the inviter under lockForUpdate and computes the credit with common.QuotaFromFloatChecked, per the AGENTS.md checked-helper rule. The ledger stores the real credited amount; a fully saturated grant stores 0 with status=skipped, the clamp is logged, and RecordLog reports the credited value rather than the requested one. Regression test: TestGrantInviteTopupRebate_LedgerRecordsSaturatedCredit (partial and full saturation).
3 model/invite_rebate.go:309-312 — return a failed ledger cleanup Fixed, differently than suggested. Because the inviter is now re-read and status-checked under a row lock inside the same transaction, the "disabled between read and update" race is gone, so the manual Delete is removed rather than error-checked. A non-1 RowsAffected now returns an error, and the rollback removes the ledger row and frees topup_id for a retry. Happy to switch to the literal suggested diff if you prefer keeping the delete path.
4 model/option.go:333-349 — reset and persist the cutoff Fixed. Every off→on transition now stamps a fresh timestamp, DB.Save runs before the in-memory flag is published, and its error is returned from updateOptionMap instead of discarded.
5 web/src/features/invite-rebate/index.tsx:125-138Promise.allSettled business errors Fixed. { success: false } is handled explicitly. A failed summary now renders an error card with a retry button instead of zero balances and "Rebate disabled". Logs/invitees/affiliate-code failures raise a single aggregated toast — four separate toasts during a full outage seemed worse than one.
6 web/src/features/invite-rebate/index.tsx:645-674 — filtering on every keystroke Fixed. Draft inputs (inviterIdDraft / inviteeIdDraft) are separate from the applied filters object that reload depends on. Only the Filter button (and Enter) copies draft into applied.
7 web/src/lib/seo/apply.ts:154-155 — preserve the real route path Fixed. path is passed through unchanged; robotsIndex alone controls indexing. Console/auth pages now emit their own canonical/OG URL with noindex,nofollow, and the existing post-spread guard still strips the long-tail title off non-home routes.
8 controller/topup.go:398-407 — database-idempotent pending→success Superseded by the merge. model.RechargeEpay does the conditional update under a row lock and returns alreadyDone for duplicate callbacks.
9 controller/topup.go:402-405 — checked quota path, cache sync Superseded by the merge. model.RechargeEpay uses QuotaFromDecimalStrict and calls syncCreditUserQuotaCache after commit.
10 system-info-section.tsx:119-120 — relative OG image paths Fixed. SEO.OGImage now validates through isOgImageValue (empty, /-prefixed, or a parseable absolute URL) in both schemas — the field's own placeholder is /logo.png. Added the error string to all seven locales.

Outside-diff comments

Finding Resolution
controller/topup.go:391-413 — atomic completion + credit before backfill sees the order Superseded by the merge, same as 8/9: status and quota commit together, and the rebate grant fires post-commit.
system-info-section.tsx:97-99 — preserve the default-enabled robots setting Fixed. Only an explicit false / 'false' normalizes to disabled; a missing option is treated as enabled, matching the browser SEO layer.

Minor and nitpick comments

  • Fixed: invitee-stats and leaderboard-rank queries now propagate errors instead of returning fabricated zero totals or rank 1 (invite_rebate.go, both sites) · duplicate Legal key removed — this was present in all seven locales, not just the three flagged, and was silently overriding the existing translation · .int() plus step={1} on the rebate ratio and backfill interval · <TableHead>{t('ID')}</TableHead> · line-clamp-1 removed so the rebate details link stays clickable · explicit return types on useHomeSeo, the five dom.ts helpers, and RootComponent.
  • Translated: all 43 invite-rebate strings in fr, ru, ja and vi were shipping their English source text as the value. Now localized, with {{rate}} placeholders preserved and verified.
  • Already fixed in d7e496d: sitemap lastmod removal, and the systemInfoSchemaWithI18n URL-validation parity.
  • Docs comments retired: see below.
  • Not done — any casts in system-info-section.tsx. I removed the casts on default-value access (the props type now models the option API's boolean | string), but kept name={'SEO.OGImage' as any} on the FormFields. React Hook Form's FieldPath reads a dotted key as a nested path, so a typed field-path contract means either renaming the option keys or adding an adapter — more churn than this PR should carry. Happy to do it in a follow-up if you want it.

Two things I changed on my own initiative

Removed a fork's branding that had leaked into upstream defaults. common.SystemName, the default SEO keywords, the index.html title / OG / Twitter tags and the document-title fallbacks all carried a downstream product name, and the shell had been switched to lang="zh-CN". All restored to New API and English. This conflicted with the protected-project-information rule in AGENTS.md and should not have been in the PR — operators who want to rebrand have the configurable SEO layer this PR adds. I also dropped the static og:url, since a relative value is not a valid OG URL and the runtime layer sets the real absolute one.

Removed docs/superpowers/** (6 files, ~2000 lines). These were dated private planning artifacts — design scratch, task checklists, a "ready for next feature" log, local debugging notes. docs/ upstream is user-facing documentation, so they do not belong here, and they were roughly a third of the diff. This also retires the four review comments about stale web/default / web/classic paths and the MD058 warning inside them.

Verification

go vet ./... and go build ./... clean on both modules · make test passes · bun run typecheck passes · bun test unchanged from main on my machine (the same 9 pre-existing failures appear on a clean main checkout, seemingly a local Bun version difference — CI pins 1.3.14) · gofmt drift this branch had introduced in six files is fixed.

One note: bun run typecheck was failing on this branch before these changes. SiteSettings types SEO.RobotsIndex as boolean | string, which SystemInfoSectionProps rejected. Fixed in 99eb9b5; the frontend CI job would have failed otherwise.

Questions for maintainers

  1. Ledger vocabulary for a saturated grant. When the inviter's aff_quota is at the int32 ceiling, I record status=skipped, rebate_quota=0 so totals stay honest and backfill stops rescanning. Would you rather have a distinct status (e.g. capped) to keep skipped meaning "not eligible"? It is a one-line change plus a migration note.
  2. Where the rebate hook belongs. I put GrantInviteTopupRebate inside model.RechargeEpay after commit, next to RecordTopupLog, so all payment providers hook it the same way. If you would rather keep model/topup.go free of feature hooks, RechargeEpay could return the settled *TopUp and the controller could call it instead.
  3. Scope. This is still two independent features (invite top-up rebate, configurable SEO) in one PR. If that is a barrier, I am happy to split it into two — say the word and I will open them separately rather than have this sit.
  4. CI has not run. The workflow appears to need maintainer approval for a fork PR; the checks above are from running the same commands locally.

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

Handle fulfilled API failures in the admin reload path.

Both calls return ApiResponse. A response with success: false does not enter catch. 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 win

Enforce noindex after applying extra.

...extra can replace robotsIndex: false with true. The current condition then preserves that value for non-public routes. For example, applySeoFromStatus(status, { path: '/sign-in', robotsIndex: true }) emits index,follow, despite the function contract.

Set input.robotsIndex = false for 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 win

Only 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" and suffix = "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 win

Localize the default SEO copy.

The default description, keywords, and title suffix support only Chinese and English. The fr, ru, ja, and vi locales receive English fallback metadata. These values are written into document and social metadata.

Use i18next.t with the requested language in this non-React helper, or add translations for all supported locales.

As per coding guidelines, “面向用户的文案必须使用 i18n;React 组件使用 useTranslation()t(),非 React 代码仅在不需要响应式更新时使用 i18nextt。”

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 win

Require 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.Error and result.RowsAffected != 1, including ManualCompleteTopUp at model/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 win

Remove the bare quota cast.

quota already has type int. Pass it directly to GrantInviteTopupRebate. 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

📥 Commits

Reviewing files that changed from the base of the PR and between d7e496d and 1949a2a.

📒 Files selected for processing (34)
  • common/constants.go
  • common/seo.go
  • controller/invite_rebate.go
  • controller/misc.go
  • controller/oauth.go
  • controller/system_task.go
  • controller/user.go
  • model/invite_rebate.go
  • model/invite_rebate_test.go
  • model/option.go
  • model/system_task.go
  • model/topup.go
  • model/user.go
  • router/api-router.go
  • web/index.html
  • web/src/features/home/hooks/use-home-seo.ts
  • web/src/features/invite-rebate/index.tsx
  • web/src/features/system-settings/billing/index.tsx
  • web/src/features/system-settings/billing/section-registry.tsx
  • web/src/features/system-settings/general/quota-settings-section.tsx
  • web/src/features/system-settings/general/system-info-section.tsx
  • web/src/features/system-settings/types.ts
  • web/src/features/wallet/components/affiliate-rewards-card.tsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/i18n/locales/zh.json
  • web/src/lib/seo/apply.ts
  • web/src/lib/seo/defaults.ts
  • web/src/lib/seo/dom.ts
  • web/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

Comment on lines +212 to +214
// Amount 10 * QuotaPerUnit * 1%
expect := CalculateInviteTopupRebate(int(float64(10)*common.QuotaPerUnit), 100)
assert.Equal(t, expect, inv.AffQuota)

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.

🎯 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.

Suggested change
// 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

Comment on lines +713 to +715
const applyFilters = useCallback(() => {
setFilters({ inviterId: inviterIdDraft, inviteeId: inviteeIdDraft })
}, [inviteeIdDraft, inviterIdDraft])

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.

🎯 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.

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