Skip to content

Desktop: Resolves #14763: Add search support for settings screen#14769

Closed
Kartikeya-guthub wants to merge 21 commits intolaurent22:devfrom
Kartikeya-guthub:desktop-settings-search
Closed

Desktop: Resolves #14763: Add search support for settings screen#14769
Kartikeya-guthub wants to merge 21 commits intolaurent22:devfrom
Kartikeya-guthub:desktop-settings-search

Conversation

@Kartikeya-guthub
Copy link
Copy Markdown

@Kartikeya-guthub Kartikeya-guthub commented Mar 16, 2026

Resolves #14763

Problem

The desktop configuration screen currently does not provide a way to search settings, which makes it difficult to quickly locate specific options when many settings are available. Users must manually scan through sections even when they already know the setting name or part of its description.

Solution

This change adds a search field to the desktop settings screen that filters visible settings by matching setting labels, descriptions, and section titles in a case-insensitive way.

The existing SearchInput component is reused so behaviour remains consistent with other parts of the application. The search field is placed in the sidebar at the top, like in the mobile app, and includes a clear button similar to the regular search field. The right panel displays all matching settings, while sections without matching settings remain visible in the sidebar but are grayed out and not clickable. By default, all matching settings are displayed, and users can click sections to filter to just those sections.

Filtering logic was extracted into a helper to keep ConfigScreen simpler and testable. Empty and null label/description values are handled safely to avoid crashes. Search also applies to plugin settings, and the search term is highlighted in labels, descriptions, and section titles.

This change only affects filtering behaviour in the desktop configuration screen and does not modify setting values or persistence.

Test Plan

  • Added unit tests for settings search matching logic
  • Open Tools → Options
  • Verify all settings appear when search is empty
  • Verify the search field appears at the top of the sidebar
  • Search using a setting label (for example: language)
  • Search using text present in a setting description
  • Search using a section title
  • Verify matching settings appear in the right panel
  • Verify sections without matching settings remain visible but grayed out and not clickable
  • Verify clicking a section filters results to that section
  • Verify plugin settings are included in search results
  • Verify matching text is highlighted in labels, descriptions, and section titles
  • Clear the search field and verify full settings list returns

Tested with NVDA on Windows.

*The new settings search field is reachable via keyboard, announced correctly as an editable search field, and can be used to filter settings without a mouse.

*Matching results remain navigable by keyboard, and focus behaves normally when moving between the sidebar and filtered settings.

Video

2026-03-19.19-33-01.mp4

AI Assistance Disclosure

AI-assisted: Yes

Used AI assistance for:

  • reviewing PR description wording
  • refining search helper edge-case handling

All code, testing, and final verification were manually reviewed before submission.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 16, 2026

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a search feature to the desktop Config screen: new search state and handlers, search/filter/highlight utilities, sidebar search input with enable/disable section logic and keyboard navigation, search-aware rendering for settings/plugins, and a “No settings match your search.” state.

Changes

Cohort / File(s) Summary
Main Config screen
packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx
Adds searchQuery/searchSelectedSectionName, search handlers, section-filtering and matching logic, search-aware rendering flow, adjusted sidebar selection during search, conditional custom-screen rendering, and “no results” handling.
Search utils & tests
packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts, packages/app-desktop/gui/ConfigScreen/configScreenUtilsImpl.tsx, packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts
New re-export entry-point and implementation for normalization/matching/highlighting (settingMatchesSearch, highlightSearchText, searchQueryIsEmpty) plus Jest tests covering matching scenarios.
Sidebar & tab UI
packages/app-desktop/gui/ConfigScreen/Sidebar.tsx, packages/app-desktop/gui/ConfigScreen/styles/setting-tab-panel.scss
Sidebar accepts search props, renders SearchInput, computes enabled/disabled tabs with isSectionMatch, restricts keyboard navigation to matched tabs, and adds .-search-results modifier for tab-panel layout.
Setting controls & plugins
packages/app-desktop/gui/ConfigScreen/controls/*, .../controls/plugins/*
Prop additions (searchQuery / highlightQuery) across SettingComponent, SettingLabel, SettingDescription, SettingHeader, PluginBox, PluginsStates; components use highlightSearchText; PluginsStates exposes getSearchText().
Search input styling
packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx
Adjusts search input/icon colours and placeholder styling; no behavioural changes.
Styles
packages/app-desktop/gui/ConfigScreen/style.scss, packages/app-desktop/gui/ConfigScreen/styles/setting-tab-panel.scss
Adds .config-screen mark.config-search-highlight styling and .setting-tab-panel.-search-results modifier to adjust layout for search results.
Build/test ignores
.eslintignore, .gitignore
Ignore generated JS artifacts for new search utils/tests from linting and TypeScript build outputs.
Mobile plugin assets
packages/app-mobile/pluginAssets/index.js, packages/app-mobile/pluginAssets/index.web.js
Updated asset manifest hash and minor reordering/removal of abc/abc_render.js entries.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Config as ConfigScreen
    participant Utils as SearchUtils
    participant Sidebar as Sidebar
    participant View as SettingsView

    User->>Config: Type query / Click clear
    Config->>Utils: Normalise query, split terms, compute matches
    Utils-->>Config: filteredSections, matchingSectionNames
    Config->>Sidebar: update searchQuery, searchStarted, provide isSectionMatch callback
    Config->>View: compute sectionsToRender, pass searchQuery for highlights
    alt no results
        View-->>User: "No settings match your search."
    else results
        View-->>User: Render matching settings with highlighted matches
    end
Loading

Suggested labels

accessibility

Suggested reviewers

  • personalizedrefrigerator
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the main change: adding search support to the settings screen, with issue reference.
Description check ✅ Passed The description comprehensively explains the problem, solution, implementation approach, and test plan; it is fully related to the changeset.
Linked Issues check ✅ Passed All requirements from #14763 are met: search field in sidebar, filtering by label/description/section title, all matching settings displayed, non-matching sections grayed out, section-based filtering, plugin search support, and term highlighting.
Out of Scope Changes check ✅ Passed All changes directly support the search feature implementation. Mobile asset updates appear to be auto-generated build artifacts. No extraneous modifications detected.
Pr Description Must Follow Guidelines ✅ Passed The pull request description includes clear sections addressing Problem (issue #14763 requesting search functionality in settings), Solution (detailed implementation of search with filtering and highlighting), and Test Plan (manual accessibility testing with NVDA on Windows, video, and AI disclosure). The comments history explicitly confirms the contributor updated the PR description to include Problem/Solution/Test Plan sections as per the GSoC PR guidelines, and the provided PR summary demonstrates all three required elements are present and properly documented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added enhancement Feature requests and code enhancements desktop All desktop platforms search labels Mar 16, 2026
@Kartikeya-guthub
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@Kartikeya-guthub
Copy link
Copy Markdown
Author

recheck

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx`:
- Around line 469-472: When filteredSections is empty the code computes
selectedSectionName = '' and passes an empty filteredSections into Sidebar,
which later allows keyboard events to emit an undefined section and crash in
sidebar_selectionChange when accessing event.section.name; fix by guarding
empty-state in ConfigScreen: ensure selectedSectionName is set only when
filteredSections.length > 0 (e.g., keep previous selection only if it exists,
otherwise set selectedSectionName to null/undefined or to the first
filteredSections[0].name), compute selectedSection from that guarded value
(selectedSection), and prevent rendering or calling Sidebar (or pass a safe
non-empty sections array) when filteredSections is empty so
sidebar_selectionChange cannot receive an undefined event.section; update any
code that assumes selectedSectionName is a string accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 539c7c5f-1afa-4294-a7ea-c5f0a30a0738

📥 Commits

Reviewing files that changed from the base of the PR and between c9dace8 and baa62c3.

📒 Files selected for processing (1)
  • packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx

Comment thread packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx Outdated
github-actions Bot added a commit that referenced this pull request Mar 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts (1)

19-26: Minor type signature mismatch for description.

The SettingItem interface defines description as (_appType: AppType) => string, but this helper returns a function that ignores its parameter. While this works at runtime, it's slightly inconsistent with the actual interface.

✨ Proposed fix
 const makeSetting = (label: string, description: string): SettingItem => {
 	const setting: SearchableSetting = {
 		label: () => label,
-		description: () => description,
+		description: (_appType: AppType) => description,
 	};

 	return setting as SettingItem;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts` around
lines 19 - 26, The helper makeSetting returns a description function that
ignores the AppType parameter, which mismatches SettingItem's description
signature; update makeSetting so description is declared as (appType: AppType)
=> string (e.g., description: (appType: AppType) => description) or explicitly
accept and ignore the parameter (description: (_appType: AppType) =>
description) to match SettingItem/SearchableSetting types; keep the return cast
to SettingItem and adjust the label similarly if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts`:
- Around line 5-17: Tests are validating a duplicated local copy of the private
settingMatchesSearch implementation from ConfigScreen.tsx, so extract that logic
into a shared utility (e.g., ConfigScreen.utils.ts) and export
settingMatchesSearch (same signature: (md: SearchableSetting, searchQuery:
string): boolean) so both ConfigScreen.tsx (replace the private method with an
import) and ConfigScreen.search.test.ts can import and test the single
implementation; if you cannot extract now, add a clear comment above the local
copy in ConfigScreen.search.test.ts pointing to the original private method in
ConfigScreen.tsx to indicate intentional duplication.

---

Nitpick comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts`:
- Around line 19-26: The helper makeSetting returns a description function that
ignores the AppType parameter, which mismatches SettingItem's description
signature; update makeSetting so description is declared as (appType: AppType)
=> string (e.g., description: (appType: AppType) => description) or explicitly
accept and ignore the parameter (description: (_appType: AppType) =>
description) to match SettingItem/SearchableSetting types; keep the return cast
to SettingItem and adjust the label similarly if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7565403-7355-4dd1-9536-2b5896bdf17b

📥 Commits

Reviewing files that changed from the base of the PR and between 195fe73 and 42a8ba2.

📒 Files selected for processing (1)
  • packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts

Comment thread packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts (1)

6-13: Consider aligning mock signature with actual SettingItem type.

The description method in SettingItem accepts an AppType parameter: description?: (_appType: AppType) => string. The mock ignores this parameter, which works but could mask issues if the implementation changes.

♻️ Optional: Match the actual signature
+import { AppType, SettingItem } from '@joplin/lib/models/Setting';
-import { SettingItem } from '@joplin/lib/models/Setting';

 const makeSetting = (label: string, description: string): SettingItem => {
 	const setting: SearchableSetting = {
 		label: () => label,
-		description: () => description,
+		description: (_appType: AppType) => description,
 	};

 	return setting as SettingItem;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts` around
lines 6 - 13, The mock factory makeSetting returns a SearchableSetting but its
description currently ignores the AppType parameter; update the description
property to match the real SettingItem signature by declaring description: (
_appType: AppType ) => description so it accepts the AppType argument (and add
an import or type reference for AppType if required). Keep label as () => label
and ensure the returned setting is still cast as SettingItem.
packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx (1)

480-491: Omit the optional inputRef prop for cleaner code.

Since SearchInput has inputRef as an optional prop (marked with ? in its Props interface), passing inputRef={null} is unnecessary. Remove it entirely for better readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx` around lines 480 -
491, In the ConfigScreen component (searchInputComp rendering), remove the
unnecessary explicit inputRef={null} prop passed to the SearchInput component
since inputRef is optional; update the JSX where SearchInput is used
(searchInputComp) to omit the inputRef prop and keep the other props
(value={this.state.searchQuery}, onChange={this.onSearchQueryChange},
onSearchButtonClick={this.onSearchButtonClick},
searchStarted={!!this.state.searchQuery}, placeholder) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts`:
- Around line 1-45: This file has CRLF line endings; convert all line endings in
ConfigScreen.search.test.ts to LF to satisfy ESLint. Update the file so every
line uses Unix-style line endings (LF) — no changes to code like SettingItem,
makeSetting, settingMatchesSearch, or the test blocks — just normalize the file
ending format and re-run linter/CI to confirm the error is resolved.

In `@packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts`:
- Around line 1-16: The file has CRLF line endings causing ESLint errors;
convert all lines in packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts
to LF endings. Open the file containing the settingMatchesSearch function and
normalize line endings (e.g., via your editor or run a tool such as sed -i
's/\r$//' on the file) so the whole file uses LF instead of CRLF, then re-run
lint to confirm the CRLF errors are resolved.

---

Nitpick comments:
In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts`:
- Around line 6-13: The mock factory makeSetting returns a SearchableSetting but
its description currently ignores the AppType parameter; update the description
property to match the real SettingItem signature by declaring description: (
_appType: AppType ) => description so it accepts the AppType argument (and add
an import or type reference for AppType if required). Keep label as () => label
and ensure the returned setting is still cast as SettingItem.

In `@packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx`:
- Around line 480-491: In the ConfigScreen component (searchInputComp
rendering), remove the unnecessary explicit inputRef={null} prop passed to the
SearchInput component since inputRef is optional; update the JSX where
SearchInput is used (searchInputComp) to omit the inputRef prop and keep the
other props (value={this.state.searchQuery},
onChange={this.onSearchQueryChange},
onSearchButtonClick={this.onSearchButtonClick},
searchStarted={!!this.state.searchQuery}, placeholder) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4cf175f9-ab49-4a72-8468-66eee00f75b3

📥 Commits

Reviewing files that changed from the base of the PR and between 42a8ba2 and 8c74075.

📒 Files selected for processing (3)
  • packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts
  • packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx
  • packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts

Comment thread packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts Outdated
Comment on lines +1 to +16
import { AppType, SettingItem } from '@joplin/lib/models/Setting';

export const settingMatchesSearch = (md: Pick<SettingItem, 'label' | 'description'>, searchQuery: string): boolean => {
if (!searchQuery) return true;

const q = searchQuery.trim().toLowerCase();
if (!q) return true;

const labelText = md.label ? md.label() : '';
const descriptionText = md.description ? md.description(AppType.Desktop) : '';

const label = String(labelText || '').toLowerCase();
const description = String(descriptionText || '').toLowerCase();

return label.includes(q) || description.includes(q);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix CRLF line endings to LF.

ESLint reports CRLF line endings throughout this file. The repository expects LF line endings for consistency. Convert all line endings to LF before merging.

🔧 Fix line endings

Run the following command to convert line endings:

sed -i 's/\r$//' packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts

Or configure your editor to use LF line endings for this repository.

🧰 Tools
🪛 ESLint

[error] 1-2: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 2-3: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 3-16: Prefer default export on a file with single export.

(import/prefer-default-export)


[error] 3-4: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 4-5: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 5-6: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 6-7: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 7-8: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 8-9: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 9-10: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 10-11: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 11-12: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 12-13: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 13-14: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 14-15: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)


[error] 15-16: Expected linebreaks to be 'LF' but found 'CRLF'.

(linebreak-style)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts` around lines 1 -
16, The file has CRLF line endings causing ESLint errors; convert all lines in
packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts to LF endings. Open
the file containing the settingMatchesSearch function and normalize line endings
(e.g., via your editor or run a tool such as sed -i 's/\r$//' on the file) so
the whole file uses LF instead of CRLF, then re-run lint to confirm the CRLF
errors are resolved.

@Daeraxa
Copy link
Copy Markdown
Contributor

Daeraxa commented Mar 17, 2026

As per the PR guidelines could you please add a video and AI disclosure section to your PR?

@Kartikeya-guthub
Copy link
Copy Markdown
Author

As per the PR guidelines could you please add a video and AI disclosure section to your PR?

Updated PR description with Problem / Solution / Test Plan, added video and AI disclosure as requested.

@laurent22
Copy link
Copy Markdown
Owner

Hello, for information, the issue has been updated with detailed spec on how it should work:

#14763

If you would like to continue working on this pull request, please check the spec.

@Kartikeya-guthub
Copy link
Copy Markdown
Author

Hello, for information, the issue has been updated with detailed spec on how it should work:

#14763

If you would like to continue working on this pull request, please check the spec.

Done👍

@personalizedrefrigerator
Copy link
Copy Markdown
Collaborator

Thank you for working on this!

Since this modifies an important part of the desktop app's UI:

  • How accessible is the new implementation?
  • Would you be willing to test the change with a screen reader? (See Joplin: Testing with a screen reader).
    • If not, I can try to do so at some point in the near future, but it may take me a few days.

Comment thread packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx Outdated
@Kartikeya-guthub
Copy link
Copy Markdown
Author

Thank you for working on this!

Since this modifies an important part of the desktop app's UI:

  • How accessible is the new implementation?

  • Would you be willing to test the change with a screen reader? (See Joplin: Testing with a screen reader).

    • If not, I can try to do so at some point in the near future, but it may take me a few days.

Tested with NVDA on Windows and added a short video as well.

The search field is reachable by keyboard, announced correctly by the screen reader, and filtering works without using a mouse. Matching results remain keyboard accessible, and focus behaves normally while navigating between the sidebar and filtered settings.

@coderabbitai coderabbitai Bot added accessibility Related to accessibility and removed search labels Mar 19, 2026
@Kartikeya-guthub
Copy link
Copy Markdown
Author

I've merged recent changes from dev: The new accessibility tests should now run in CI.

The accessibility checker has flagged two potential issues:

  • Elements with role="tablist" cannot contain buttons or inputs.

    • Based on the spec, tablist should only contain elements with the tab role.
  • The settings search input has insufficient text contrast.

Details

Thanks @personalizedrefrigerator — I synced with the latest dev changes and addressed the two accessibility issues flagged by CI:

  • moved the search input outside the tablist so the tablist now contains only tab elements
  • adjusted the search input styling to improve text contrast

I also rechecked keyboard navigation for the sidebar/search flow locally. CI should reflect the updated state after this push.

Copy link
Copy Markdown
Collaborator

@personalizedrefrigerator personalizedrefrigerator left a comment

Choose a reason for hiding this comment

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

Thank you for working on this! I've tested the pull request locally.

Observed issues:

  • The up/down arrows only sometimes changes the selected tab:
Screencast.from.2026-03-23.08-52-36.webm

To reproduce: 1) start searching, 2) use the tab key to move keyboard focus to the tab list, and 3) press the up/down arrows. For me, this scrolls the tab list, rather than changes tabs. However, after clicking on the tab list, the arrow keys can successfully change the selected tab.

  • Low contrast in highlight marks while searching: It's difficult to read highlighted text in light mode (see WCAG 2.2: Contrast/minimum):
    screenshot: Very low contrast in search markers

};

describe('ConfigScreen search', () => {
test('empty query returns true', () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

(Optional) It may be possible to simplify the tests in this file using test.each:

  • Since all tests in this file call settingMatchesSearch and assert different output, test.each may help reduce duplicate setup code and make it easier to add new tests.
  • However, the setup logic is already quite simple. test.each might not provide much benefit.

Comment on lines +85 to +88
chunks.push(React.createElement('mark', {
key: `${index}-${end}`,
className: 'config-search-highlight',
}, source.slice(index, end)));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In general, please prefer JSX to React.createElement (this file may need to be changed to a .tsx file).

@coderabbitai coderabbitai Bot added enhancement Feature requests and code enhancements desktop All desktop platforms search and removed accessibility Related to accessibility labels Mar 24, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/app-desktop/gui/ConfigScreen/Sidebar.tsx (2)

160-178: ⚠️ Potential issue | 🟡 Minor

Prevent disabled entries from still navigating to #.

The early return stops the section change, but the anchor's default click still runs. A greyed-out section can therefore still move the hash/scroll position, so it is not actually “not clickable”.

🔧 Suggested change
-				onClick={() => {
-					if (disabled) return;
-					props.onSelectionChange({ section: section });
+				onClick={event => {
+					event.preventDefault();
+					if (disabled) return;
+					props.onSelectionChange({ section });
 				}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-desktop/gui/ConfigScreen/Sidebar.tsx` around lines 160 - 178,
The anchor StyledListItem still navigates to '#' when disabled because the
onClick early-return doesn't prevent the anchor's default; update the onClick
handler on StyledListItem to accept the click event and call
event.preventDefault() when disabled (e.g., onClick={(e) => { if (disabled) {
e.preventDefault(); return; } props.onSelectionChange({ section }); }}), and
optionally remove or set href to undefined when disabled to avoid native
navigation; refer to StyledListItem, onClick, href='#', tabIndex and disabled to
locate the change.

160-170: ⚠️ Potential issue | 🟠 Major

Search mode is no longer a real tab interface.

When a query is active, the right pane can show combined matches from multiple sections. In that state these controls are filters, not tabs, so role='tablist' / role='tab' gives assistive tech the wrong interaction model. Either switch the search state to button/list semantics, or add an explicit “All results” tab/panel pair.

Also applies to: 233-242

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-desktop/gui/ConfigScreen/Sidebar.tsx` around lines 160 - 170,
The items rendered by StyledListItem are currently given role='tab' but when a
query/search is active they act as filters (not true tabs); update the logic so
that the role and ARIA attributes switch based on the search state (e.g., use a
boolean like isSearching or queryActive): when not searching keep role='tab'
with aria-controls/aria-selected and tabIndex as-is; when searching change role
to a button/list-item (e.g., role='button' or role='listitem') and replace
aria-selected with the appropriate attribute (aria-pressed for buttons or
aria-checked if using a listbox), and adjust tabIndex accordingly; apply the
same conditional change for the other similar block (lines around 233-242) and
keep references such as StyledListItem, buttonRefs.current[index], and the
id/setting-tab-... naming consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app-desktop/gui/ConfigScreen/configScreenUtilsImpl.tsx`:
- Around line 8-16: The search logic is inconsistent: searchTerms(tokenizes) but
settingMatchesSearch and related match helpers still require the whole
normalized phrase to appear in a single field, so multi-word queries (e.g.,
"editor markdown") can highlight both tokens but fail to filter; fix by changing
settingMatchesSearch (and the other match helpers referenced) to use
searchTerms(searchQuery) and verify that every token is found somewhere in the
setting's searchable text (e.g., name, description, key, category) — i.e., build
a normalized combined field(s) or check each token against all searchable fields
and only include the setting if all tokens are present (order/contiguity not
required), keeping the existing highlightSearchText tokenization and reusing
searchTerms to ensure consistent behavior.

---

Outside diff comments:
In `@packages/app-desktop/gui/ConfigScreen/Sidebar.tsx`:
- Around line 160-178: The anchor StyledListItem still navigates to '#' when
disabled because the onClick early-return doesn't prevent the anchor's default;
update the onClick handler on StyledListItem to accept the click event and call
event.preventDefault() when disabled (e.g., onClick={(e) => { if (disabled) {
e.preventDefault(); return; } props.onSelectionChange({ section }); }}), and
optionally remove or set href to undefined when disabled to avoid native
navigation; refer to StyledListItem, onClick, href='#', tabIndex and disabled to
locate the change.
- Around line 160-170: The items rendered by StyledListItem are currently given
role='tab' but when a query/search is active they act as filters (not true
tabs); update the logic so that the role and ARIA attributes switch based on the
search state (e.g., use a boolean like isSearching or queryActive): when not
searching keep role='tab' with aria-controls/aria-selected and tabIndex as-is;
when searching change role to a button/list-item (e.g., role='button' or
role='listitem') and replace aria-selected with the appropriate attribute
(aria-pressed for buttons or aria-checked if using a listbox), and adjust
tabIndex accordingly; apply the same conditional change for the other similar
block (lines around 233-242) and keep references such as StyledListItem,
buttonRefs.current[index], and the id/setting-tab-... naming consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d3a3cef9-727b-42f5-a175-fc87bb27a997

📥 Commits

Reviewing files that changed from the base of the PR and between 72dca8f and 52bf800.

📒 Files selected for processing (7)
  • .eslintignore
  • .gitignore
  • packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts
  • packages/app-desktop/gui/ConfigScreen/Sidebar.tsx
  • packages/app-desktop/gui/ConfigScreen/configScreenUtils.ts
  • packages/app-desktop/gui/ConfigScreen/configScreenUtilsImpl.tsx
  • packages/app-desktop/gui/ConfigScreen/style.scss
✅ Files skipped from review due to trivial changes (4)
  • packages/app-desktop/gui/ConfigScreen/style.scss
  • .gitignore
  • .eslintignore
  • packages/app-desktop/gui/ConfigScreen/ConfigScreen.search.test.ts

Comment thread packages/app-desktop/gui/ConfigScreen/configScreenUtilsImpl.tsx Outdated
@coderabbitai coderabbitai Bot added accessibility Related to accessibility and removed enhancement Feature requests and code enhancements desktop All desktop platforms search labels Mar 24, 2026
@Kartikeya-guthub
Copy link
Copy Markdown
Author

Thank you for working on this! I've tested the pull request locally.

Observed issues:

  • The up/down arrows only sometimes changes the selected tab:

Screencast.from.2026-03-23.08-52-36.webm
To reproduce: 1) start searching, 2) use the tab key to move keyboard focus to the tab list, and 3) press the up/down arrows. For me, this scrolls the tab list, rather than changes tabs. However, after clicking on the tab list, the arrow keys can successfully change the selected tab.

  • Low contrast in highlight marks while searching: It's difficult to read highlighted text in light mode (see WCAG 2.2: Contrast/minimum):
    screenshot: Very low contrast in search markers

Thanks for the detailed review sir,

I’ve addressed the reported issues:

  • Fixed arrow key navigation in the sidebar tab list — up/down arrows now reliably change the selected tab when focused via keyboard and no longer scroll the list.
  • Improved contrast of search highlight marks to ensure readability (WCAG-compliant in both themes).
  • Replaced React.createElement usage with JSX for highlight rendering.

I’ve re-tested keyboard navigation and search behavior locally, including keyboard-only interaction.

I’ve also attached a short video demonstrating:

Joplin.DEV.-.C__Users_Lenovo_.config_joplindev-desktop.-.Options.2026-03-24.17-45-12.mp4
  • keyboard navigation with arrow keys
  • search + highlight visibility

Please let me know if anything still needs adjustment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app-desktop/gui/ConfigScreen/Sidebar.tsx (1)

162-178: ⚠️ Potential issue | 🟠 Major

Cancel the anchor default before bailing out on disabled tabs.

Line 176 returns early, but Line 162 still makes this a real link. A mouse click on a disabled section will therefore still follow #, so the item is not actually non-clickable. Prevent the default click first, then handle selection.

🛠️ Proposed fix
-				onClick={() => {
-					if (disabled) return;
-					props.onSelectionChange({ section: section });
-				}}
+				onClick={event => {
+					event.preventDefault();
+					if (disabled) return;
+					props.onSelectionChange({ section });
+				}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app-desktop/gui/ConfigScreen/Sidebar.tsx` around lines 162 - 178,
The anchor still navigates to '#' when clicking a disabled tab because the
onClick handler returns early after the browser has already processed the click;
update the click handler on the anchor (the element that sets ref via
buttonRefs.current[index] and uses Setting.isSubSection and
props.onSelectionChange) to call event.preventDefault() as the first step, then
if disabled return, otherwise call props.onSelectionChange({ section }). Ensure
the handler signature accepts the click event so you can call preventDefault()
before any early returns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/app-desktop/gui/ConfigScreen/Sidebar.tsx`:
- Around line 162-178: The anchor still navigates to '#' when clicking a
disabled tab because the onClick handler returns early after the browser has
already processed the click; update the click handler on the anchor (the element
that sets ref via buttonRefs.current[index] and uses Setting.isSubSection and
props.onSelectionChange) to call event.preventDefault() as the first step, then
if disabled return, otherwise call props.onSelectionChange({ section }). Ensure
the handler signature accepts the click event so you can call preventDefault()
before any early returns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05bcbe5b-8090-4923-abac-a2f2aadced4c

📥 Commits

Reviewing files that changed from the base of the PR and between 52bf800 and ef41abd.

📒 Files selected for processing (1)
  • packages/app-desktop/gui/ConfigScreen/Sidebar.tsx

// These screens handle their own loading/saving of settings and have bespoke rendering.
// When screenComp is null, it means we are viewing the regular settings.
const screenComp = this.state.screenName ? <div className="config-screen-content-wrapper" style={{ overflow: 'scroll', flex: 1 }}>{this.screenFromName(this.state.screenName)}</div> : null;
const screenComp = !isSearchActive && selectedSection?.isScreen && this.state.screenName ? <div className="config-screen-content-wrapper" style={{ overflow: 'scroll', flex: 1 }}>{this.screenFromName(this.state.screenName)}</div> : null;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Style: For readability, consider breaking this up into multiple lines. For example,

Suggested change
const screenComp = !isSearchActive && selectedSection?.isScreen && this.state.screenName ? <div className="config-screen-content-wrapper" style={{ overflow: 'scroll', flex: 1 }}>{this.screenFromName(this.state.screenName)}</div> : null;
const screenComp = !isSearchActive && selectedSection?.isScreen && this.state.screenName ? (
<div
className="config-screen-content-wrapper"
style={{ overflow: 'scroll', flex: 1 }}
>{this.screenFromName(this.state.screenName)}</div>
) : null;

<>
{screenComp}
<div style={containerStyle}>
<div style={{ display: 'flex', flex: 1, flexDirection: 'column' }}>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Prefer RSCSS for new components.

Comment on lines +573 to +574
<div style={{ display: 'flex', flex: 1, flexDirection: 'column' }}>
<div style={{ ...theme.textStyle, maxWidth: 640 }}>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

As above, please use RSCSS for new styles. See spec/desktop_styling for details.

@Kartikeya-guthub
Copy link
Copy Markdown
Author

Thanks for the suggestions @personalizedrefrigerator

I've updated the screenComp assignment to a multi-line format for better readability.

I’m also refactoring the new inline styles to use RSCSS and aligning them with the guidelines in spec/desktop_styling.

Let me know if anything else stands out.

@Kartikeya-guthub
Copy link
Copy Markdown
Author

@personalizedrefrigerator Hi, I’ve addressed all the requested changes and updated the PR accordingly.

Would appreciate a re-review when you have time. Thanks!

@laurent22
Copy link
Copy Markdown
Owner

Hello, unfortunately we no longer need this pull request as there's a duplicate here, that we will probably merge instead: #14820

@laurent22 laurent22 closed this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accessibility Related to accessibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for searching for settings in the desktop app

4 participants