Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 26, 2025

Adds three performance optimizations to PageLayout resize operations to reduce layout thrashing and improve perceived performance during viewport changes.

Changelog

Changed

  • Separated resize update strategies in usePaneWidth.ts:

    • Throttled CSS updates (16ms via rAF): Updates --pane-max-width immediately for smooth visual feedback
    • Debounced full sync (150ms): Defers React state updates, ARIA sync, and localStorage writes until resize stops
  • Extracted resize timing constants to module level:

    • RESIZE_THROTTLE_MS = 16 for CSS-only updates
    • RESIZE_DEBOUNCE_MS = 150 for expensive operations
  • Enhanced cleanup:

    • Cancels both rAF and debounce timers on unmount
    • Removes containment optimizations

Note: Smart breakpoint detection (only calling getComputedStyle() when crossing 1280px) was already implemented correctly. Will-change cleanup on rAF cancel was verified working as designed.

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Performance optimization with no API changes. Existing behavior preserved, just more efficient.

Testing & Reviewing

All existing tests pass (43 usePaneWidth + 17 PageLayout). The dual strategy can be observed by:

  1. Opening Storybook PageLayout example with resizable pane
  2. Slowly resizing viewport - CSS updates happen immediately (~60fps)
  3. Rapidly resizing - expensive operations deferred until resize stops (150ms)

Merge checklist

Original prompt

Summary

Add three additional performance optimizations to the PageLayout drag/resize functionality building on PR #7349.

Changes Required

1. Smart Breakpoint Detection in usePaneWidth.ts

Only call getComputedStyle() when crossing the 1280px breakpoint where --pane-max-width-diff changes. This avoids expensive style recalculations on every resize event.

// In the resize handler, only update maxWidthDiffRef when crossing the breakpoint
const crossedBreakpoint =
  (lastViewportWidth < DEFAULT_PANE_MAX_WIDTH_DIFF_BREAKPOINT &&
    currentViewportWidth >= DEFAULT_PANE_MAX_WIDTH_DIFF_BREAKPOINT) ||
  (lastViewportWidth >= DEFAULT_PANE_MAX_WIDTH_DIFF_BREAKPOINT &&
    currentViewportWidth < DEFAULT_PANE_MAX_WIDTH_DIFF_BREAKPOINT)

if (crossedBreakpoint) {
  maxWidthDiffRef.current = getPaneMaxWidthDiff(paneRef.current)
}

The constant DEFAULT_PANE_MAX_WIDTH_DIFF_BREAKPOINT is already defined and exported from the CSS module (value: 1280).

2. Will-change Cleanup on rAF Cancel in PageLayout.tsx

Ensure will-change is properly cleaned up when rAF is cancelled during handleLostPointerCapture. The cleanup should happen even if the rAF callback never fires:

In the handleLostPointerCapture callback, after cancelling the rAF, ensure removeDraggingStyles is called which already handles will-change cleanup via pane?.style.removeProperty('will-change').

Verify this is working correctly - if rafIdRef.current is not null when lost pointer capture fires, the rAF is cancelled but endDragging() is still called which should clean up. This may already be correct, but verify the flow.

3. Separate Debouncing for Final State Sync vs Visual Updates in usePaneWidth.ts

Currently the resize handler uses a single throttle approach. Change to:

  • Throttled (16ms via rAF): CSS variable updates for immediate visual feedback
  • Debounced (150ms): React state updates, ARIA sync, and localStorage writes

This provides smooth visual updates while deferring expensive React state changes until resize stops.

// Throttle approach for CSS updates
const THROTTLE_MS = 16
let lastUpdateTime = 0
let rafId: number | null = null

// Separate debounce for state sync
const DEBOUNCE_MS = 150
let debounceId: ReturnType<typeof setTimeout> | null = null

const handleResize = () => {
  startResizeOptimizations()
  
  const now = Date.now()
  
  // Throttled CSS-only update for immediate visual feedback
  if (now - lastUpdateTime >= THROTTLE_MS) {
    lastUpdateTime = now
    updateCSSOnly() // Just update --pane-max-width
  } else if (rafId === null) {
    rafId = requestAnimationFrame(() => {
      rafId = null
      lastUpdateTime = Date.now()
      updateCSSOnly()
    })
  }
  
  // Debounced full sync (state, ARIA, cleanup) when resize stops
  if (debounceId !== null) {
    clearTimeout(debounceId)
  }
  debounceId = setTimeout(() => {
    debounceId = null
    syncAll() // Full sync including React state
    endResizeOptimizations()
  }, DEBOUNCE_MS)
}

The updateCSSOnly function should only update the CSS variable:

const updateCSSOnly = () => {
  const actualMax = getMaxPaneWidthRef.current()
  paneRef.current?.style.setProperty('--pane-max-width', `${actualMax}px`)
}

The syncAll function handles the expensive operations (already exists in the codebase).

4. Update cleanup in effect return

Make sure to clean up both rafId and debounceId in the effect cleanup:

return () => {
  if (rafId !== null) cancelAnimationFrame(rafId)
  if (debounceId !== null) clearTimeout(debounceId)
  endResizeOptimizations()
  window.removeEventListener('resize', handleResize)
}

Files to Modify

  1. packages/react/src/PageLayout/usePaneWidth.ts - Smart breakpoint detection and separate throttle/debounce
  2. packages/react/src/PageLayout/PageLayout.tsx - Verify will-change cleanup flow (may already be correct)

Testing

  • Existing tests should continue to pass
  • Update any tests that check timing behavior if needed (tests in usePaneWidth.test.ts may need adjustment for the new throttle+debounce approach)

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@changeset-bot
Copy link

changeset-bot bot commented Dec 26, 2025

⚠️ No Changeset found

Latest commit: 1913597

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copilot AI changed the title [WIP] Add performance optimizations to PageLayout drag/resize Optimize PageLayout resize performance with separate throttle/debounce strategy Dec 26, 2025
Copilot AI requested a review from mattcosta7 December 26, 2025 03:58
@github-actions
Copy link
Contributor

👋 Hi, this pull request contains changes to the source code that github/github-ui depends on. If you are GitHub staff, test these changes with github/github-ui using the integration workflow. Or, apply the integration-tests: skipped manually label to skip these checks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants