Skip to content

frontend: settings search#4151

Draft
shonsirsha wants to merge 1 commit into
BitBoxSwiss:masterfrom
shonsirsha:frontend-search-settings
Draft

frontend: settings search#4151
shonsirsha wants to merge 1 commit into
BitBoxSwiss:masterfrom
shonsirsha:frontend-search-settings

Conversation

@shonsirsha
Copy link
Copy Markdown
Collaborator

@shonsirsha shonsirsha commented May 17, 2026

Adds search to the settings area so users can find settings without manually scanning each tab.

Behaviour:

  • Typing in the settings search field filters matches across General, Manage device, Advanced settings, and About.
  • Results are grouped by destination page and navigate directly to the matching settings page with the item highlighted (border flashes 3x).
  • Device-only settings are shown only when a device is available. Same with bluetooth, their entries are also gated by device Bluetooth support.

Technical notes:

  • Introduces shared settings search descriptors, grouping/filtering helpers, and unit coverage for matching and availability rules.
  • Adds the reusable clearable SearchInput variant and keeps the search term in the URL query params.

@shonsirsha
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review!

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@shonsirsha shonsirsha force-pushed the frontend-search-settings branch 7 times, most recently from 86fc55c to 5bf0fd2 Compare May 24, 2026 14:49
@shonsirsha
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

@shonsirsha Sure, triggering a new review of the PR now!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@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: 5

♻️ Duplicate comments (1)
frontends/web/src/routes/settings/bb02-settings.tsx (1)

40-40: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Rename BB02Settings props typing to match the TProps rule.

TWrapperProps is used as a component props type for BB02Settings, which breaks the project convention. Keep TProps for one component and inline the other component’s intersection type to avoid introducing another props-type alias name.

♻️ Minimal fix
-type TWrapperProps = TCommonProps & TPagePropsWithSettingsTabs;
-
 type TProps = TCommonProps;
@@
-const BB02Settings = ({ deviceID, devices, hasAccounts }: TWrapperProps) => {
+const BB02Settings = ({ deviceID, devices, hasAccounts }: TCommonProps & TPagePropsWithSettingsTabs) => {
As per coding guidelines, "Component props types must be named `TProps` (prefixed with `T`)."

Also applies to: 52-52

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/web/src/routes/settings/bb02-settings.tsx` at line 40, The props
type alias TWrapperProps used for the BB02Settings component violates the
convention requiring component props to be named TProps; rename TWrapperProps to
TProps for BB02Settings and replace the other component’s separate alias by
inlining its intersection type (TCommonProps & TPagePropsWithSettingsTabs) where
that component expects props, ensuring any other occurrence (see the second
instance around lines ~52) is updated similarly so only one alias named TProps
is used per component.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontends/web/src/components/forms/search-input.module.css`:
- Around line 24-27: The CSS rule for the clear button currently targets only
img (.clearButton img) so SVG icon components won't get the 20px sizing; update
the selector for the .clearButton sizing rule to target both img and svg (or use
a group/:is() selector) so icon components rendered as <svg> receive the same
height/width styling—locate the .clearButton rule in search-input.module.css and
change its selector accordingly.

In `@frontends/web/src/routes/settings/components/settings-search-content.tsx`:
- Around line 67-90: The page group renders a title even when all its entries
yield non-navigable URLs; update the rendering in the SETTINGS_SEARCH_PAGE_ORDER
loop to first map/filter pageSearchResults using
getSettingsSearchResultURL(firstDeviceID) to produce a list of navigable items,
return null if that filtered list is empty, and then render the group (SubTitle
and SettingsItem) by iterating over the filtered items (use searchResult.id,
searchResult.title and navigate(url) as before) so no empty pageGroup or
orphaned title is rendered.

In `@frontends/web/src/routes/settings/mobile-settings.tsx`:
- Around line 44-46: WithSettingsTabs already renders its own Tabs, so the
mobile settings file is mounting Tabs twice which causes duplicate
TabWithVersionCheck/useLoad(getVersion) calls; remove the inner <Tabs /> usage
in frontends/web/src/routes/settings/mobile-settings.tsx (leave
<WithSettingsTabs devices={devices} hasAccounts={hasAccounts}> without a nested
<Tabs />) or refactor so WithSettingsTabs only renders children when explicitly
provided, ensuring TabWithVersionCheck and useLoad(getVersion) run exactly once.

In `@frontends/web/src/routes/settings/settings-search.test.ts`:
- Around line 130-140: The test assertions rely on ambient env flags provided by
'`@/utils/env`' so make them deterministic by mocking that module in this test;
for the "export logs" case mock the debug/build flag (e.g. isDebugBuild or
similar exported name) to false before calling
getItems()/filterSettingsSearchItems, and for the "screen lock" case mock the
mobile-app flag (e.g. isMobileApp or isNativeMobile) to false, using your test
runner's module-mocking API (jest.mock/vi.mock) at the top of
settings-search.test.ts so getItems() observes the mocked env values and the
expectations become runner-independent.

In `@frontends/web/src/routes/settings/use-settings-search.ts`:
- Around line 30-37: The code is incorrectly deriving the target device from
Object.keys(devices)[0] which depends on object key order; change it to use the
actual active/selected device ID (e.g., the settings or component prop that
represents the current device) instead of the first key. Replace uses of
deviceId/device/device === 'bitbox02'/bb02DeviceId with the activeDeviceId
(falling back to a safe default or undefined), and pass that activeDeviceId into
useLoad (i.e., useLoad(activeDeviceId ? () => getDeviceInfo(activeDeviceId) :
null, [activeDeviceId])) so deviceInfoResult and deviceInfo reflect the real
selected device. Ensure all references to devices and bb02DeviceId are updated
to use that active device variable.

---

Duplicate comments:
In `@frontends/web/src/routes/settings/bb02-settings.tsx`:
- Line 40: The props type alias TWrapperProps used for the BB02Settings
component violates the convention requiring component props to be named TProps;
rename TWrapperProps to TProps for BB02Settings and replace the other
component’s separate alias by inlining its intersection type (TCommonProps &
TPagePropsWithSettingsTabs) where that component expects props, ensuring any
other occurrence (see the second instance around lines ~52) is updated similarly
so only one alias named TProps is used per component.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ff3e2423-fa3d-4793-8c02-73daa6b779b6

📥 Commits

Reviewing files that changed from the base of the PR and between ca1002e and 5bf0fd2.

📒 Files selected for processing (26)
  • CHANGELOG.md
  • frontends/web/src/components/forms/search-input.module.css
  • frontends/web/src/components/forms/search-input.tsx
  • frontends/web/src/locales/en/app.json
  • frontends/web/src/routes/device/no-device-connected.tsx
  • frontends/web/src/routes/router.tsx
  • frontends/web/src/routes/settings/about.tsx
  • frontends/web/src/routes/settings/advanced-settings.tsx
  • frontends/web/src/routes/settings/bb02-settings.module.css
  • frontends/web/src/routes/settings/bb02-settings.tsx
  • frontends/web/src/routes/settings/components/settings-content.module.css
  • frontends/web/src/routes/settings/components/settings-content.test.tsx
  • frontends/web/src/routes/settings/components/settings-content.tsx
  • frontends/web/src/routes/settings/components/settings-search-content.module.css
  • frontends/web/src/routes/settings/components/settings-search-content.test.tsx
  • frontends/web/src/routes/settings/components/settings-search-content.tsx
  • frontends/web/src/routes/settings/components/tabs.module.css
  • frontends/web/src/routes/settings/components/tabs.tsx
  • frontends/web/src/routes/settings/general.module.css
  • frontends/web/src/routes/settings/general.tsx
  • frontends/web/src/routes/settings/mobile-settings.tsx
  • frontends/web/src/routes/settings/settings-search.test.ts
  • frontends/web/src/routes/settings/settings-search.ts
  • frontends/web/src/routes/settings/use-settings-highlight.ts
  • frontends/web/src/routes/settings/use-settings-search.test.tsx
  • frontends/web/src/routes/settings/use-settings-search.ts
💤 Files with no reviewable changes (1)
  • frontends/web/src/routes/settings/general.module.css

Comment thread frontends/web/src/components/forms/search-input.module.css Outdated
Comment thread frontends/web/src/routes/settings/mobile-settings.tsx Outdated
Comment thread frontends/web/src/routes/settings/settings-search.test.ts
Comment thread frontends/web/src/routes/settings/use-settings-search.ts
@shonsirsha shonsirsha force-pushed the frontend-search-settings branch from 5bf0fd2 to aa9fc69 Compare May 25, 2026 01:22
@shonsirsha shonsirsha force-pushed the frontend-search-settings branch from aa9fc69 to faf5219 Compare May 25, 2026 01:25
@shonsirsha shonsirsha requested a review from thisconnect May 25, 2026 01:25
@shonsirsha shonsirsha marked this pull request as ready for review May 25, 2026 01:26
@shonsirsha shonsirsha requested a review from a team as a code owner May 25, 2026 01:26
};

[0, 100, 300].forEach(delay => {
window.setTimeout(scrollToHighlightedItem, delay);
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.

please add a comment why the 0, 100 and 300 delay.

Also I am not sure where when and how the scrolling works, could you mention in the commit message how to test or what the intention is?

sections,
}: TProps) => {
const highlightedItemID = useSettingsHighlight();
const highlightedItemRef = useRef<HTMLDivElement | null>(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.

not sure what highlightedItemRef is used for, it seems to be forever null maybe leftover code, remove?


return (
<>
{sections.map(section => {
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.

nit: outer fragment has no purpose.

Suggested change
{sections.map(section => {
return sections.map(section => {

const updateSearchTerm = (value: string) => {
setSearchTerm(value);
const nextSearchParams = new URLSearchParams(searchParams);
if (value.toLowerCase().trim()) {
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.

Suggested change
if (value.toLowerCase().trim()) {
if (value.trim()) {


useEffect(() => {
setSearchTerm(searchTermFromParams);
}, [searchTermFromParams]);
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.

The next function takes a value, sets setSearchTerm, but then uses searchParams for nextSearchParams. I find it a bit hard mentally to follow.

GPT comment on this effect:

That’s usually fine, but it creates dual sources of truth: React state / URL params

You could simplify by making URL params the sole source of truth and removing local state entirely.

I think with that this hook would become a bit simpler to follow.

<Input ref={ref} type="text" {...inputProps}>
{String(inputProps.value ?? '').trim().length > 0 ? (
<button
aria-label={clearButtonLabel}
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.

nit: just hardcode aria-label={t('generic.close')} and remove the prop.


type TProps = TClearProps | TSearchProps;

export const SearchInput = forwardRef<HTMLInputElement, TProps>((props, ref) => {
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.

With this PR there are now different "variant's" so I checked the other 2 places where <SearchInput> is used.

Not directly related to this PR, but in the other 2 places 'ref' is used to scroll to the results, but in both places the original list is replaced by the actually found results. Sometimes it quickly starts scrolling but then immediately updates the list with the found results stopping the scrolling a spit second later.

The search in settings has scrolling but doesn't need this ref to do so.

So it looks to me that we could drop ref here and the scrolling in address list and account overview.

clearButtonLabel={t('generic.close')}
onChange={event => updateSearchTerm(event.currentTarget.value)}
onClear={() => updateSearchTerm('')}
placeholder={t('generic.search')}
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.

The placeholder of this input field is "Search…" (t('generic.search')), in account it is hardcoded to "Search transactions..." (no translation) and the one in address-list is "Search address" (t('addresses.searchPlaceholder')).

Not a blocker, but would be nice to translate the account search.


const whenAccountsExist = ({ hasAccounts }: TSettingsSearchContext) => hasAccounts;
const whenDeviceExists = ({ deviceIDs }: TSettingsSearchContext) => deviceIDs.length > 0;
const whenDeviceSupportsBluetooth = ({ deviceInfo }: TSettingsSearchContext) => !!deviceInfo?.bluetooth;
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.

It is not ideal to have to maintain the same logic for showing setting-items in two times, in search results (here) and for the actual settings.

Would it make sense to import these functions to use the code to conditionally render the settings items i.e. in bb02-settings etc.?

@shonsirsha shonsirsha marked this pull request as draft May 28, 2026 04:48
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.

2 participants