Compare Page Trust Badges and Freshness Indicators
Version: 1.0
Status: Draft
Issue: BUY-2590
Parent Goal: BUY-2037 Goal
Last Updated: April 2026
1. Overview
This document defines the trust-layer design for the BuyWhere comparison surface. It establishes a badge and label system that communicates data freshness, merchant coverage, and affiliate context to both AI agents and human users.
Design Principles:
- Trust signals must be scannable at a glance (≤3 seconds)
- Freshness context prevents misleading price displays
- Merchant coverage signals set appropriate expectations
- Affiliate context maintains transparency with users
2. Badge System Architecture
2.1 Badge Categories
| Category | Purpose | Audience |
|---|---|---|
| Freshness Badges | Data age and reliability | AI agents + humans |
| Merchant Trust Badges | Verification and affiliate status | AI agents + humans |
| Coverage Badges | Scope of comparison | AI agents + humans |
| Context Badges | Sponsored/featured context | Humans primarily |
2.2 Freshness Badge Taxonomy
| Tier | Age Range | Badge Color | Badge Label | Icon | data_freshness Value |
|---|---|---|---|---|---|
| Fresh | < 24 hours | Green #10B981 | "Fresh" | Circle-fill | fresh |
| Recent | 24h - 7 days | None (no badge) | — | — | recent |
| Stale | 7 - 30 days | Amber #F59E0B | "Price may vary" | Warning triangle | stale |
| Very Stale | > 30 days | Red #EF4444 | "Outdated" | X circle | very_stale |
| Unknown | No timestamp | Gray #6B7280 | "Verify price" | Question mark | null |
2.3 Merchant Trust Badge Taxonomy
| Badge | Meaning | Icon | Color | Usage |
|---|---|---|---|---|
| Verified Merchant | Identity confirmed by BuyWhere | Shield check | Blue #0066FF | Merchant name in compare table |
| Affiliate Partner | Active commission arrangement | Dollar sign | Green #10B981 | Price row or buy button |
| Direct Link | No affiliate relationship | External link | Gray #6B7280 | Price row |
| Sponsored | Paid placement | Star | Amber #F59E0B | Product card header |
2.4 Coverage Badge Taxonomy
| Badge | Trigger | Icon | Color | Placement |
|---|---|---|---|---|
| Full Coverage | 3+ retailers with prices | None | — | Page header |
| Low Coverage | 2 retailers | Alert triangle | Amber #F59E0B | Page header + table |
| Limited Coverage | 1 retailer | Alert octagon | Red #EF4444 | Page header + card |
| No Coverage | 0 retailers | X circle | Red #EF4444 | Empty state |
3. Freshness Indicator Design
3.1 Freshness Badge Specifications
┌─────────────────────────────────────────────────────────────┐
│ FRESHNESS BADGE SPECS │
│ │
│ ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ ● Fresh │ │ ⚠ Price may │ │ ✕ Outdated │ │
│ │ 12px 500 │ │ vary 12px 500 │ │ 12px 500 │ │
│ └──────────────┘ └─────────────────┘ └────────────────┘ │
│ │
│ Height: 20px │
│ Padding: 4px 8px │
│ Border-radius: 4px │
│ Font: Inter 500, 12px │
└─────────────────────────────────────────────────────────────┘
Visual Specs:
- Badge height: 20px
- Horizontal padding: 8px (space-2)
- Border-radius: 4px (radius-sm)
- Font: Inter 500, 12px
- Icon size: 14px, left-aligned with 4px gap
- Text: sentence case
3.2 Freshness Badge Colors
| State | Background | Text | Icon |
|---|---|---|---|
| Fresh | #D1FAE5 | #065F46 | #10B981 |
| Stale | #FEF3C7 | #92400E | #F59E0B |
| Very Stale | #FEE2E2 | #991B1B | #EF4444 |
| Unknown | #F3F4F6 | #6B7280 | #9CA3AF |
3.3 Timestamp Display
Primary Format: Relative time ("Updated 2 hours ago") Fallback Format: Absolute date ("Updated Apr 15, 2026")
┌─────────────────────────────────────────────────────────────┐
│ TIMESTAMP DISPLAY │
│ │
│ Relative (default for < 7 days): │
│ "Updated 2 hours ago" — body-sm, text-muted │
│ "Updated 3 days ago" — body-sm, text-muted │
│ │
│ Absolute (for stale/very_stale or user preference): │
│ "Updated on Apr 15, 2026" — body-sm, text-muted │
│ │
│ Very Stale override: │
│ "⚠ Verified on Apr 1 — prices may have changed" │
│ body-sm, warning color │
└─────────────────────────────────────────────────────────────┘
Specs:
- Font: Inter 400, 14px (body-sm)
- Color:
#6B7280(text-muted) - Margin-top: 4px (space-1)
- Position: Below price in product card
3.4 Freshness in Comparison Table
┌─────────────────────────────────────────────────────────────┐
│ COMPARISON TABLE FRESHNESS DISPLAY │
│ │
│ ┌──────────────────┬──────────┬──────────┬──────────┐ │
│ │ │ Shopee │ Lazada │ Amazon │ │
│ ├──────────────────┼──────────┼──────────┼──────────┤ │
│ │ Price │ SGD 449 │ SGD 459 │ SGD 469 │ │
│ │ │ ● Fresh │ ● Fresh │ ⚠ Stale │ │
│ ├──────────────────┼──────────┼──────────┼──────────┤ │
│ │ Last Updated │ 2h ago │ 5h ago │ 12d ago │ │
│ └──────────────────┴──────────┴──────────┴──────────┘ │
│ │
│ Freshness indicator: small inline badge, right of price │
│ Timestamp: separate row below attribute name │
└─────────────────────────────────────────────────────────────┘
4. Merchant Trust Badges
4.1 Verified Merchant Badge
Purpose: Confirms the merchant is a known, vetted entity.
┌─────────────────────────────────────────────────────────────┐
│ VERIFIED MERCHANT BADGE │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 🛡️ Verified Merchant Name │ │
│ │ 12px 500 Inter 500, 14px, text-primary │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Badge specs: │
│ - Icon: Shield check, 14px, #0066FF │
│ - Gap: 4px between icon and text │
│ - Position: Left of merchant name in table │
└─────────────────────────────────────────────────────────────┘
4.2 Affiliate Partner Badge
Purpose: Discloses active commission relationship.
┌─────────────────────────────────────────────────────────────┐
│ AFFILIATE BADGE DISPLAY │
│ │
│ Option A — Inline with price: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ SGD 449 💰 │ │
│ │ Affiliate │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Option B — Below buy button: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ [View Deal →] │ │
│ │ 💰 Affiliate link — supports our site │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Badge specs: │
│ - Icon: Dollar sign, 12px, #10B981 │
│ - Text: Inter 400, 11px, #10B981 │
│ - Background: #D1FAE5, 4px padding │
└─────────────────────────────────────────────────────────────┘
4.3 Sponsored Badge
Purpose: Indicates paid placement or featured listing.
┌─────────────────────────────────────────────────────────────┐
│ SPONSORED BADGE │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ⭐ Sponsored │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Badge specs: │
│ - Background: #FEF3C7 │
│ - Text: #92400E │
│ - Icon: Star, 12px, #F59E0B │
│ - Font: Inter 500, 11px, uppercase │
│ - Border-radius: 4px │
│ - Position: Top-right of product card, above image │
└─────────────────────────────────────────────────────────────┘
5. Coverage Indicators
5.1 Page-Level Coverage Header
┌─────────────────────────────────────────────────────────────┐
│ PAGE HEADER WITH COVERAGE │
│ │
│ Comparing: Sony WH-1000XM5 Wireless Headphones │
│ 3 products from 3 merchants ✓ Full Coverage │
│ │
│ — OR — │
│ │
│ Comparing: Sony WH-1000XM5 │
│ 2 products from 2 merchants ⚠ Low Coverage │
└─────────────────────────────────────────────────────────────┘
Specs:
- Coverage badge: Right-aligned in page title area
- Font: Inter 500, 12px
- Full Coverage: No badge shown (cleaner UI)
- Low/Limited/No Coverage: Badge displayed with amber/red color
5.2 Coverage Warning Banner
For 1-retailer (Limited Coverage) state:
┌─────────────────────────────────────────────────────────────┐
│ ⚠️ LIMITED COVERAGE │
│ Only 1 retailer available for this product. │
│ Price comparison is unavailable — limited data. │
│ [Search for alternatives →] │
└─────────────────────────────────────────────────────────────┘
Specs:
- Background:
#FEF3C7(amber-50) - Border-left: 4px solid
#F59E0B - Padding: 12px 16px
- Text: Inter 400, 14px
- Icon: Alert triangle, 16px,
#F59E0B
6. Mobile Placement Guidance
6.1 Mobile Card Layout
┌─────────────────────────────────────────┐
│ [Sponsored Badge - if applicable] │ ← Top-right corner
│ ┌───────────────────────────────────┐ │
│ │ │ │
│ │ Product Image │ │
│ │ (1:1 ratio) │ │
│ │ │ │
│ └───────────────────────────────────┘ │
│ │
│ Shopee SG 🛡️ Verified │ ← Merchant + trust
│ Sony WH-1000XM5 Wireless Headphones │ ← Product name
│ ★★★★☆ (1,234 reviews) │ ← Rating
│ │
│ SGD $449.00 ● Fresh │ ← Price + freshness
│ Updated 2 hours ago │ ← Timestamp
│ │
│ Free shipping │
│ │
│ [View Deal →] │
│ 💰 Affiliate link │ ← If applicable
└─────────────────────────────────────────┘
Mobile Freshness Position:
- Freshness badge: Right of price, same line
- Timestamp: Below price, smaller text
- Trust badges: Left of merchant name
6.2 Mobile Comparison Table
┌─────────────────────────────────────────┐
│ COMPARISON TABLE (Mobile) │
│ Horizontal scroll with sticky labels │
│ │
│ ┌────────────┬────────┬────────┬────┐ │
│ │ Attribute │ Shopee │ Lazada │ ... │ │
│ ├────────────┼────────┼────────┼────┤ │
│ │ Price │ SGD449 │ SGD459 │ │ │
│ │ │ ● Fresh│ ⚠ Stale│ │ │
│ ├────────────┼────────┼────────┼────┤ │
│ │ Merchant │🛡️ V. │ │ │ │
│ ├────────────┼────────┼────────┼────┤ │
│ │ Shipping │ 2 days │ 2 days │ │ │
│ ├────────────┼────────┼────────┼────┤ │
│ │ Rating │ ★★★★☆ │ ★★★★☆ │ │ │
│ └────────────┴────────┴────────┴────┘ │
└─────────────────────────────────────────┘
Mobile Table Specs:
- First column: Sticky position, 100px width
- Cell padding: 8px 12px
- Freshness badge: Below price in same cell
- Horizontal scroll: Shadow indicators on edges
7. Desktop Placement Guidance
7.1 Desktop Card Layout
┌─────────────────────────────────────────────────────────────────────┐
│ DESKTOP PRODUCT CARD │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ [Sponsored Badge - if applicable] [Image] │ │
│ │ 1:1 │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ 🛡️ Verified Shopee SG │ │
│ │ │ │
│ │ Sony WH-1000XM5 Wireless Headphones │ │
│ │ ★★★★☆ (1,234 reviews) │ │
│ │ │ │
│ │ SGD $449.00 ● Fresh │ │
│ │ Updated 2 hours ago │ │
│ │ │ │
│ │ ✓ In Stock • Free shipping │ │
│ │ │ │
│ │ [View Deal →] 💰 Affiliate │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
7.2 Desktop Comparison Table
┌─────────────────────────────────────────────────────────────────────┐
│ DESKTOP COMPARISON TABLE │
│ │
│ ┌────────────┬────────────────┬────────────────┬────────────────┐ │
│ │ Attribute │ Shopee │ Lazada │ Amazon │ │
│ ├────────────┼────────────────┼────────────────┼────────────────┤ │
│ │ Price │ SGD 449 ● Fresh│ SGD 459 │ SGD 469 ⚠ Stale│ │
│ │ │ Updated 2h ago │ Updated 5h ago │ Updated 12d ago│ │
│ ├────────────┼────────────────┼────────────────┼────────────────┤ │
│ │ Merchant │ 🛡️ Verified │ 🛡️ Verified │ │ │
│ ├────────────┼────────────────┼────────────────┼────────────────┤ │
│ │ Shipping │ 2 days │ 2 days │ 3-5 days │ │
│ ├────────────┼────────────────┼────────────────┼────────────────┤ │
│ │ Rating │ ★★★★☆ (4.5) │ ★★★★☆ (4.2) │ ★★★★★ (4.8) │ │
│ ├────────────┼────────────────┼────────────────┼────────────────┤ │
│ │ Condition │ New │ New │ New │ │
│ ├────────────┼────────────────┼────────────────┼────────────────┤ │
│ │ Affiliate │ 💰 Active │ 💰 Active │ — Direct │ │
│ └────────────┴────────────────┴────────────────┴────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Desktop Table Specs:
- Column width: Flexible, min 120px
- Attribute column: Fixed 140px
- Cell padding: 12px 16px
- Price cell: Badge right-aligned
- Affiliate row: Icon + status text
8. Implementation Reference
8.1 Component Hierarchy
ComparePage
├── CoverageHeader
│ └── CoverageBadge (conditional)
├── ProductCard (×N)
│ ├── SponsoredBadge (conditional)
│ ├── MerchantTrustBadges
│ │ └── VerifiedBadge (conditional)
│ ├── FreshnessIndicator
│ │ ├── FreshnessBadge
│ │ └── TimestampText
│ └── AffiliateDisclosure (conditional)
└── ComparisonTable
├── FreshnessColumn (per merchant)
└── AffiliateColumn (per merchant)
8.2 API Data Contract
Minimum required fields for trust layer:
interface TrustLayerData {
// Freshness
data_freshness: 'fresh' | 'recent' | 'stale' | 'very_stale' | null;
last_checked: string | null; // ISO timestamp
// Merchant trust
merchant_verified: boolean;
affiliate_status: 'active' | 'pending' | 'direct' | null;
// Coverage
retailer_count: number;
total_retailers: number; // For percentage calculation
}
8.3 Rendering Logic
// Freshness badge rendering
function renderFreshnessBadge(freshness: string | null): BadgeConfig {
switch (freshness) {
case 'fresh':
return { label: 'Fresh', color: 'green', icon: 'circle-fill' };
case 'recent':
return { label: '', color: 'none', icon: null }; // No badge
case 'stale':
return { label: 'Price may vary', color: 'amber', icon: 'warning' };
case 'very_stale':
return { label: 'Outdated', color: 'red', icon: 'x-circle' };
default:
return { label: 'Verify price', color: 'gray', icon: 'help-circle' };
}
}
// Coverage badge rendering
function renderCoverageBadge(count: number): BadgeConfig {
if (count >= 3) return null; // No badge for full coverage
if (count === 2) return { label: 'Low Coverage', color: 'amber' };
if (count === 1) return { label: 'Limited Coverage', color: 'red' };
return { label: 'No Coverage', color: 'red' };
}
9. QA Acceptance Criteria
9.1 Freshness Badge Testing
| Test Case | Expected Result |
|---|---|
| Product updated < 24h ago | Green "Fresh" badge displayed |
| Product updated 24h-7d ago | No badge displayed |
| Product updated 7-30d ago | Amber "Price may vary" badge displayed |
| Product updated > 30d ago | Red "Outdated" badge displayed |
| No timestamp available | Gray "Verify price" badge displayed |
| All products fresh | Header shows "Full Coverage" with no badge |
9.2 Trust Badge Testing
| Test Case | Expected Result |
|---|---|
| Verified merchant | Shield check icon displayed |
| Active affiliate | Dollar sign icon + "Affiliate link" text |
| Sponsored listing | Star badge in top-right of card |
| Direct (non-affiliate) | No affiliate badge displayed |
9.3 Coverage Badge Testing
| Test Case | Expected Result |
|---|---|
| 3+ retailers | No badge, clean header |
| 2 retailers | Amber "Low Coverage" in header |
| 1 retailer | Red "Limited Coverage" in header + warning banner |
| 0 retailers | Red "No Coverage" + empty state message |
9.4 Accessibility Testing
- Freshness badges have
aria-labelwith full text ("Data refreshed 2 hours ago") - Color is not sole indicator (icon + text always paired)
- Warning badges meet 3:1 contrast ratio
- Screen reader announces: "Price $449, Fresh data, Updated 2 hours ago"
- Touch targets minimum 44px × 44px on mobile
10. Implementation Checklist
Backend (Priority Order)
- Populate
data_freshnessfield based onupdated_attimestamp - Populate
merchant_verifiedboolean from merchant registry - Populate
affiliate_statusfrom affiliate system - Add
last_checkedtimestamp to all compare responses - Compute
retailer_countandtotal_retailersfor meta
Frontend (Priority Order)
- Implement
FreshnessBadgecomponent - Implement
VerifiedMerchantBadgecomponent - Implement
AffiliateBadgecomponent - Implement
SponsoredBadgecomponent - Implement
CoverageBadgecomponent - Add coverage warning banner for 1-retailer state
- Add freshness column to comparison table
- Add affiliate row to comparison table
- Test all badge states and transitions
- Verify WCAG 2.1 AA compliance
Design Handoff
- Export Figma components for all badge types
- Document color tokens for all badge states
- Confirm spacing and typography specs
- Review with Head of Design (Iris)
Appendix: Badge Quick Reference
Freshness Badges
● Fresh ⚠ Price may vary ✕ Outdated ? Verify price
#D1FAE5/#065F46 #FEF3C7/#92400E #FEE2E2/#991B1B #F3F4F6/#6B7280
Trust Badges
🛡️ Verified 💰 Affiliate ⭐ Sponsored
#0066FF #10B981 #F59E0B
Coverage Badges
✓ Full Coverage ⚠ Low Coverage ⚠ Limited Coverage
(no badge) #FEF3C7/#92400E #FEE2E2/#991B1B
Design specification for BUY-2590. For questions, tag the design team (Iris) or engineering lead.