Skip to content

RSM-2998: Persist Jetpack Search dashboard active tab via URL state#48752

Open
Copilot wants to merge 8 commits into
trunkfrom
copilot/persist-active-tab-across-refresh
Open

RSM-2998: Persist Jetpack Search dashboard active tab via URL state#48752
Copilot wants to merge 8 commits into
trunkfrom
copilot/persist-active-tab-across-refresh

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

Fixes RSM-2998

Why

Users of the Jetpack Search admin dashboard lose their place every time they refresh: the dashboard always lands back on Plan & Usage even if they were working in AI Answers (and soon, Settings). This is especially jarring after saving an experience change, since the refresh snaps them away from the control they just used. This PR keeps users on whichever tab they had selected across refresh, share, and back/forward.

Proposed changes

  • Mirror the active Search dashboard tab into the URL as ?tab=<value> via history.replaceState whenever the user switches tabs (no history-entry spam).
  • Hydrate the initial tab from URLSearchParams on mount, with an allowlist (plan-usage, ai-answers) so unknown or missing values fall back to the default tab.
  • Guard for typeof window === 'undefined' so SSR / non-browser callers still get the default tab.
  • Add unit tests covering: default tab, hydration from a valid ?tab=, ignoring an unknown ?tab= (URL left untouched), and the URL update on user-driven tab change.

Related product discussion/links

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

No. The tab value lives only in the URL on the user's own browser; nothing new is sent to the server or logged.

Testing instructions

  1. Build and activate the Jetpack Search plugin from this branch.
  2. Visit wp-admin > Jetpack > Search. Confirm the dashboard opens on Plan & Usage and the URL has no ?tab= parameter (or has ?tab=plan-usage after you click it).
  3. Click the AI Answers tab. The URL should update to include ?tab=ai-answers without adding a new browser-history entry (back button should still go to the page you came from).
  4. Refresh the page. The dashboard should stay on AI Answers.
  5. Manually edit the URL to ?tab=bogus-value and reload. The dashboard should fall back to Plan & Usage, and the URL should be left as-is (no rewrite).
  6. Use the browser back/forward buttons after switching tabs — no extra history entries should have been added by tab changes.

Copilot AI changed the title [WIP] Update search dashboard to persist active tab on refresh RSM-2998: Persist Jetpack Search dashboard active tab via URL state May 13, 2026
Copilot AI requested a review from adamwoodnz May 13, 2026 00:27
@adamwoodnz
Copy link
Copy Markdown
Contributor

@copilot follow the PR template

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 13, 2026

@copilot follow the PR template

Updated the PR description to match .github/PULL_REQUEST_TEMPLATE.md section structure and content. No code changes were needed for this update (HEAD: 12d9c31).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh gh pr view --json number,title,body,url (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

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), and enable the copilot/persist-active-tab-across-refresh branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack copilot/persist-active-tab-across-refresh

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

@github-actions github-actions Bot added [Package] Search Contains core Search functionality for Jetpack and Search plugins [Status] In Progress [Tests] Includes Tests labels May 13, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

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 May 13, 2026
@adamwoodnz adamwoodnz removed the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label May 13, 2026
@jp-launch-control
Copy link
Copy Markdown

jp-launch-control Bot commented May 13, 2026

Code Coverage Summary

Coverage changed in 1 file.

File Coverage Δ% Δ Uncovered
projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx 60/71 (84.51%) 0.58% 2 ❤️‍🩹

Full summary · PHP report · JS report

@adamwoodnz
Copy link
Copy Markdown
Contributor

@claude review please

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 13, 2026

Claude finished @adamwoodnz's task in 1m 39s —— View job


PR Review

  • Gather context
  • Read changed files
  • Analyze implementation
  • Review tests
  • Post review feedback

The implementation is clean and directly addresses the issue. Here are my findings:

Implementation (dashboard-page.jsx)

Looks good:

  • Lazy-initializer pattern useState( getInitialTab ) is correct — getInitialTab is passed as a function reference, so it only runs once on mount rather than on every render.
  • history.replaceState (not pushState) is the right call — it keeps tab switching out of the browser history stack, avoiding confusing back-button behavior.
  • The typeof window === 'undefined' SSR guard is appropriate for the WordPress/Gutenberg ecosystem.
  • Constants (DEFAULT_TAB, VALID_TABS, TAB_QUERY_PARAM) are a clean single source of truth.

One thing to flag — unknown tab params are silently left in the URL:

In getInitialTab (line 23–31), when the URL contains an invalid tab (e.g. ?tab=unknown), the component falls back to DEFAULT_TAB but the URL is never corrected. The user ends up on the Plan & Usage tab but the URL still reads ?tab=unknown. The test at line 141 explicitly asserts this behavior:

expect( window.location.search ).toContain( 'tab=unknown' );

This is a deliberate design choice, but it's worth noting: if a user bookmarks or shares a URL with an invalid tab param, that stale param will persist silently. An alternative is to call replaceState in getInitialTab when falling back, removing or correcting the invalid param — though that adds complexity. Since this mirrors the issue's suggested approach, it's acceptable as-is.

VALID_TABS will need manual updates when new tabs are added:

VALID_TABS = [ DEFAULT_TAB, 'ai-answers' ] is a separate list from the actual <Tabs.Tab> definitions in the JSX (lines 162–168). These can drift. When a new tab is added to the render tree, a developer has to also remember to add it to VALID_TABS. This is a minor maintainability concern — a comment near VALID_TABS pointing to the tab definitions would help future authors.

Fix this →

replaceState state arg:

window.history.replaceState( {}, '', url.toString() ) passes an empty object, which drops any previous history state entry. In the WordPress admin context this is harmless, but it's worth knowing.


Tests (dashboard-page.test.jsx)

Good coverage of the three key scenarios:

  1. Hydration from a valid URL param (?tab=ai-answers → AI Answers tab selected)
  2. Fallback for invalid URL param (?tab=unknown → Plan & Usage selected, URL unchanged)
  3. URL update on tab click (click AI AnswersreplaceState called with correct URL)

Minor observation — no explicit test for the "no tab param" case:

There's no test that renders with DEFAULT_TEST_URL (no tab param) and asserts Plan & Usage is selected. This is implicitly covered by the existing test at line 98 (which renders without a tab param and works), and the beforeEach always resets to DEFAULT_TEST_URL. Still, an explicit assertion would make the intent clearer. Not a blocker.

Test isolation is correct: The beforeEach resets the URL with replaceState before each test, and all three new tests restore their spies with mockRestore(). No leakage between tests.


Changelog

The changelog entry (RSM-2998-dashboard-persist-active-tab) is correctly formatted with Significance: patch and Type: fixed. The description is clear.


Summary

This is a solid, minimal implementation. The main question for the author is whether leaving ?tab=unknown in the URL on fallback is intentional — the test asserts it is, but it may be worth a brief comment in the code explaining why. Everything else looks correct. No blockers.
| Branch: copilot/persist-active-tab-across-refresh

@adamwoodnz adamwoodnz marked this pull request as ready for review May 13, 2026 02:15
@adamwoodnz
Copy link
Copy Markdown
Contributor

VALID_TABS will need manual updates when new tabs are added:

VALID_TABS = [ DEFAULT_TAB, 'ai-answers' ] is a separate list from the actual <Tabs.Tab> definitions in the JSX (lines 162–168). These can drift. When a new tab is added to the render tree, a developer has to also remember to add it to VALID_TABS. This is a minor maintainability concern — a comment near VALID_TABS pointing to the tab definitions would help future authors.

@copilot add this comment

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 13, 2026

VALID_TABS will need manual updates when new tabs are added:

VALID_TABS = [ DEFAULT_TAB, 'ai-answers' ] is a separate list from the actual <Tabs.Tab> definitions in the JSX (lines 1...

Added the maintainability note above VALID_TABS to keep it aligned with the rendered tab values. Addressed in 73fe91e.

@adamwoodnz adamwoodnz force-pushed the copilot/persist-active-tab-across-refresh branch from 73fe91e to 17c6fea Compare May 13, 2026 08:56
@adamwoodnz adamwoodnz requested a review from kangzj May 13, 2026 09:04
@adamwoodnz adamwoodnz added [Status] Needs Review This PR is ready for review. and removed [Status] In Progress labels May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Search Contains core Search functionality for Jetpack and Search plugins [Status] Needs Review This PR is ready for review. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants