Skip to content

fix(web,importer): per-category spec templates for earbuds and watches (#1142) - #1163

Merged
JOY (JOY) merged 5 commits into
devfrom
claude/cranky-williams-fe031c
Oct 28, 2026
Merged

JOY (JOY) merged 5 commits into
devfrom
claude/cranky-williams-fe031c

Conversation

@JOY

@JOY JOY (JOY) commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1142

What changes for users

  • Earbuds (/earbuds/<slug>): the spec section opens on "1. Audio & multimedia" and shows 7 groups (audio, battery, connectivity, hardware with the chipset row only, design, availability, regulatory). No screen size, refresh rate, ppi, RAM, ROM, CPU, GPU, OS, camera or cellular rows. The "Memory (RAM/ROM)" tile is gone; the first tile reads "Driver" (was "Driver / Display"). Cards on /earbuds and the home page no longer carry "xGB RAM · yGB".
  • Smartwatches: no "5G" next to the brand, no camera group, and the cellular group is titled "Cellular network"; the 5G row is hidden and the bands row reads "4G bands".
  • Phones, tablets, laptops: unchanged (all ten groups).
  • Group headings are numbered by their place in each category's template, and the "10 spec groups" badge shows the real count.

Root cause and the writer

The bad values are written by this repo's importer, not by Rate-Services code: AiSpecEnricher (apps/importer/src/shopee/ai-spec-enricher.ts) asks the LLM for the phone-shaped ten-group schema for every category, and EntityMatcher stores the result (Tier 3 insert, enrichMasterProductById update). Rate-Services only runs this engine through vendor/rate (ShopeeIngestionPipeline in src/index.ts); a read-only grep found no own writer of ram_gb / has_5g / specs there. So the fix lands here and reaches the crawler on the next vendor/rate bump. The :64gb model key on Buds3 Pro is the normalizer's title parse, tracked in #1123.

How

  • @repo/shared-data/rules: getCategorySpecTemplate / applyCategorySpecTemplate / specFieldApplies: groups, hidden fields, key_specs and flat columns (ram_gb, storage_gb, screen_size_inch, has_5g) per category. Pure, idempotent, never mutates; unknown slugs keep the phone template. No imports, so the rules bundle stays browser-safe.
  • Web: the product page reads the product and its peers through the template; spec groups are a keyed map rendered in template order. Catalogue cards and home cards use it too.
  • Importer: the enricher result and both master writes go through the template in storage mode (forStorage), so new and re-enriched earbud masters stop storing screen, RAM, ROM, camera and cellular fields, and watches stop storing a camera. Hiding 5G on watches is display-only: a stored 5G flag is kept, since a few watches (Apple Watch Series 11, RedCap) really have 5G. Re-enrichment scopes the write by the stored category only, never by a guess from the title. The prompt now says earbuds have no display/RAM/camera and a cellular watch is LTE unless it really supports 5G.

Tests

  • packages/shared-data: per-category templates (phones/tablets/unknown untouched, laptops, earbuds, smartwatches, idempotence, no mutation).
  • apps/web/src/lib/__tests__/spec-template.test.ts: headings per category in vi/en/cs (earbuds 6 groups starting with audio, watches 9 groups with no "5G"), card scrub, and that the page/cards apply the template.
  • apps/importer entity-matcher.test.ts [10/10]: earbud and watch re-enrich, earbud Tier 3 insert.
  • E2E category-spec-template.spec.ts with a seeded bud stored with phone-shaped specs (e2e-bud-002): page, card, and a watch-template page. Docker is not available on this machine, so this runs in CI only.
  • Local: shared-data, web and importer suites, web typecheck, importer tsc, eslint on all three packages: green.

Data cleanup (proposal only, not run)

The display fix makes this optional; re-enrichment also cleans masters now. If JOY approves a one-off cleanup of stored rows (data, no DDL), the PO would run:

-- earbuds: drop phone-only columns and groups
UPDATE rate.master_products mp
SET ram_gb = NULL, storage_gb = NULL, screen_size_inch = NULL, has_5g = false,
    specifications = mp.specifications - 'display' - 'hardware' - 'camera' - 'cellular',
    key_specs = mp.key_specs - 'screen' - 'camera' - 'ram' - 'storage',
    updated_at = now()
FROM rate.categories c
WHERE c.id = mp.category_id AND c.slug = 'earbuds';
-- smartwatches: no camera on any watch
UPDATE rate.master_products mp
SET specifications = mp.specifications - 'camera',
    key_specs = mp.key_specs - 'camera',
    updated_at = now()
FROM rate.categories c
WHERE c.id = mp.category_id AND c.slug = 'smartwatches';
-- Watch Ultra 2 is LTE, not 5G (only the models named in the issue; RedCap 5G watches keep their flag)
UPDATE rate.master_products
SET has_5g = false,
    specifications = jsonb_set(specifications, '{cellular,has_5g}', 'false'::jsonb, false),
    updated_at = now()
WHERE slug LIKE 'apple-watch-ultra-2%';

Check first with the same WHERE as a SELECT count(*); rollback is a restore from a SELECT id, ram_gb, storage_gb, screen_size_inch, has_5g, specifications, key_specs snapshot taken before.

Review

  • Fresh-context subagent: no P0 or P1. Fixed in round 1: P2 earbud chipset was dropped on write; P2 laptops half-applied "no 5G"; P2 a title-guessed category could strip a phone's RAM on re-enrich; P3 watch key_specs.ram; P3 the generic's narrowing is now documented. Not changed: P3 the template follows the route (as the page and breadcrumbs already do).
  • Codex: three P1s. Fixed: the importer forced has_5g=false on real 5G watches and laptops (hiding 5G is now display-only); the watch bands row lost the 4G bands (now shown as "4G bands"). Residual, classed P2: a free-text cellular.technology such as "LTE / 5G" stored by the enricher would still render on a watch; the prompt change and the data cleanup above address that at the source.

Not in scope (follow-ups)

  • /compare (CompareWorkspace.tsx) and the spec export toolbar still read raw stored fields.
  • Earbud values that remain in kept groups can still be odd text from the enricher (for example "Galaxy Wearable OTA" under storage expansion is now hidden, but free-text quality is the enricher's).

Screenshots

Before shots were taken from production (Buds3 Pro, AirPods Pro 3, Watch Ultra 2, /earbuds; 1280 and 390 wide) and are kept out of the repo. The PR's Vercel preview is behind SSO, so per the PO the evidence for this PR is the hermetic E2E (category-spec-template.spec.ts), which seeds an earbud with the exact phone-shaped values from the issue and checks the DOM shows none of them, plus a 5G product read through the watch template. After screenshots of rate.vn will be added after the promote to main.

🤖 Generated with Claude Code


📌 TL;DR

This PR introduces a category-specific specification template system to ensure that product data (e.g., earbuds, smartwatches) only displays and stores relevant fields, preventing nonsensical data like "RAM" or "5G" from appearing on non-phone devices. It updates the AI enrichment pipeline, database write logic, and frontend rendering to filter out irrelevant specs based on the product's category.

🎯 Type of Change

  • 🚀 New feature
  • 🐛 Bugfix
  • 🧹 Refactor
  • ⚡ Performance
  • 📚 Documentation
  • ⚙️ CI / Configuration

🔍 Changes Walkthrough

File Summary of Changes
apps/importer/src/shopee/__tests__/entity-matcher.test.ts Added test cases verifying that EntityMatcher correctly strips irrelevant fields (RAM, screen, 5G) for earbuds and watches during enrichment and creation.
apps/importer/src/shopee/ai-spec-enricher.ts Integrated applyCategorySpecTemplate to filter AI-generated specs. Updated AI prompt rules to explicitly instruct the model to omit fields not applicable to the device category.
apps/importer/src/shopee/entity-matcher.ts Applied applyCategorySpecTemplate to both the initial master_products insert and the subsequent update operations to ensure database consistency with category-specific constraints.
apps/web/e2e/category-spec-template.spec.ts New Playwright E2E tests verifying that the UI correctly hides phone-specific specs (RAM, Screen, Camera) for earbuds and 5G for smartwatches.
apps/web/e2e/seed.sql Updated test seed data to include an earbud with "phone-shaped" specs to verify the frontend correctly filters them out.
apps/web/locales/*.json Updated localization strings for quickSpecDriver to remove "Display" references, as earbuds do not have displays.
apps/web/package.json Added spec-template.test.ts to the test execution pipeline.
apps/web/src/app/[locale]/page.tsx Applied applyCategorySpecTemplate to product cards on the homepage to ensure quick-spec chips (RAM/Storage) are hidden for non-phone categories.
apps/web/src/app/[locale]/smartphones/[slug]/page.tsx Refactored the product detail view to dynamically render spec groups based on getCategorySpecTemplate. Removed hardcoded spec groups and replaced them with conditional rendering logic that respects the category template.

📊 Architectural Flow

flowchart TD
    A[AI Enricher] -->|Raw Specs| B{applyCategorySpecTemplate}
    C[Entity Matcher] -->|DB Write| B
    B -->|Filtered Specs| D[Database: master_products]
    D -->|Stored Data| E[Web Frontend]
    E -->|getCategorySpecTemplate| F{Render Logic}
    F -->|Category: Earbuds| G[Hide RAM, Screen, Camera]
    F -->|Category: Watch| H[Hide 5G, Camera]
    F -->|Category: Phone| I[Show All Specs]
Loading

Summary by CodeRabbit

  • New Features
    • Product cards and detail pages now show specification fields suited to each product category. Earbud listings omit phone-specific details, while smartwatch listings show relevant cellular information without unsupported fields.
    • Category-specific specification groups and labels are available in English, Czech, and Vietnamese.
  • Improvements
    • The quick-spec label now reads “Driver” instead of “Driver / Display” in English and Czech, and uses the matching shorter label in Vietnamese.

#1142)

Earbuds rendered with the phone spec template, filled by the AI enricher
with a 1 inch screen, 1GB RAM, 64GB ROM and microphones as cameras; a
cellular Apple Watch showed 5G.

- @repo/shared-data/rules: getCategorySpecTemplate / applyCategorySpecTemplate
  list the groups, fields, key_specs and flat columns each category has.
  Earbuds: audio, battery, connectivity, design, availability, regulatory.
  Smartwatches: no camera, no 5G. Laptops: no 5G. Phones and tablets keep
  all ten groups.
- Product page: groups render in the category's order with renumbered
  headings and a matching group count; the memory and camera tiles and
  rows a category lacks are gone. The page, its peers, catalogue cards and
  home cards all read the product through the template.
- Importer: the enricher result and every master insert/update go through
  the template, so new and re-enriched masters stop storing phone fields
  for earbuds and 5G for watches. The prompt says so too.
- Tests: shared-data per-category templates, web headings and wiring,
  EntityMatcher writes, and a hermetic E2E with a phone-shaped seeded bud.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

@vercel

vercel Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
rate Ready Ready Preview Oct 28, 2026 4:05am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Walkthrough

Category templates now filter product specifications during importer enrichment and master creation, and during web rendering of product details and cards. Tests cover earbuds, smartwatches, and products without a stored category. Localized specification headings and group counts are also updated.

Changes: Category-scoped specifications

Layer / File(s) Summary
Define and test category templates
packages/shared-data/src/rules/spec-template.ts, packages/shared-data/src/rules/index.ts, packages/shared-data/src/__tests__/shared-data.test.ts
Adds templates for smartphones, tablets, laptops, smartwatches, and earbuds. Template application filters groups, fields, key specs, and unsupported flat values, with separate display and storage behavior.
Apply templates during import
apps/importer/src/shopee/ai-spec-enricher.ts, apps/importer/src/shopee/entity-matcher.ts, apps/importer/src/shopee/__tests__/entity-matcher.test.ts
Applies storage-mode templates to AI enrichment, existing-product updates, and new master rows. Tests cover category-specific filtering and the case where no stored category exists.
Render category-specific detail specifications
apps/web/src/app/[locale]/smartphones/[slug]/page.tsx, apps/web/src/lib/spec-localizer.ts, apps/web/locales/*.json, apps/web/src/lib/__tests__/spec-template.test.ts, apps/web/e2e/category-spec-template.spec.ts, apps/web/e2e/seed.sql
Detail pages render template-defined groups and fields. Localized headings distinguish cellular specifications without 5G, and the section badge uses the template group count.
Filter category cards and validate display
apps/web/src/app/[locale]/page.tsx, apps/web/src/app/[locale]/smartphones/page.tsx, apps/web/e2e/category-spec-template.spec.ts, apps/web/src/lib/__tests__/spec-template.test.ts, apps/web/package.json
Home and catalog cards use category-templated products. Tests check card fields and template application on web pages.

Priority: Not assessed

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • DOS/Rate issue 1166: The pull request applies templates to product details and cards; the issue identifies other raw-field reads in compare, leaderboard, export, competitor lines, and API v1.

Suggested labels: bug

Suggested reviewers: dos

Merge Risk: Low · up to f256a

Category filtering is mergeable with bounded follow-up: cross-category links can hide valid specifications, unusual category names break template lookup, and enrichment logs can misreport returned fields. These issues do not establish broad availability or data-loss risk.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

…-fe031c

# Conflicts:
#	apps/web/src/app/[locale]/smartphones/page.tsx
@github-actions github-actions Bot added area:frontend Frontend / Next.js / UI area:shared Shared packages / contracts / configs area:crawlers labels Oct 27, 2026
dos[bot]
dos Bot approved these changes Oct 27, 2026

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⏱️ Code Review completed (17 eligible files · 101,547 chars · 3 model call(s) · 4 PR unit(s))

Whole diff with bounded context at base 26773bf / head 2eb4f59.

✅ CLEAN_PASS: Complete diff reviewed

Context investigation was bounded to the explicitly requested snapshot paths.

- Watches: hiding 5G is display-only. The importer keeps a stored 5G flag
  and 5G bands (a few watches carry RedCap 5G) and drops only the camera.
  The bands row stays on watches as "4G bands".
- Earbuds keep the hardware group with the chipset row only, so the audio
  chip (H2, BES2700) survives writes and the chip tile has a fallback.
- Laptops stay on the full template (some ship with 5G modems).
- Re-enrichment scopes the write by the stored category only, never by a
  guess from the title.
- Watches keep key_specs.ram; the generic's narrowing is documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dos[bot]
dos Bot approved these changes Oct 27, 2026

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⏱️ Code Review completed (17 eligible files · 110,250 chars · 2 model call(s) · 4 PR unit(s))

Whole diff with bounded context at base 26773bf / head 4da548f.

✅ CLEAN_PASS: Complete diff reviewed

Context investigation was bounded to the explicitly requested snapshot paths.

…-fe031c

# Conflicts:
#	apps/web/package.json
#	apps/web/src/app/[locale]/page.tsx
dos[bot]
dos Bot approved these changes Oct 27, 2026

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⏱️ Code Review completed (17 eligible files · 110,508 chars · 2 model call(s) · 4 PR unit(s))

Whole diff with bounded context at base 645c155 / head 5b38067.

✅ CLEAN_PASS: Complete diff reviewed

Context investigation was bounded to the explicitly requested snapshot paths.

…-fe031c

# Conflicts:
#	apps/web/package.json
#	apps/web/src/app/[locale]/smartphones/[slug]/page.tsx
#	packages/shared-data/src/__tests__/shared-data.test.ts
#	packages/shared-data/src/rules/index.ts
dos[bot]
dos Bot approved these changes Oct 28, 2026

@dos dos Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⏱️ Code Review completed (17 eligible files · 110,776 chars · 3 model call(s) · 4 PR unit(s))

Whole diff with bounded context at base 70f310f / head f256a74.

✅ CLEAN_PASS: Complete diff reviewed

Context investigation was bounded to the explicitly requested snapshot paths.

@coderabbitai coderabbitai Bot added the bug Something isn't working label Oct 28, 2026
@JOY
JOY (JOY) merged commit 20809d6 into dev Oct 28, 2026
16 of 17 checks passed
@JOY
JOY (JOY) deleted the claude/cranky-williams-fe031c branch October 28, 2026 04:11

This branch was successfully deployed

1 active deployment
Preview — f256a740 Deployed Oct 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:crawlers area:frontend Frontend / Next.js / UI area:shared Shared packages / contracts / configs bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(web): earbuds show phone specs (1 inch screen, 1GB RAM, 64GB ROM, cameras); Watch Ultra 2 shows 5G

1 participant