diff --git a/apps/web/e2e/item-model/item-product-page.spec.ts b/apps/web/e2e/item-model/item-product-page.spec.ts
index 77762d27..5e39e946 100644
--- a/apps/web/e2e/item-model/item-product-page.spec.ts
+++ b/apps/web/e2e/item-model/item-product-page.spec.ts
@@ -9,10 +9,10 @@ const ITEM = '/smartphones/e2e-item-phone';
const BIG = '/smartphones/e2e-item-big';
const NO_DATA_MERCHANT = '17e00000-0000-4000-8000-0000000d1484';
const ORDER = [
- 'offers-table',
- 'pdv-price-history',
'specs-section',
'jreviews-section',
+ 'pdv-price-history',
+ 'offers-table',
];
/** Clicks once the page is hydrated: a click before it does nothing. */
@@ -72,7 +72,7 @@ test('the size picker replaces the quick specs and links each size', async ({
// The lazy Item also with another size loaded on demand.
for (const path of [ITEM, BIG, `${BIG}?v=256gb`]) {
- test(`${path}: price, jump row, sellers, history, specs, reviews`, async ({
+ test(`${path}: price, jump row, specs, reviews, history, sellers`, async ({
page,
}) => {
await page.goto(path);
diff --git a/apps/web/e2e/product-above-fold.spec.ts b/apps/web/e2e/product-above-fold.spec.ts
index f258e024..cddf5746 100644
--- a/apps/web/e2e/product-above-fold.spec.ts
+++ b/apps/web/e2e/product-above-fold.spec.ts
@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
// On a phone the product name, the price line and the user score are in the
// first screen, not behind a full-screen photo (DOS/Rate#605). The score value
// itself, not only its label, must fit (DOS/Rate#630). So does the whole jump row to
-// the sellers, history, specs and reviews (DOS/Rate#1517).
+// the specs, reviews, history and sellers (DOS/Rate#1517, #1530).
const PRODUCT = '/smartphones/iphone-16-pro-max-256gb';
for (const viewport of [
diff --git a/apps/web/e2e/product-section-order.spec.ts b/apps/web/e2e/product-section-order.spec.ts
index 94afd8f0..5fdf3709 100644
--- a/apps/web/e2e/product-section-order.spec.ts
+++ b/apps/web/e2e/product-section-order.spec.ts
@@ -1,19 +1,19 @@
import { expect, test } from '@playwright/test';
-// A shopper reaches the sellers first: the page reads price box, sellers,
-// price history, specs, then reviews, with a jump row to each, and phones
-// show the first three reviews until expanded (DOS/Rate#1469).
+// The product comes first: the page reads price box, specs, reviews, price
+// history, then the sellers last, with a jump row to each (DOS/Rate#1469,
+// #1530), and phones show the first three reviews until expanded.
const PRODUCT = '/smartphones/iphone-16-pro-max-256gb';
// Six seeded reviews (seed.sql, DOS/Rate#566).
const MANY_REVIEWS = '/smartphones/e2e-review-labels-phone';
const ORDER = [
- 'offers-table',
- 'pdv-price-history',
'specs-section',
'jreviews-section',
+ 'pdv-price-history',
+ 'offers-table',
];
-test('sellers come first, then price history, specs and reviews', async ({
+test('specs come first, then reviews, price history and sellers', async ({
page,
}) => {
await page.goto(PRODUCT);
diff --git a/apps/web/src/app/[locale]/smartphones/[slug]/page.tsx b/apps/web/src/app/[locale]/smartphones/[slug]/page.tsx
index 8acda22d..193ec94c 100644
--- a/apps/web/src/app/[locale]/smartphones/[slug]/page.tsx
+++ b/apps/web/src/app/[locale]/smartphones/[slug]/page.tsx
@@ -1026,23 +1026,23 @@ export async function ProductDetailView({
)}
- {/* Where to buy comes first, then the price history, specs and
- reviews; this row jumps to each (DOS/Rate#1469). It sits under the
- price, before the scoreboard, so a phone shows it in the first
- screen (DOS/Rate#1517). */}
+ {/* The specs come first, then the reviews, the price history and
+ the sellers; this row jumps to each (DOS/Rate#1469, #1530). It
+ sits under the price, before the scoreboard, so a phone shows it
+ in the first screen (DOS/Rate#1517). */}
{/* #1 Core Focus Above the Fold: JReviews Dual-Rating & 5-Criteria Scoreboard (DESIGN.md v3.0) */}
@@ -1356,65 +1356,10 @@ export async function ProductDetailView({
-
- {/* Listings whose size is not stated: their own group, never part
- of the "from" price (Q2, DOS/Rate#1154). */}
- {itemPage && itemPage.unknownOffers.length > 0 && (
-
- )}
-
- {/* Time-Series Price History Chart, Fake Discount Detector & Neutral
- Merchant Audit. The wrapper is the jump row's target: a lazy size
- panel has no #price-history-section until it loads (#1469). */}
-
-
- {/* Spec groups, per category template (DOS/Rate#1142), before
- the reviews (DOS/Rate#1436) */}
+ {/* The product first: spec groups, per category template
+ (DOS/Rate#1142), then the reviews, the price history and the sellers
+ last (DOS/Rate#1530). The price strip above keeps the best price in
+ the first screen. */}
{(() => {
const specUi = getSpecUiDictionary(locale);
// Rows with no value and groups with no row are left out
@@ -1521,6 +1466,63 @@ export async function ProductDetailView({
/>
+ {/* Time-Series Price History Chart, Fake Discount Detector & Neutral
+ Merchant Audit. The wrapper is the jump row's target: a lazy size
+ panel has no #price-history-section until it loads (#1469). */}
+
+
+ {/* Listings whose size is not stated: their own group, never part
+ of the "from" price (Q2, DOS/Rate#1154). */}
+ {itemPage && itemPage.unknownOffers.length > 0 && (
+