Skip to content

chore(release): promote dev to main - #1128

Merged
JOY (JOY) merged 6 commits into
mainfrom
dev
Oct 27, 2026
Merged

JOY (JOY) merged 6 commits into
mainfrom
dev

Conversation

@JOY

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

Copy link
Copy Markdown
Contributor

Promote dev to main.

Merge with a merge commit, never squash (Docs/Resources/RELEASE.md).


📌 TL;DR

This PR introduces size-based product keying for Apple devices (e.g., distinguishing 11" vs 13" iPad Pros) to improve data accuracy, refactors the web focus-trap to handle dynamic DOM changes and foreign inert states, and enhances the listing price floor logic to correctly classify over-ear headphones vs. earbuds.

🎯 Type of Change

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

🔍 Changes Walkthrough

File Summary of Changes
apps/importer/src/shopee/product-normalizer.ts Implements readAppleSize to extract screen/case sizes from titles. Updates model keying to include size (e.g., ipad-pro-m4-11). Adjusts benchmark enrichment logic to only apply screen/battery specs when the registry entry matches the detected size.
apps/importer/src/shopee/__tests__/open-core-intelligence.test.ts Adds extensive test cases for Apple size keying (#1113, #1124), covering edge cases like bundled accessories, watch bands, and ambiguous numbers (RAM vs. screen size).
apps/web/src/lib/focus-trap.ts Refactors inertOutside to use a MutationObserver and a reference-counting map (inertHolds). This ensures dynamically added elements become inert while the menu is open, and that inert states set by other code are preserved upon cleanup.
apps/web/e2e/phone-menu-focus-trap.spec.ts Adds E2E tests verifying that late-added content is inert while the menu is open, foreign inert states are preserved, and the menu toggle remains visible/focusable at 320px width.
apps/web/src/app/style/scss/style.scss Adjusts mobile navigation width to min(320px, calc(100% - 56px)) to ensure the menu toggle button remains visible and accessible on narrow screens.
apps/web/src/lib/rate-api.ts Refactors seller data fetching to include metadata_dosafe_risk_score from merchants table. Updates mapTrustViewRowToMerchantOffer to use metadata scores when the view's DOSafe score is missing (null), improving risk scoring accuracy.
apps/web/src/server/rate/__tests__/core-rate-architecture.test.ts Adds tests for the new metadata DOSafe risk fallback logic, verifying that threat_source is selected and metadata is only used when the view score is missing.
packages/shared-data/src/rules/listing-price-floor.ts Updates EARBUDS_TITLE_REGEX to exclude over-ear headphones (e.g., Sony WH-1000XM5) and handle glued brand names (e.g., SamsungGalaxyBuds3). Changes minimumListingPriceVnd to evaluate title and URL as a single text to prevent URL keywords from overriding title classification.
packages/shared-data/src/__tests__/shared-data.test.ts Adds tests for the updated price floor logic, ensuring over-ear headphones retain the device floor and glued earbuds names are correctly classified.

📊 Architectural Flow

flowchart TD
    subgraph "Product Normalization (Importer)"
        A[Shopee Product Title] --> B{Is Apple Device?}
        B -->|Yes| C[readAppleSize]
        C --> D{Size Detected?}
        D -->|Yes| E[Append Size to Model Key<br>e.g., ipad-pro-m4-11]
        D -->|No| F[Keep Base Model Key]
        E --> G[Lookup Benchmark Registry]
        F --> G
        G --> H{Size Matches Registry?}
        H -->|Yes| I[Apply Screen/Battery Specs]
        H -->|No| J[Ignore Size-Specific Specs]
    end

    subgraph "Focus Trap (Web)"
        K[Menu Opens] --> L[inertOutside]
        L --> M[Observe DOM Mutations]
        M --> N{New Element Added?}
        N -->|Yes| O[Set inert=true]
        N -->|No| P[Check Foreign Inert]
        P --> Q[Preserve Foreign Inert State]
        R[Menu Closes] --> S[Release Holds]
        S --> T[Clear Only Own Inert States]
    end

    subgraph "Price Floor (Shared Data)"
        U[Title + URL] --> V[Join Texts]
        V --> W{Match EARBUDS_REGEX?}
        W -->|Yes| X[Apply Earbuds Floor<br>400k VND]
        W -->|No| Y[Apply Device Floor<br>1.2M VND]
    end
Loading

JOY (JOY) and others added 6 commits October 27, 2026 15:32
…#1119)

* fix(web): view offers read the merchant metadata DOSafe score (#1108)

The trust view's metadata column is the product's, so a seller scored only
in merchants.metadata.dosafe_risk_score showed "-" on the view path and a
number on the fallback path. The existing merchants side read now also
fetches that score for sellers the view has none for, and it only fills a
missing view score. A test pins threat_source in TRUST_VIEW_SELECT through
the request URL.

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

* fix(web): any threat_source string marks a joined threat_intel row (#1108)

dosafe.threat_intel.source is NOT NULL in the DOS-Me ledger, so a string
threat_source, even a blank one, means the LEFT JOIN found a row and its 0
is a real score. Only null or absent means no row.

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

* fix(web): keep follower counts when the metadata path is rejected (#1108)

Review round 1: one merchants read carried both values, so a rejected
metadata->dosafe_risk_score path also dropped the #685 follower
adjustment. Fall back to the id, follower_count read for unscored sellers.
Tests cover a seller missing both scores, the rejected path, and a view
row with no seller.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… toggle) (#1118)

* fix(a11y): keep late content inert and clear only our inert (#1110)

- inertOutside watches the kept ancestors with a MutationObserver, so
  content added while the phone menu is open (a toast, a portal, new
  content after a soft navigation) becomes inert too.
- A shared hold count marks the inert set by inertOutside; the undo
  clears only that, once no other open call holds the element, and
  gives up an element whose inert other code changes meanwhile.
- The phone drawer is min(320px, 100vw - 56px) wide, so at a 320px
  viewport it no longer covers the live menu toggle.

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

* fix(a11y): release removed nodes and size the drawer without the scrollbar (#1110)

Review follow-ups: a node removed while the menu is open is released
and no longer watched; the drawer width uses 100% of its fixed box, so
a classic scrollbar does not push it over the toggle; drop a wrong
comment about the opening slide in the 320px test.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…1113) (#1121)

* fix(normalizer): key Apple lines sold in several sizes by their size (#1113)

The 11 and 13 inch iPad Pro, the 13 and 15 inch MacBook Air, the 14 and 16
inch MacBook Pro and the 42 and 46mm Watch Series are separate products at
separate prices, but the normalizer gave each pair one key, so their
listings could land on one master. A title that names its size now keys
with it ("apple:ipad-pro-m5-11:256gb"), which is also how the published
masters are keyed. A title without its size keeps the size-less key, and
one-size lines (Watch Ultra, iPad mini, MacBook Neo) never carry one. The
benchmark registry is still read per line.

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

* fix(normalizer): read Apple sizes past core counts, part codes and OS versions (#1113)

Review follow-up. A bare number is no size before "nhân"/"lõi" core words, a
"16G" RAM, inside a part code ("Z15S") or after "RAM" or "macOS"; a MacBook
Air is only 13 or 15 inch. The registry's screen and battery describe one
size of a line, so a title of the other size no longer takes them. Each
whitespace gap is one optional space (CodeQL js/polynomial-redos).

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

* fix(normalizer): skip a bundled accessory's size and read "13.0 inch" (#1113)

Adversarial review follow-up. The size is read only before a "+" bundle
("iPad Pro M4 256GB + bàn phím 11 inch" is no 11 inch iPad), except "GPS +
Cellular", and "13.0 inch" is the 13 inch iPad.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…WH- (#1116) (#1122)

* fix(shared-data): earbuds regex reads glued tiers and drops over-ear WH- (#1116)

EARBUDS_TITLE_REGEX now allows a number after a tier ("BudsPro3",
"FreeBudsPro4"), a "pro"/"se" tier after FreeBuds, and "GalaxyBuds" glued
to the brand ("SamsungGalaxyBuds3"). "wh-" is dropped: Sony WH- models are
over-ear headphones and keep the device floor.

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

* test(shared-data): pin model-key forms for the earbuds floor (#1116)

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

* fix(shared-data): keep the earbuds regex free of polynomial backtracking (#1116)

CodeQL js/polynomial-redos flagged the back-to-back digit runs around the
optional tier. Moving the trailing digits inside the tier group matches
the same strings (300k random probes, no difference) in linear time.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#1126)

* fix(normalizer): a Watch band's size no longer keys the watch (#1124)

Retailers name the case size before the band ("46mm Viền nhôm Dây cao su
S/M"), so readAppleSize now skips a size after "dây"/"band"/"strap"/"loop"/
"bracelet" up to the next ")", ",", "|" or " - ". A title whose only size is
the band's keeps the size-less key.

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

* fix(normalizer): keep a lone band size as the Watch case size (#1124)

Review (P2): Apple sells a band by the case it fits ("Dây Cao Su 46mm"),
so a lone size after a band word is the case's. A size outside a band
phrase still wins, and a band size counts only when the title names no
other size. Band phrases also end at ";" and an en dash.

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

* test(normalizer): pin each Watch band word and separator (#1124)

Review follow-ups: "không dây" (wireless) is no band word, a glued
"Sportband" is, and fixtures now fail when any band word, separator or
the lone-size rule is removed. Adds the bracketed retailer title
"Viền nhôm dây cao su [40mm/GPS]", which keeps its case size.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…i nghe titles (#1125) (#1127)

* fix(shared-data): earbuds regex reads glued brands and drops over-ear titles that say tai nghe (#1125)

Any brand glued to Buds, FreeBuds, AirPods or WF1000XM now reads as earbuds
(PixelBudsPro2, HuaweiFreeBudsPro4, SonyWF1000XM5), as do AirPodsPro2 and
two glued tiers (Buds3ProFE). An anchored lookahead keeps over-ear WH- titles
on the device floor even when they also say "tai nghe". The pattern stays
linear: the lookahead runs once and no two repeats share characters.

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

* fix(shared-data): over-ear WH with a space or underscore, glued Redmi/Samsung/Enco/Tune Buds (#1125)

Review follow-up on PR #1127.

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

* fix(shared-data): any Sony WH- model, brand-bound AirPods, title and URL read together (#1125)

Codex review follow-up on PR #1127: WH-L600/WH-RF400 are over-ear,
"repairpods" is not AirPods, and an over-ear title wins over tai-nghe
in the URL.

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

---------

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 27, 2026 10:47am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ea70c438-fd33-4a5f-8660-6f9e72ed5244

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • 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.

@github-actions github-actions Bot added area:frontend Frontend / Next.js / UI area:shared Shared packages / contracts / configs area:crawlers labels Oct 27, 2026
@JOY

Copy link
Copy Markdown
Contributor Author

[Agent: PO] commitlint fails on dev's head commit 69e8b84 (the squash of #1127): a body line and a footer line exceed 100 characters. commitlint is not a required check on main, and dev history cannot be rewritten, so this promotion is merged with --ignore commitlint as merge-when-green.mjs documents. Required checks are unaffected.

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 (9 eligible files · 52,066 chars · 4 model call(s) · 2 PR unit(s))

Whole diff with bounded context at base a3ad4ba / head 69e8b84.

✅ No verified defects

2 possible issue(s) could not be grounded in or verified against the snapshot source, so they are not published.

Context investigation was bounded to the explicitly requested snapshot paths.

This branch was successfully deployed

1 active deployment
Preview — 69e8b84c Deployed Oct 27, 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant