Skip to content

Premium Analytics: align the video detail composition with the design mocks - #50970

Merged
dognose24 merged 17 commits into
trunkfrom
update/wooa7s-1785-video-detail-composition
Aug 5, 2026
Merged

Premium Analytics: align the video detail composition with the design mocks#50970
dognose24 merged 17 commits into
trunkfrom
update/wooa7s-1785-video-detail-composition

Conversation

@dognose24

@dognose24 dognose24 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Part of WOOA7S-1785

Why

The video detail page should match the design mocks so beta interviewees see the intended composition: a full-width highlights row (Impressions / Hours watched / Retention rate), a Views performance chart beside the "Used on posts & pages" list, and a 72×72 thumbnail header.

Proposed changes

  • Data layer: upgrade stats/video/:id requests to statType=all with the page's date range (start_date/date; range-scoped server total including play-weighted retention_rate, as confirmed against wpcom #229903 / Calypso #112969). One shared cache entry feeds the highlights tiles and chart.
  • Highlights: show Impressions / Hours watched / Retention rate from the range-scoped totals. Views move to the chart card; the trailing-30-day sums and per-statType requests from WOOA7S-1625: Premium Analytics: compose video detail widgets and summary header #50591 are removed.
  • New jpa/video-detail-views-performance widget: add a single-series views line chart with day/week/month granularity. Bucket keys are parsed as site-local calendar dates (parseSiteDateTime) so labels match the legacy chart in any site timezone.
  • Header: add a 72×72 poster (cropped to a square; post.poster passthrough guarded by safeHttpUrl, with an onError video-icon fallback), a single-line ellipsized title, and a line stating the applied performance range.
  • Report scope: keep the date range in the URL and normalize comparison params out because the video detail design has no comparison series. The header date-filter UI will follow separately after Premium Analytics: measure the date presets instead of guessing a breakpoint #50906 / WOOA7S-1816 lands.
  • Embeds card: rename it to "Used on posts & pages", use the pages icon, and place it 2×2 beside the chart. Pin the fixed page grid to the small row height used by post detail.
  • Overflow fixes: contain the summary's inline size so long unbroken titles cannot force horizontal scrolling, allow the admin-ui breadcrumb slot to shrink, and preserve the poster's square box against the boot shell's image reset.

Testing instructions

  1. Open a video detail page (?page=jetpack-premium-analytics-wp-admin&p=%2Fvideo%2F<id>). Highlights show Impressions / Hours watched / Retention rate, the Views performance chart renders beside "Used on posts & pages", and Network shows one range-scoped stats/video/<id> request with statType=all shared by the highlights and chart.
  2. Confirm the performance range shown below the title matches the range in the URL and the data rendered by both widgets.
  3. Confirm chart tooltips show the same calendar dates as the legacy Odyssey chart, including on a non-UTC site timezone.
  4. Confirm the header shows a 72×72 thumbnail, falls back to the video icon when no poster is available or loading fails, and ellipsizes long filename-like titles without horizontal page scrolling.
  5. Add comparison params such as comp=1 to the URL and confirm the route strips them and renders only the current Views series.
  6. Run pnpm test and pnpm run typecheck from projects/packages/premium-analytics.

Notes for reviewers

Does this pull request change what data or activity we track or use?

No.

@dognose24

Copy link
Copy Markdown
Contributor Author

@claude please review this PR.

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dognose24's task in 3m 41s —— View job


Code review

I read through the data layer, the new video-detail-views-performance widget, the UI-package date-filter changes, and the route/stage/summary-card work. This is a careful, well-documented PR — the comments explaining why (the boot-shell img squash, the intrinsic-width scroll, the compact-state deadlock) are genuinely helpful and made review much faster. Below are findings, mostly minor.

Files reviewed
  • Data: processing/stats/single-video.ts, queries/stats-single-video-query.ts, hooks/use-stats-single-video.ts, utils/stats-params.ts
  • Widgets: video-detail-views-performance/* (render, use-video-views.ts, widget, css), video-detail-highlights/render.tsx
  • UI: date-filters-panel.tsx, date-range-layout.ts
  • Route: route.ts, config/layout.ts, stage.tsx, hooks/use-video-summary.ts, components/video-summary-card/*

✅ Verified: the "single shared request" claim holds

The highlights widget passes the raw reportParams.from/to, while use-video-views.ts passes date-only slices (toValidDay). These look like they'd produce different react-query keys, but reportParamsToStatsQueryParams runs both through datePart() (stats-params.ts:51), collapsing them to the same start_date/date/days. Both also send period: 'day', statType: 'all', so the query keys are identical and the two widgets share one cache entry as documented. Good.

✅ Security / correctness spot-checks

  • post.poster is routed through safeHttpUrl in use-video-summary.ts:49 before it reaches <img src>, with an onError → placeholder fallback. Matches the widget URL-guard convention. 👍
  • Comparison params are stripped both in route.ts (COMPARISON_SEARCH_PARAMS normalized out of the URL) and opted out at the UI (showComparison={ false }), and the data hooks never request a comparison window. Consistent belt-and-suspenders.
  • Inverted/hand-edited URL ranges are guarded (calendarBucketWindows returns [] when from > to, and toValidDay rejects malformed days before they reach parseISO/each*OfInterval). Good defensive handling of user-editable URLs.

🟡 Minor — worth addressing

  1. Highlights re-implements toVideoId instead of reusing the shared toPostId. video-detail-highlights/render.tsx:49-53 hand-rolls a parse that the views widget gets from the shared toPostId (render.tsx:56). They also differ in the invalid sentinel (NaN vs 0), which makes the two scope checks read differently for no real reason. Since the file already imports from @jetpack-premium-analytics/data, consider using toPostId here too for consistency. Fix this →

  2. style.module.css uses height: 100% where the rest of the file uses logical props. video-detail-views-performance/style.module.css:7 sets height: 100% while lines 8/14 already use min-block-size/flex. The repo guideline is logical properties throughout — block-size: 100% keeps it consistent (functionally equivalent here). Trivial.

  3. Subtitle punctuation is inconsistent. In video-summary-card.tsx, publishedSentence ends in a period (Video published on %s.) but performanceSentence does not (Performance from %1$s to %2$s), so the joined subtitle reads "… 2025. Performance from … to …" with no closing period. Intentional per the mock is fine — just flagging in case it's an oversight.

🟢 Non-blocking / questions

  1. retention_rate / 100 depends on the server unit. video-detail-highlights/render.tsx:106 divides by 100 before the percentage formatter (which multiplies by 100 again), i.e. it assumes the server returns retention_rate as a 0–100 number. The PR says this was confirmed against wpcom #229903 — no change requested, just noting that if the endpoint ever returns a 0–1 fraction the tile would read 100× too small. The processing fixture/test is the right place to pin this expectation (looks like the new test does).

  2. The open questions you raised in the description (no "All time" preset in the shared DateRangeFilter; the post-detail shared bugs; localTZDate day-shift follow-up; WOOA7S-1839 narrow-tile clipping) all read as reasonable scope boundaries. Filing the follow-ups rather than forking the shared control is the right call.

Tests & docs

Test coverage looks solid — new/updated suites for the processing sanitizer, the query builder (incl. the statType=all + range case), the summary hook, the stage, the highlights widget, and a full 224-line suite for the new views widget. The new widget also follows the folder contract (widget.json jpa/ name, presentation: framed, story present) and the changelog entry is present.

I couldn't independently run pnpm jest / typecheck in this review pass — worth confirming CI is green on the two commits, but nothing I read suggests a failure.

Overall: clean, well-scoped, and matches the stated design intent. Only #1 (DRY on the scope helper) and #2 (logical property) are worth a quick touch-up; the rest are optional.
· update/wooa7s-1785-video-detail-composition

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the update/wooa7s-1785-video-detail-composition branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/wooa7s-1785-video-detail-composition
bin/jetpack-downloader test jetpack-mu-wpcom-plugin update/wooa7s-1785-video-detail-composition

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@dognose24 dognose24 self-assigned this Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Jul 31, 2026
@jp-launch-control

jp-launch-control Bot commented Jul 31, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 7 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/premium-analytics/packages/routing/src/search/report-params/report-params.ts 17/18 (94.44%) -5.56% 1 ❤️‍🩹
projects/packages/premium-analytics/packages/data/src/processing/stats/__fixtures__/single-video.ts 3/3 (100.00%) 0.00% 0 💚
projects/packages/premium-analytics/packages/data/src/processing/stats/single-video.ts 19/19 (100.00%) 0.00% 0 💚
projects/packages/premium-analytics/routes/video-detail/components/video-summary-card/video-summary-card.tsx 11/11 (100.00%) 0.00% 0 💚
projects/packages/premium-analytics/routes/video-detail/stage.tsx 22/23 (95.65%) 0.20% 0 💚

3 files are newly checked for coverage.

File Coverage
projects/packages/premium-analytics/widgets/video-detail-views-performance/use-video-views.ts 29/33 (87.88%) 💚
projects/packages/premium-analytics/widgets/video-detail-views-performance/render.tsx 12/13 (92.31%) 💚
projects/packages/premium-analytics/widgets/video-detail-views-performance/widget.ts 0/0 (—%) 🤷

Full summary · PHP report · JS report

Coverage check overridden by Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code .

@dognose24 dognose24 added [Tests] Includes Tests [Status] Needs Team Review Obsolete. Use Needs Review instead. labels Jul 31, 2026
@dognose24
dognose24 marked this pull request as ready for review July 31, 2026 17:29
@dognose24
dognose24 requested review from a team as code owners July 31, 2026 17:29
@dognose24 dognose24 removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. [Status] In Progress labels Jul 31, 2026
@dognose24 dognose24 added [Status] Needs Review This PR is ready for review. and removed [Status] In Progress [Status] Needs Team Review Obsolete. Use Needs Review instead. labels Jul 31, 2026
@dognose24
dognose24 requested a review from chihsuan July 31, 2026 17:31
@dognose24

Copy link
Copy Markdown
Contributor Author

Addressing claude[bot]'s review: both items fixed in ecc8881 — highlights now uses the shared toPostId (0 sentinel, hasVideoScope adjusted) and the chart widget uses block-size per the logical-properties convention. Note for a possible follow-up: widgets/post-views/style.module.css on trunk has the same height: 100% pattern.

Comment on lines +5 to +18
// A long unbroken video title in the breadcrumb refuses to shrink: admin-ui's
// Page header renders the breadcrumbs slot inside a flex chain (inner header
// Stack → the Breadcrumbs `nav`) whose items keep the flexbox default
// `min-inline-size: auto`, so the crumb's nowrap min-content propagates and
// drags the whole page into horizontal scrolling before the crumb's own
// ellipsis can engage. Let both links shrink until admin-ui fixes the slot;
// the `nav`'s parent has no stable class, hence the structural `:has()`.
.page :has(> nav[aria-label]) {
min-inline-size: 0;
}

.page nav[aria-label] {
min-inline-size: 0;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this issue documented somewhere in Gutenberg?

I'm thinking these likely cover it WordPress/gutenberg#77628 and WordPress/gutenberg#77039 but asking in case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question — I checked both: neither documents this exact min-content propagation as its own bug. #77039 (Breadcrumb component extraction) and #77628 (the admin-ui 2.0 proposal) are the tracks under which the slot gets reworked, so I've linked both from the workaround comment in 73b093a and noted no dedicated issue exists. Happy to file one upstream if you think it's worth pinning.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Linked the tracking issues from the workaround comment in 73b093a: WordPress/gutenberg#77039 (Breadcrumbs slot extraction) and WordPress/gutenberg#77628 (admin-ui 2.0 proposal).

Comment on lines 61 to 66
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--wpds-dimension-gap-lg);
padding-block: 40px;
padding-inline: var(--wpds-dimension-padding-2xl);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is starting to look like you just need Stack component there ;-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adopted in a1b00f5 for the header row — and then the row shrank back to a summary-only div in 569bdf4 when the date-filter half was dropped, so the Stack went with it. It'll return with the follow-up that reintroduces the filters row.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adopted in a1b00f5 — but the header row it applied to went with the date-filter deferral in 569bdf4 (waiting on the preset-measurement rework that has since landed via #50906), so the surviving summary-only header is a plain div again. Will re-apply Stack when the filters row returns.

@dognose24

Copy link
Copy Markdown
Contributor Author

Housekeeping: the one-line jest coverage-glob fix moved out to #50997 — touching tools/ routed this PR to the monorepo owners via CODEOWNERS, and the tooling change deserves its own review anyway. Until it lands, the storybook mocks under stories/mocks/ count as uncovered here again, so the coverage check is overridden with the Covered by non-unit tests label (storybook itself exercises those files). The route.beforeLoad tests stay in this PR.

@dognose24
dognose24 removed the request for review from a team August 3, 2026 12:28
@dognose24 dognose24 changed the title Premium Analytics: align the video detail page with the design mocks Premium Analytics: align the video detail composition with the design mocks Aug 3, 2026
@dognose24
dognose24 requested review from chihsuan and simison August 3, 2026 17:55

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the update! Left a few more comments.

// comparison, so drop them before they reach the URL and the widgets.
for ( const param of COMPARISON_SEARCH_PARAMS ) {
delete seeded[ param ];
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we keep the comparison params in the URL and let these widgets continue to ignore them instead?

pickReportDateParams() intentionally carries comparison state into the video detail route, while useDashboardLink() and the “Back to Videos” link read that state back when navigating away. Removing it here means Dashboard → Video → Dashboard silently loses the user’s comparison settings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — done in 65e3e3e. The route now leaves comparison params untouched: the page's widgets already ignore them, and preserving them means the dashboard link and "Back to Videos" carry the user's comparison settings back out instead of silently dropping them on a Dashboard → Video → Dashboard round trip. Replaced the strip test with two pass-through tests (settled URL and seeded URL).

@dognose24 dognose24 Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Update after more live testing: going with your suggestion outright. The route-level stripping turned out to fight the round-trip state (and my serialization fix effectively disabled it anyway), so the contract is now: comparison params pass through the video URL untouched and every widget on the page ignores them (test-locked); the page still renders no comparison UI per the mock. The buildDashboardLink serialization fix stays — it was independently broken. Thanks for pushing on this one.

icon: video,
value: sumVideoMetric( impressions.data ),
icon: seen,
value: total?.impressions ?? 0,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we preserve missing metrics instead of coercing them to zero?

The available statType=all columns are described by fields, so a missing impressions, watch_time, or retention_rate value does not necessarily mean a measured zero. With ?? 0, a partial response displays 0, 0.0, or 0.0% as real data.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 65e3e3e. The tiles now pass null for metrics missing from the response instead of coercing to 0 — MetricTileGrid already renders its placeholder for any non-finite value, so a partial response shows placeholders rather than fake zeros. Added a test covering a fields: [period, plays]-only response.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 65e3e3e — metrics missing from the response pass null through, so MetricTileGrid renders its placeholder instead of a fabricated 0 / 0.0 / 0.0%.

if ( fields.length >= 2 && Array.isArray( payload.data ) ) {
const metricNames = fields.slice( 1 );
metrics = metricNames;
rows = tuples.map( ( [ period, ...cells ] ) => ( {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to omit rows for now and add the per-metric series shape when its first consumer lands? It appears that rows and StatsSingleVideoMetricRow currently have no consumers and add normalization, exports, and tests across several layers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — removed in 65e3e3e. rows and StatsSingleVideoMetricRow are gone from the normalizer and the export surface; metrics stays since it still marks range mode. The per-metric series shape can come back with its first consumer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 65e3e3e — rows / StatsSingleVideoMetricRow are gone from the normalizer; the widgets read data/total/post directly.

…etrics, drop unused rows

- The video-detail route no longer strips comparison params: the page
  ignores them, but the dashboard link and Back to Videos carry the URL
  state back out, so stripping lost the user's comparison settings on a
  round trip.
- Highlights tiles render the MetricTileGrid placeholder for metrics
  missing from the response instead of coercing them to 0/0.0/0.0%.
- Remove the unconsumed rows/StatsSingleVideoMetricRow from the
  single-video normalizer; widgets read data/total/post directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dognose24 added a commit that referenced this pull request Aug 4, 2026
Mirror the video-detail review outcome (#50970): the page renders no
comparison and its widgets ignore the params, but the breadcrumb's
dashboard link reads the URL state back out, so stripping them lost the
user's comparison settings on a Dashboard → Post → Dashboard round trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dognose24 and others added 4 commits August 4, 2026 17:26
The breadcrumb's dashboard link serialized search values with bare
URLSearchParams, but the router JSON-parses every search value on read —
so comp: '1' came back as the number 1 and every strict comp === '1'
check treated comparison as disabled. From that dashboard, detail links
then carried no comparison at all.

- buildDashboardLink now JSON-quotes string values that would re-parse
  as a different type, matching the router's own stringifier.
- hasComparisonEnabled, deriveComparisonRange, and the range patch
  accept a numeric comp flag from URLs already written unquoted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ment panel

Conflicts were the #50964 externals refactor against the video-detail
stage/summary-card imports (resolved to the externals convention) and the
date-filters panel, taken wholly from trunk — this branch's net change to
packages/ui is zero since 'Drop the header date-filter half', and trunk's
WOOA7S-1817 content-measurement rework is what that deferral was waiting for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @dognose24 Overall this looks good

I left two notes worth addressing, then feel free to merge. See inline comments. Thanks!

safeParseFloat( value ),
] )
)
: null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we drop non-numeric cells here instead of coercing them?

A video with no retention data renders Retention rate 0.0% — a fabricated statistic that reads as a real finding, which on an analytics surface costs more than a visible blank. Same fake-zero we removed from the tiles last round: safeParseFloat() falls back to 0, so ?? null never sees it. normalizeStatsSummary() already guards this way (processing/stats/utils.ts:276).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in cefbbc0 — non-numeric cells are now dropped with the same guard normalizeStatsSummary uses (isStatsNumericSummaryValue, now exported from processing/stats/utils.ts), so a missing metric stays undefined and the tile renders its placeholder instead of a fabricated 0.0%. Added a normalizer test pinning the dropped-cell behavior.

onClick: () => {
void Promise.all( queries.map( query => query.refetch() ) );
},
onClick: () => void refetch(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this error state go through describeError() too, now that its sibling does?

Without Stats access the page contradicts itself — both cards share one cache entry, so Views performance says You don't have access to this data. while this one offers a Retry that can never succeed, so the user keeps pressing it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in cefbbc0 — the highlights card now routes its error through describeError() with the same shape as the Views performance card, so a plain 403 shows the shared no-access copy with no Retry, and only the healable no_connection 403 keeps the Retry action. Tests updated: the retry test now uses the no_connection envelope, and a new case pins the permission 403 rendering without a Retry button.

…escribeError

Per review: a non-numeric totals cell coerced to 0 renders a fabricated
statistic (e.g. "Retention rate 0.0%"), so drop it with the same guard
normalizeStatsSummary uses and let the tile show its placeholder. The
highlights card's error state now goes through describeError like its
sibling, so a plain 403 shows the shared no-access copy instead of a
Retry that can never succeed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dognose24
dognose24 requested a review from chihsuan August 5, 2026 06:02
…ideo-detail-composition

# Conflicts:
#	projects/packages/premium-analytics/routes/video-detail/stage.tsx

@chihsuan chihsuan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good! 🚢

@dognose24
dognose24 merged commit d00239e into trunk Aug 5, 2026
81 checks passed
@dognose24
dognose24 deleted the update/wooa7s-1785-video-detail-composition branch August 5, 2026 13:14
@github-actions github-actions Bot added [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. and removed [Status] Needs Review This PR is ready for review. labels Aug 5, 2026
dognose24 added a commit that referenced this pull request Aug 7, 2026
…tents trail (#51085)

* Premium Analytics: fix the breadcrumb overflow behind the display:contents trail

The StatsBreadcrumbs wrapper is display:contents, so the route-level
':has(> nav)' workaround from #50970 lands min-inline-size on a box-less
element and the crumb's nowrap min-content propagates again, widening
the whole page into horizontal scrolling (regressed when #51022 swapped
the raw Breadcrumbs for StatsBreadcrumbs). Own the constraint in the
component: the wrapper's DOM parent and the nav — the actual flex item
in the box tree — both get min-inline-size: 0, fixing every page that
renders StatsBreadcrumbs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Drop the superseded route-level breadcrumb workaround

The ':has(> nav)' rule targets the display:contents trail wrapper (no
box, so min-inline-size has nothing to act on) and the nav rule is
covered by the component-owned fix — StatsBreadcrumbs' stylesheet is
now the single source of the constraint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Cite the upstream issue for the breadcrumbs slot propagation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: retrigger checks — workflow runs were never created for the previous push

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dognose24 added a commit that referenced this pull request Aug 7, 2026
…resets, no comparison (#50971)

* Premium Analytics: post detail parity — overflow fixes, timezone, no comparison

Applies to the post/email detail page the fixes proven on the video
detail page: the breadcrumb/summary overflow bugs (contain: inline-size
plus the breadcrumbs-slot shrink shim), aspect-ratio: 1 so the boot
shell's img reset cannot squash the featured image, post-views bucket
keys parsed as site-local calendar dates (parseSiteDateTime, UTC-12
regression test), and block-size nits.

The page's design has no period-over-period comparison: the post-views
and email-time-series widgets drop their comparison series, the route
normalizes comparison params away, and DateFiltersPanel gains a minimal
showComparison prop so the Compare control hides at the existing
fixed-bar call site.

Moving the panel onto the summary's title row (per the mock) is
deferred until the preset measurement rework lands (WOOA7S-1816) — it
would collide head-on, the same reason the video-detail PR dropped its
filters row. Rebased onto trunk accordingly: nothing here depends on
the video-detail branch anymore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Pass comparison params through the post-detail route

Mirror the video-detail review outcome (#50970): the page renders no
comparison and its widgets ignore the params, but the breadcrumb's
dashboard link reads the URL state back out, so stripping them lost the
user's comparison settings on a Dashboard → Post → Dashboard round trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Premium Analytics: align comparison pass-through comments with the route contract

* Strip comparison params from injected widget reportParams and drop the parseISO fallback

Per review: the page-wide no-comparison invariant now holds by
construction — usePostDetailTabs injects comparison-stripped
reportParams (via the new omitComparisonReportParams helper) into every
layout entry, so comparison-capable widgets like UTM insights and
highlights can no longer render deltas from URL state. The URL keeps
the comparison params for the breadcrumb round trip.

Also remove the parseISO fallback in use-post-views: bucket dates come
from format(start, 'yyyy-MM-dd') so parseSiteDateTime cannot fail, and
the fallback would silently reintroduce the browser-local day shift;
an unparseable bucket now drops the point instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Record the crypto-js deprecation in pnpm-lock.yaml

The npm registry marked crypto-js@4.2.0 deprecated after this lock was
written, so the lock check's fresh resolution now expects the
deprecation note. Registry-metadata drift only; no dependency change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Put the date filter presets on the summary title row per the mocks

One header row per the design mocks: title left, presets right,
vertically centered. The summary grows into free space and absorbs the
squeeze down to a 400px floor (title ellipsis); its inline-size
containment keeps a long title from wrapping the row.

Known rough edge, deferred to #51088: the panel self-measures its root,
which in this shrink-to-fit slot always sees its own content width — so
the presets keep their full layout and narrow rows degrade poorly. The
external-measurement wiring (containerElement / reservedInlineSize)
ships there to keep this PR free of shared-component changes beyond the
already-reviewed showComparison prop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Drop the route-level breadcrumb shim in favor of the component-owned fix

Superseded by #51085: the ':has(> nav)' rule targets StatsBreadcrumbs'
display:contents trail wrapper (no box for min-inline-size to act on)
and the nav rule is covered by the component stylesheet. Tracked
upstream as WordPress/gutenberg#81297.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: retrigger checks — workflow runs were never created for the previous push

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dognose24 added a commit that referenced this pull request Aug 7, 2026
…mparison from its widgets (#51082)

* Premium Analytics: post detail parity — overflow fixes, timezone, no comparison

Applies to the post/email detail page the fixes proven on the video
detail page: the breadcrumb/summary overflow bugs (contain: inline-size
plus the breadcrumbs-slot shrink shim), aspect-ratio: 1 so the boot
shell's img reset cannot squash the featured image, post-views bucket
keys parsed as site-local calendar dates (parseSiteDateTime, UTC-12
regression test), and block-size nits.

The page's design has no period-over-period comparison: the post-views
and email-time-series widgets drop their comparison series, the route
normalizes comparison params away, and DateFiltersPanel gains a minimal
showComparison prop so the Compare control hides at the existing
fixed-bar call site.

Moving the panel onto the summary's title row (per the mock) is
deferred until the preset measurement rework lands (WOOA7S-1816) — it
would collide head-on, the same reason the video-detail PR dropped its
filters row. Rebased onto trunk accordingly: nothing here depends on
the video-detail branch anymore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Pass comparison params through the post-detail route

Mirror the video-detail review outcome (#50970): the page renders no
comparison and its widgets ignore the params, but the breadcrumb's
dashboard link reads the URL state back out, so stripping them lost the
user's comparison settings on a Dashboard → Post → Dashboard round trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Premium Analytics: align comparison pass-through comments with the route contract

* Strip comparison params from injected widget reportParams and drop the parseISO fallback

Per review: the page-wide no-comparison invariant now holds by
construction — usePostDetailTabs injects comparison-stripped
reportParams (via the new omitComparisonReportParams helper) into every
layout entry, so comparison-capable widgets like UTM insights and
highlights can no longer render deltas from URL state. The URL keeps
the comparison params for the breadcrumb round trip.

Also remove the parseISO fallback in use-post-views: bucket dates come
from format(start, 'yyyy-MM-dd') so parseSiteDateTime cannot fail, and
the fallback would silently reintroduce the browser-local day shift;
an unparseable bucket now drops the point instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Premium Analytics: restore the video detail date filters and strip comparison from its widgets

WOOA7S-1816: the date filter presets were dropped from the video detail
page in #50970 because they collided with the preset-measurement rework;
now that #50906 has landed, restore them using the same construction as
post detail (DateFiltersPanel with showComparison=false, fixed above the
scroll container).

Also port the comparison-strip construction from #50971: inject
omitComparisonReportParams()-stripped reportParams into every layout
entry so comparison-capable widgets (highlights) cannot render deltas
from URL state, while the URL keeps the params for the breadcrumb round
trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Record the crypto-js deprecation in pnpm-lock.yaml

The npm registry marked crypto-js@4.2.0 deprecated after this lock was
written, so the lock check's fresh resolution now expects the
deprecation note. Registry-metadata drift only; no dependency change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Clarify that the video detail comparison strip is defensive

All three current video-detail widgets already ignore comparison params
in their own query mapping; the injection exists so the page-wide
invariant holds by construction (matching post detail) rather than
relying on each widget. Adjust the code comment and changelog wording
accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Put the date filter presets on the summary title row per the mocks

One header row per the design mocks: title left, presets right,
vertically centered. The summary grows into free space and absorbs the
squeeze down to a 400px floor (title ellipsis); its inline-size
containment keeps a long title from wrapping the row.

Known rough edge, deferred to #51088: the panel self-measures its root,
which in this shrink-to-fit slot always sees its own content width — so
the presets keep their full layout and narrow rows degrade poorly. The
external-measurement wiring (containerElement / reservedInlineSize)
ships there to keep this PR free of shared-component changes beyond the
already-reviewed showComparison prop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Put the video detail date filter presets on the summary title row

Same composition as post detail: one header row, title left and presets
right, 400px summary floor with inline-size containment. The panel's
step-down on narrow rows needs external measurement and ships
separately (#51088).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Drop the route-level breadcrumb shim in favor of the component-owned fix

Superseded by #51085: the ':has(> nav)' rule targets StatsBreadcrumbs'
display:contents trail wrapper (no box for min-inline-size to act on)
and the nav rule is covered by the component stylesheet. Tracked
upstream as WordPress/gutenberg#81297.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Drop the video detail route-level breadcrumb shim in favor of #51085

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: retrigger checks — workflow runs were never created for the previous push

* ci: retrigger checks — workflow runs were never created for the previous push

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code [Package] Premium Analytics [Status] UI Changes Add this to PRs that change the UI so documentation can be updated. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants