Appearance
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:
| File | Role | Size |
|---|---|---|
ui_kits/merchant-console-v2/manifest.js | nav/entitlement registry (the one code change the revision hinged on) | read |
prototype/admin-panel/Affiliates.dc.html | Admin command center (Part 1) | 111.6 KB |
prototype/mobile-console/InviteEarn.dc.html | Tier 0 "Invite & Earn" (Part 2A) | read |
prototype/mobile-console/Loyalty.dc.html | Tier 1 Partner (Part 2B) | read |
prototype/mobile-console/Home.dc.html | non-destructive entry-point integration | read |
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 noplangate, so Free users see it (its entire purpose as a free-to-paid lever), andappliesTo: '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 itsplan:'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. dsSelectused, zero native<select>. The admin file usesdsSelect×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.htmlcommission hero:linear-gradient(150deg,#1e2540,#3a2a4d)— a bespoke dark navy/purple, no token, novar(…, #hex)fallback wrapper. (InviteEarn's hero correctly usedvar(--accent),var(--coral-500).)Affiliates.dc.htmlBronze/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:
| Finding | Mobile | Admin | Desktop (modules.jsx) |
|---|---|---|---|
| 1 · tier/hero hex | Loyalty 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-dash | progress copy → "… joined · N more to unlock" (middle dot) | 0 em dashes | 0 em dashes |
| 3 · pending state | isPending 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.