Skip to content

Affiliate, Referral & Growth Rewards — Screen Review

Status: 🟢 All findings resolved (revalidated 2026-07-19) · Last reviewed: 2026-07-19 (reviewed + revalidated same day)

The first review of the module generated from the Affiliate Program spec and ADR-0005. Five files were fetched and read in full via DesignSync, plus the manifest, on 2026-07-19:

FileRoleSize
ui_kits/merchant-console-v2/manifest.jsnav/entitlement registry (the one code change the revision hinged on)read
prototype/admin-panel/Affiliates.dc.htmlAdmin command center (Part 1)111.6 KB
prototype/mobile-console/InviteEarn.dc.htmlTier 0 "Invite & Earn" (Part 2A)read
prototype/mobile-console/Loyalty.dc.htmlTier 1 Partner (Part 2B)read
prototype/mobile-console/Home.dc.htmlnon-destructive entry-point integrationread

Not yet fetched: the desktop equivalents ui_kits/merchant-console-v2/invite-earn.html and loyalty.html (see the last finding).

Confirmed compliant — the architecture-critical parts all landed

The single most important correction this revision required — the manifest gate — is done, and done better than the spec asked:

  • { id: 'invite', label: 'Invite & Earn', group: 'grow', appliesTo: 'both' } — the new Tier-0 module carries no plan gate, so Free users see it (its entire purpose as a free-to-paid lever), and appliesTo: 'both' exposes it to individuals too, honoring the locked dual-audience decision. The spec only asked for "ungated"; the implementation correctly generalized it to individuals as well.
  • { id: 'loyalty', label: 'Loyalty & referrals', group: 'customers', appliesTo: 'business', plan: 'pro' } — the Tier-1 partner slot kept its plan:'pro' gate, exactly as specified.

Everything else load-bearing checks out:

  • Two tiers, one backbone, cleanly separated. Tier 0 rewards are non-cash only — the InviteEarn "How rewards work" note states "Credits apply to your next QR setu bill" (i.e. subscription_credit, not a cash payout), plus Pro-unlock and badges. Tier 1 is the cash tier — commission ledger, pending/paid, "Request payout," "Monthly payouts to your UPI or bank." No cash-payout surface leaked into Tier 0.
  • dsSelect used, zero native <select>. The admin file uses dsSelect ×9 and has 0 native <select> elements — the recurring admin-screen violation flagged on AdManager (4 native selects) was not repeated.
  • Gamification scoped as requested. Milestones + badges only; an in-code comment explicitly notes "In-scope mechanics: milestones + badges only." Points/streaks/leaderboards were correctly left out and flagged rather than built as placeholders (per Part 3.8 of the brief).
  • Illustrative values flagged on every surface ("Example values shown here are illustrative and set by the platform team" / "Rates and tier thresholds shown are illustrative").
  • Non-destructive Home integration. The existing "Share my card" CTA (→ ServiceCard.dc.html) is intact; a new "Invite & Earn" card was added directly beneath it (→ InviteEarn.dc.html), not in place of it.
  • State coverage. InviteEarn: default / empty / loading / error. Loyalty: default / empty / loading / error / locked (a proper Pro upsell — "Upgrade to Pro / Included with Pro and Business plans").

Finding 1 — premium/tier colors are hardcoded hex (root cause: the DS has no tier tokens) — 🟢 resolved

Severity: minor (design-fixable) · same class as the Templates Custom-HTML regression · 🟢 resolved 2026-07-19 (see Revalidation)

Raw hex colors appear wherever a "premium" or "performance tier" look is wanted, because no design token exists for those hues:

  • Loyalty.dc.html commission hero: linear-gradient(150deg,#1e2540,#3a2a4d) — a bespoke dark navy/purple, no token, no var(…, #hex) fallback wrapper. (InviteEarn's hero correctly used var(--accent),var(--coral-500).)
  • Affiliates.dc.html Bronze/Silver tier medals: #c8825a (bronze), #e7ebef / #9aa6b2 (silver), #f3e3d6 / #e7ebef (tints) — hardcoded metallic hues.

Everything else is clean: the only other hex are var(--danger-soft,#fee2e2) fallbacks (the sanctioned pattern) and #fff for foreground-on-accent (house style across every mobile-console screen — not a regression).

The right fix is not a one-off hex swap. It's a design-system gap: there is no metallic/tier palette in the token files, so every tier surface will keep reinventing these hexes. Add tier tokens once and reference them.

Design-fixable — copy-paste prompt for Claude Designs

markdown
In this project, the affiliate/growth screens hardcode hex colors for "premium" and "performance tier"
surfaces because the design system has no token for them:

- ui_kits/merchant-console-v2/loyalty.html and prototype/mobile-console/Loyalty.dc.html: the commission hero
  uses linear-gradient(150deg,#1e2540,#3a2a4d) with raw hex.
- prototype/admin-panel/Affiliates.dc.html: Bronze/Silver tier medals use #c8825a, #e7ebef, #9aa6b2, #f3e3d6.

Please (1) add tier/premium color tokens to the design-system token files (e.g. --tier-bronze, --tier-silver,
--tier-gold, and a --partner-hero-from / --partner-hero-to pair for the dark commission hero), defined for both
light and dark themes, and (2) replace the hardcoded hex in all affiliate screens (mobile + desktop) with those
tokens. Do not change the visual appearance — pick token values that match the current colors. Confirm zero raw
hex remains outside of var(--token, #fallback) fallbacks and #fff-on-accent.

Finding 2 — an em-dash in InviteEarn UI copy — 🟢 resolved

Severity: minor (design-fixable) · same prose-rule class as the Templates regressions · 🟢 resolved 2026-07-19

InviteEarn.dc.html renders the progress label as "<n> of <need> joined — <remaining> more to unlock" — a literal em dash () in user-facing copy. (Loyalty and the admin file are clean: 0 em dashes.)

Design-fixable — copy-paste prompt for Claude Designs

markdown
In prototype/mobile-console/InviteEarn.dc.html, the milestone progress label uses an em dash: "<n> of <need>
joined — <remaining> more to unlock". Per this project's prose rules, do not use em dashes in UI copy. Rewrite
without the em dash (e.g. "<n> of <need> joined · <remaining> more to unlock" using a middle dot, or split into
two clauses). Check the desktop invite-earn.html for the same string and fix it there too.

Finding 3 — Tier-1 "pending approval" enrollment state is missing — 🟢 resolved

Severity: minor (design-fixable) · a spec item (Part 2B) not fully covered · 🟢 resolved 2026-07-19

Loyalty.dc.html moves from locked (needs Pro) straight to the enrolled/ready dashboard. Part 2B of the brief called for the application lifecycle: not-enrolled → pending (awaiting admin approval) → approved. The admin side has the approval queue (Part 1 Tab 2), so a partner who has applied but isn't yet approved currently has no screen state — the two sides don't meet. The empty state even pre-assumes approval ("You're an approved partner…").

Design-fixable — copy-paste prompt for Claude Designs

markdown
In prototype/mobile-console/Loyalty.dc.html (and the desktop loyalty.html), add a "pending" demoState between
locked and the enrolled dashboard: a Pro user who has applied to the Partner program but is awaiting admin
approval. Show the program explanation, a clear "Application received — we'll notify you once it's approved"
message (not a dead end), and no referral link/ledger yet (those appear only after approval). This matches the
admin approval queue in Affiliates.dc.html Tab 2. Add "pending" to the demoState enum options.

Finding 4 — desktop counterparts not yet verified — 🟢 resolved

Severity: informational · 🟢 verified 2026-07-19

The desktop console renders its screens from ui_kits/merchant-console-v2/modules.jsx (the .html files are thin React shells); that file was read during revalidation. It carries 0 of the flagged raw hex, 0 em dashes, and does implement the Tier-1 pending state ("Application received / reviewing your"). Desktop is at parity with the fixed mobile screens.

Revalidation — 2026-07-19

Re-fetched InviteEarn.dc.html, Loyalty.dc.html, Affiliates.dc.html, the desktop modules.jsx, and the DS token file _ds/.../tokens/colors.css via DesignSync. All three findings are resolved, and the fix was made at the correct layer:

FindingMobileAdminDesktop (modules.jsx)
1 · tier/hero hexLoyalty hero → var(--partner-hero-from,#1e2540) / var(--partner-hero-to,#3a2a4d)Bronze/Silver/Gold → var(--tier-bronze|silver|gold, #hex) (tokens ×4 each)0 raw flagged hex
2 · em-dashprogress copy → "… joined · N more to unlock" (middle dot)0 em dashes0 em dashes
3 · pending stateisPending block + pending added to demoState enum(admin approval queue already present)'pending' / "Application received" present

Root cause closed at the token layer (the important part). colors.css now defines--tier-bronze/--tier-bronze-soft/--tier-silver/--tier-silver-soft/--tier-gold/--tier-gold-soft and --partner-hero-from/--partner-hero-to in both :root (light) and :root[data-theme="dark"] — so this is a real design-system addition, not a per-screen hex swap. --tier-gold correctly maps to var(--accent); the bronze/silver metallics keep their hue in dark mode (there is no HSL primitive for a metallic, which is the expected reason they stay hex). Screens reference the tokens with the original hex as var(--token, #hex) fallbacks — the sanctioned pattern.

Nothing outstanding on this screen set.

Not architecture-gated

Unlike the Ad Manager review, nothing here needs an architecture-gated tracker row — ADR-0005 already made the model call, the manifest correction already shipped, and all four findings are ordinary design-fixable nits or a follow-up verification. When these prompts are applied, ask to revalidate affiliates and this page's findings get dated Revalidation entries in place.