Repository navigation
Manager: Restructure review feature state, layout integration, and thumbnail pipeline - #35351
Conversation
…les into CollectionGrid The review thumbnail message layer had a renaming re-export module (iframeResizeMessage.ts) that gave the shared iframe.resize wire protocol a second vocabulary. The parser now lives in shared/constants/iframe-resize.ts next to the payload type it validates, and the frame-style helpers live in CollectionGrid next to the Frame styled component that consumes their CSS variables. Also drops the tautological THUMBNAIL_BOOTSTRAP_SCALE constant test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Slot accounting for the review thumbnail concurrency cap was split across the
scheduler (start/finish), the hook (a settle timer after src assignment), and
CollectionGrid (iframe load/error). The scheduler now owns the deadline: it
starts a slot timer when it starts a task and auto-releases when it fires.
Callers get an opaque {forceStart, release} handle instead of a task object
with public mutable booleans, so the accounting-drift clamp is no longer
needed. Also fixes the header comment that pointed at a CollectionGrid
explanation which no longer exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add 'review' to the API_ViewMode union and introduce a single nav-availability policy (api.getNavAvailability) in the manager-api layout module. Layout, the sidebar menu tool, the fullscreen tool, keyboard shortcuts, and the review summary portal now consume that policy instead of each re-deriving sidebar suppression from isReviewManagerRoute. Also unify the isPagesShown derivation between Layout and Sidebar via isPagesViewMode, and share the review-collection story predicate as isReviewCollectionStoryRoute. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…achine One pure reducer now drives each thumbnail cell through idle -> queued -> booting -> measured -> settled (evicted -> idle), with src, the loading spinner, and remembered dimensions derived from a single state value instead of four independent atoms reset from three places. Each event (enqueued, started, resized, settled, evicted) is one atomic dispatch, which deletes the load-generation counter: stale rAF callbacks and timers are now ignored by phase guards instead of generation bookkeeping. Supporting changes: - Per-hook message listener comparing event.source against the iframe's contentWindow, replacing the module-level handler registry, its never-removed window listener, and the attach/detach-on-load dance. - Visibility is only the mount/evict IntersectionObserver pair (hysteresis via differing rootMargins); the bespoke preload geometry and ResizeObserver are gone. While the summary portal parks the grid off-screen (previewsPaused), the whole visibility subsystem freezes: one gate instead of three. - Deleted the dead frameRef and its cast in CollectionGrid, and the speculative SSR/IntersectionObserver guards in browser-only manager code. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Channel handlers and actions now write directly to the module-level reviewStore instead of React state that was mirrored into the store after the fact. ReviewProvider shrinks to channel wiring plus the derivation of index/status/route-dependent values, pushed via a single setDerived path. - Delete the setState mirror, the internalPendingReview side channel, the setState(getState(), next) self-copy, and the notificationKey trigger; ReviewNotification subscribes to the real state/pendingReview fields. - The store owns isInReviewMode including its session persistence, so the path-change resync effect is gone; enterReviewMode/exitReviewMode update the store (optimistically on entry, rolled back on failure). - Replace the REVIEW_EXITING_SESSION_KEY storage mutex with a store field. - dismissReview, acceptPendingReview and getStoryPreviewHref are no longer state fields; they are exported actions / portal-local helpers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pending-update-beats-stale precedence rule was implemented three times (ReviewSummaryPortal pre-masked isStale, SummaryScreen and ReviewToolbarHeader each re-implemented the ternary with diverging guards). It is now derived once in ReviewProvider as a single ReviewBanner value (reusing AttentionBanner's discriminated-union props), and both render sites just spread it into AttentionBanner. The isStale/hasPendingUpdate/ onAcceptPendingUpdate prop triplet and the runtime callback-pairing check are gone; the union type makes the pairing impossible to violate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the body-level fixed portal with a persistent overlay slot on Layout. The review layer now renders in slotOverlay, an always-mounted cell that occupies the grid's content area (the full container on mobile), so the CSS grid supplies the sidebar/panel insets that ReviewSummaryPortal previously re-derived by hand. This deletes useDesktopChromeInsets, the duplicated 600px breakpoint matchMedia listener, the -10000px off-screen parking hack, and the legacy portal-host cleanup. The hidden state is reduced to visibility: hidden plus inert, which was verified empirically to keep thumbnail iframe documents alive without reloading. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…efactor # Conflicts: # code/core/src/manager/components/review/screens/ReviewSummaryPortal.tsx
Keeping it mounted left the previously selected story's iframe alive underneath the summary, so it flashed through for a frame when navigating to a curated story. Unmounting boots a fresh preview per navigation instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The overlay's inert state was gated on a suppression flag set when navigating to a curated story and released only by an effect keyed on the summary route becoming visible. A click that never left the summary route left the flag stuck, so the overlay stayed inert on return. The main preview now unmounts on the summary route, so the suppression mechanism is no longer needed to prevent a stale-story flash. Drop it and gate the overlay purely on the summary route. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: ghengeveld <321738+ghengeveld@users.noreply.github.com>
📝 WalkthroughWalkthroughThis PR introduces a route/view-mode-driven ChangesReview mode, navigation and thumbnail pipeline
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant App
participant Layout
participant ReviewPersistentLayer
participant ReviewSummaryHost
participant reviewStore
App->>Layout: slotOverlay=ReviewPersistentLayer, slotMain=MainPreview
Layout->>ReviewPersistentLayer: render overlay slot
ReviewPersistentLayer->>ReviewSummaryHost: render always-mounted host
ReviewSummaryHost->>reviewStore: read isSummaryVisible/isInReviewMode
reviewStore-->>ReviewSummaryHost: state
ReviewSummaryHost-->>App: toggle visibility/pointerEvents/inert
sequenceDiagram
participant IntersectionObserver
participant usePreviewThumbnail
participant previewScheduler
participant thumbnailReducer
participant iframe
IntersectionObserver->>usePreviewThumbnail: isInView=true
usePreviewThumbnail->>previewScheduler: enqueuePreview(start)
previewScheduler-->>usePreviewThumbnail: PreviewHandle
usePreviewThumbnail->>thumbnailReducer: dispatch(enqueued/started)
iframe->>usePreviewThumbnail: postMessage resize payload
usePreviewThumbnail->>thumbnailReducer: dispatch(resized/settled)
usePreviewThumbnail->>previewScheduler: handle.release()
Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
Comment |
Package BenchmarksCommit: The following packages have significant changes to their size or dependencies:
|
| Before | After | Difference | |
|---|---|---|---|
| Dependency count | 34 | 32 | 🎉 -2 🎉 |
| Self size | 22.41 MB | 22.41 MB | 0 B |
| Dependency size | 22.54 MB | 20.07 MB | 🎉 -2.47 MB 🎉 |
| Bundle Size Analyzer | Link | Link |
parsePath now maps /review/ and /review to viewMode review so manager state matches the layout policy. Rename previewsPaused to summaryHidden across the thumbnail pipeline for clearer intent.
What I did
Behavior-preserving restructuring of the review (agentic/display review) feature, addressing the structural findings of a deep code-quality audit. Three independent tracks, merged here:
1. Review routes as a first-class layout policy (manager-api)
'review'toAPI_ViewModeand introducedgetNavAvailability(): 'shown' | 'hidden' | 'unavailable'inmanager-api/modules/layout.ts— the single place that consults review routing.isReviewManagerRouteconditionals in shared chrome code (Layout.tsx,shortcuts.ts,Toolbar.tsx,tools/menu.tsx, the summary portal) with that one policy.isPagesShownbetweenLayoutandSidebarvia a sharedisPagesViewModeselector (they previously disagreed on review routes), and unified the two divergentisReviewCollectionStoryderivations viaisReviewCollectionStoryRoute.2. Review summary host inside the Layout grid
ReviewSummaryPortal(body-levelposition: fixedportal that re-derived nav/panel geometry by hand, duplicated the 600px breakpoint, and parked itself atleft: -10000pxto keep thumbnail iframes alive).ReviewSummaryHostrendered in a new LayoutslotOverlayinside the always-mounted content cell:position: absolute; inset: 0, hidden viavisibility+inert. The grid supplies geometry for free; iframe persistence verified empirically (visibility: hiddenpreserves iframe documents and in-frame state).3. Review store as single source of truth
reviewStorenow owns core state (state,pendingReview,isStale,isInReviewMode,isExiting, overlay suppression) with explicit actions; channel handlers write to it directly. Deleted the React-state-mirrored-into-store layout effect, theinternalPendingReviewside channel, and thenotificationKey-as-effect-trigger pattern.AttentionBannerProps | null.4. Thumbnail pipeline: explicit phase machine
idle → queued → booting → measured → settled, evict →idle) inpreviewThumbnailState.ts, withsrc, spinner, and frame style derived from one state value. Deleted the load-generation counter, duplicated resets, and the module-levelMap<Window, handler>resize registry (replaced by a per-cell listener comparingevent.source).previewSchedulernow owns the full slot lifecycle (opaque{ forceStart, release }handles, deadline-based auto-release), instead of splitting accounting across three files.iframe.resizepayload parser moved next to its payload type inshared/constants/iframe-resize.ts; the aliasing re-export layer is gone; frame-style helpers live next to theFramecomponent that defines their contract.No intended behavior changes. One known nuance: grid cells first rendered while the summary is hidden now boot when it becomes visible, instead of preloading while parked off-screen.
Checklist for Contributors
Testing
The changes in this PR are covered in the following automated tests:
Manual testing
Caution
This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!
cd code && yarn storybook:uidisplay-reviewtool (or replay one of theReviewPagestories undermanager/review).s,/) are inactive on review routes; the fullscreen and "show sidebar" tools are hidden.Documentation
MIGRATION.MD
Checklist for Maintainers
When this PR is ready for testing, make sure to add
ci:normal,ci:mergedorci:dailyGH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found incode/lib/cli-storybook/src/sandbox-templates.tsDeclare whether manual QA will be needed for this PR during the next release, through
qa:neededorqa:skipMake sure this PR contains one of the labels below:
Available labels
bug: Internal changes that fixes incorrect behavior.maintenance: User-facing maintenance tasks.dependencies: Upgrading (sometimes downgrading) dependencies.build: Internal-facing build tooling & test updates. Will not show up in release changelog.cleanup: Minor cleanup style change. Will not show up in release changelog.documentation: Documentation only changes. Will not show up in release changelog.feature request: Introducing a new feature.BREAKING CHANGE: Changes that break compatibility in some way with current major version.other: Changes that don't fit in the above categories.🦋 Canary release
This pull request has been released as version
0.0.0-pr-35351-sha-068e5c71. Try it out in a new sandbox by runningnpx storybook@0.0.0-pr-35351-sha-068e5c71 sandboxor in an existing project withnpx storybook@0.0.0-pr-35351-sha-068e5c71 upgrade.More information
0.0.0-pr-35351-sha-068e5c71review-quality-refactor068e5c711783000920)To request a new release of this pull request, mention the
@storybookjs/coreteam.core team members can create a new canary release here or locally with
gh workflow run --repo storybookjs/storybook publish.yml --field pr=35351🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes