Skip to content

Comments

Activate tabs containing search matches#14053

Merged
cderv merged 15 commits intomainfrom
feature/search-tab-activation
Feb 20, 2026
Merged

Activate tabs containing search matches#14053
cderv merged 15 commits intomainfrom
feature/search-tab-activation

Conversation

@cderv
Copy link
Collaborator

@cderv cderv commented Feb 18, 2026

Important

This PR is to be merged AFTER #14049 which as a prerequisite fix

When navigating to a page with ?q=term in the URL, search matches inside inactive Bootstrap tabs are invisible because the matching tab is not activated. This builds on #14049 (mark-clearing fix) to also activate tabs that contain highlighted matches.

How it works

After highlight() wraps matches in <mark> elements during DOMContentLoaded, a pageshow listener activates any tab panes containing marks. The pageshow timing is key — tabsets.js (loaded as an ES module) registers its own pageshow handler during module execution, which runs before DOMContentLoaded. By registering our handler during DOMContentLoaded, listener ordering guarantees we run after tabsets.js restores tab state from localStorage.

This means search activation wins over stored tab preferences, which is the intended UX — if you searched for something in the Python tab, you want to see the Python tab regardless of your stored language preference.

Design decisions

  • bootstrap.Tab.show() used instead of .click() to avoid triggering group sync in tabsets.js. Search activation is intentionally local to the tabset containing the match.
  • Nested tabsets supported: walks ancestor .tab-pane elements, groups by .tab-content, activates outermost first.
  • If the active tab already contains a match, no switch occurs (avoids unnecessary tab changes).
  • {once: true} and !event.persisted guard on the pageshow listener for clean teardown.

Test plan

  • Playwright tests (5 scenarios, all pass Chromium/Firefox/WebKit):
    1. Inactive tab activation
    2. Active-tab-has-match stays
    3. Outside-tabs no change
    4. Nested tabsets both activate
    5. Grouped + localStorage override
  • TDD verified: 3/5 tests fail against stock v1.9.20, all pass with this code
  • Chrome MCP timing experiments confirmed pageshow deferral approach

Fixes #14047 (partial — tab activation aspect)

@posit-snyk-bot
Copy link
Collaborator

posit-snyk-bot commented Feb 18, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cderv cderv marked this pull request as draft February 18, 2026 13:57
cderv and others added 4 commits February 20, 2026 17:21
Complements the persistence test by confirming that quarto-hrChanged
does clear marks after the 1000ms registration delay elapses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR #13442 removed scroll-based highlight clearing. This commit adds
Playwright tests verifying: scroll events never clear marks, query-change
clearing still works, and no marks appear without ?q= parameter.

Restores test fixture files dropped during rebase (source files were in
the skipped JS fix commit). Updates changelog to credit @jtbayly and
reference both #9802 and #14047.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cderv cderv force-pushed the feature/search-tab-activation branch from d1f5047 to 6ac6527 Compare February 20, 2026 16:48
@cderv
Copy link
Collaborator Author

cderv commented Feb 20, 2026

Rebased on the updated fix/issue-14047 branch (which is now rebased on main with #13442 merged). The tab activation code is unchanged — no conflicts with the scroll-clearing removal.

All 24 Playwright tests pass (3 highlight + 5 tabset × 3 browsers).

@cderv cderv marked this pull request as ready for review February 20, 2026 17:03
cderv and others added 9 commits February 20, 2026 18:10
The previous test dispatched quarto-hrChanged/quarto-sectionChanged
custom events, which were leftovers from when search code listened
to those events. Since #13442 removed those listeners entirely,
dispatching those events tested nothing. Replace with actual scroll
behavior which is the real user scenario from #14047.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace .aa-DetachedSearchButton and .aa-Input with ARIA role
locators (getByRole('button'), getByRole('searchbox')). These are
resilient to Algolia autocomplete class name changes and follow
Playwright best practices used elsewhere in the test suite.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After highlight() adds <mark> tags to matching text, the new
activateTabsWithMatches() function detects marks inside inactive
Bootstrap tab panes and activates the first one. If the already-active
tab in a tabset contains a match, no switch occurs.

Includes test fixture for manual and future Playwright testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Escape pane IDs with CSS.escape() before interpolating into
querySelector selectors to handle special characters in
user-derived tab IDs. Use closest(".tab-content") instead of
parentElement for resilience to intermediate wrapper elements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
activateTabsWithMatches() now walks up ancestor tab panes so matches
inside nested tabsets activate both outer and inner tabs (outermost
first via DOM depth sorting).

Tab activation is deferred from DOMContentLoaded to a pageshow listener.
This ensures it runs after tabsets.js restores grouped tab state from
localStorage, so search results override stored tab preferences without
flash. Listener ordering is guaranteed because tabsets.js (a module)
registers its pageshow handler before DOMContentLoaded fires.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Five tests covering tab activation when navigating to a page with ?q=:
- Match in inactive tab activates that tab
- Match in already-active tab keeps it (no unnecessary switch)
- Match outside tabs leaves tab state unchanged
- Match in nested tabset activates both outer and inner tabs
- Search activation overrides localStorage tab preference

TDD verified: tests fail against stock v1.9.20 (3 of 5 fail where
tab activation is needed), pass with our changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap bootstrap.Tab.show() call in try-catch so a single broken tab
does not prevent activation of remaining tabsets with search matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…owing

Replace empty catch block with console.debug so Bootstrap Tab API
failures leave a diagnostic breadcrumb. Also add explicit format: html
to search-tabsets test fixture for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cderv cderv force-pushed the feature/search-tab-activation branch from 6ac6527 to 4739866 Compare February 20, 2026 17:21
cderv and others added 2 commits February 20, 2026 18:43
…tests

Tab pane IDs like `tabset-1-2` are auto-generated by Pandoc and break if
the fixture structure changes. Replace with `getByRole('tab', { name })`
and `toHaveClass(/active/)`, matching the pattern used in html-tabsets.spec.ts.
Section-scoped locators disambiguate duplicate tab names (R, Python).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After activating tabs that contain search matches, scroll the first
visible <mark> element into view. This completes the UX: the user
lands on the page with the correct tab open and the match centered
in the viewport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Base automatically changed from fix/issue-14047 to main February 20, 2026 17:52
@cderv cderv merged commit 11314dc into main Feb 20, 2026
2 checks passed
@cderv cderv deleted the feature/search-tab-activation branch February 20, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search highlights (<mark>) cleared within milliseconds, invisible to users

2 participants