From 181514df6307319400598f561bbb2875a2d2f7f1 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Tue, 10 Mar 2026 15:57:16 +0900 Subject: [PATCH 1/2] feat: Refactor ignored apps to use bundle IDs directly Simplify ignored platforms logic by storing bundle IDs instead of app names. Extract dropdown options logic into separate function and remove unnecessary name-to-bundleId conversions. Update form handling to work directly with bundle IDs and improve variable naming for clarity. --- .../general/notification-app-options.test.ts | 34 ++++++++ .../general/notification-app-options.ts | 22 +++++ .../src/settings/general/notification.tsx | 87 +++++++------------ 3 files changed, 89 insertions(+), 54 deletions(-) create mode 100644 apps/desktop/src/settings/general/notification-app-options.test.ts create mode 100644 apps/desktop/src/settings/general/notification-app-options.ts diff --git a/apps/desktop/src/settings/general/notification-app-options.test.ts b/apps/desktop/src/settings/general/notification-app-options.test.ts new file mode 100644 index 0000000000..6a203b8d68 --- /dev/null +++ b/apps/desktop/src/settings/general/notification-app-options.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, test } from "vitest"; + +import { getIgnoredAppOptions } from "./notification-app-options"; + +describe("getIgnoredAppOptions", () => { + test("returns installed app matches for partial searches", () => { + const options = getIgnoredAppOptions({ + allInstalledApps: [ + { id: "us.zoom.xos", name: "Zoom Workplace" }, + { id: "com.tinyspeck.slackmacgap", name: "Slack" }, + ], + ignoredPlatforms: [], + inputValue: "zoom", + defaultIgnoredBundleIds: [], + }); + + expect(options).toEqual([{ id: "us.zoom.xos", name: "Zoom Workplace" }]); + }); + + test("filters out already ignored and default ignored apps", () => { + const options = getIgnoredAppOptions({ + allInstalledApps: [ + { id: "us.zoom.xos", name: "Zoom Workplace" }, + { id: "com.tinyspeck.slackmacgap", name: "Slack" }, + { id: "com.openai.chat", name: "ChatGPT" }, + ], + ignoredPlatforms: ["com.tinyspeck.slackmacgap"], + inputValue: "", + defaultIgnoredBundleIds: ["com.openai.chat"], + }); + + expect(options).toEqual([{ id: "us.zoom.xos", name: "Zoom Workplace" }]); + }); +}); diff --git a/apps/desktop/src/settings/general/notification-app-options.ts b/apps/desktop/src/settings/general/notification-app-options.ts new file mode 100644 index 0000000000..41330ce440 --- /dev/null +++ b/apps/desktop/src/settings/general/notification-app-options.ts @@ -0,0 +1,22 @@ +import type { InstalledApp } from "@hypr/plugin-detect"; + +export function getIgnoredAppOptions({ + allInstalledApps, + ignoredPlatforms, + inputValue, + defaultIgnoredBundleIds, +}: { + allInstalledApps: InstalledApp[] | undefined; + ignoredPlatforms: string[]; + inputValue: string; + defaultIgnoredBundleIds: string[] | undefined; +}) { + return (allInstalledApps ?? []).filter((app) => { + const matchesSearch = app.name + .toLowerCase() + .includes(inputValue.toLowerCase()); + const notAlreadyAdded = !ignoredPlatforms.includes(app.id); + const notDefaultIgnored = !(defaultIgnoredBundleIds ?? []).includes(app.id); + return matchesSearch && notAlreadyAdded && notDefaultIgnored; + }); +} diff --git a/apps/desktop/src/settings/general/notification.tsx b/apps/desktop/src/settings/general/notification.tsx index 7a632724ab..4801adbd62 100644 --- a/apps/desktop/src/settings/general/notification.tsx +++ b/apps/desktop/src/settings/general/notification.tsx @@ -21,6 +21,8 @@ import { import { Switch } from "@hypr/ui/components/ui/switch"; import { cn } from "@hypr/utils"; +import { getIgnoredAppOptions } from "./notification-app-options"; + import { useConfigValues } from "~/shared/config"; import * as settings from "~/store/tinybase/store/settings"; @@ -72,12 +74,7 @@ export function NotificationSettingsView() { return allInstalledApps?.find((a) => a.id === bundleId)?.name ?? bundleId; }; - const nameToBundleId = (name: string) => { - return allInstalledApps?.find((a) => a.name === name)?.id ?? name; - }; - - const isDefaultIgnored = (appName: string) => { - const bundleId = nameToBundleId(appName); + const isDefaultIgnored = (bundleId: string) => { return defaultIgnoredBundleIds?.includes(bundleId) ?? false; }; @@ -121,7 +118,7 @@ export function NotificationSettingsView() { notification_event: configs.notification_event, notification_detect: configs.notification_detect, respect_dnd: configs.respect_dnd, - ignored_platforms: configs.ignored_platforms.map(bundleIdToName), + ignored_platforms: configs.ignored_platforms, mic_active_threshold: configs.mic_active_threshold, }, listeners: { @@ -133,9 +130,7 @@ export function NotificationSettingsView() { handleSetNotificationEvent(value.notification_event); handleSetNotificationDetect(value.notification_detect); handleSetRespectDnd(value.respect_dnd); - handleSetIgnoredPlatforms( - JSON.stringify(value.ignored_platforms.map(nameToBundleId)), - ); + handleSetIgnoredPlatforms(JSON.stringify(value.ignored_platforms)); handleSetMicActiveThreshold(value.mic_active_threshold); }, }); @@ -144,42 +139,33 @@ export function NotificationSettingsView() { configs.notification_event || configs.notification_detect; const ignoredPlatforms = form.getFieldValue("ignored_platforms"); - const installedApps = allInstalledApps?.map((app) => app.name) ?? []; - - const filteredApps = installedApps.filter((app) => { - const matchesSearch = app.toLowerCase().includes(inputValue.toLowerCase()); - const notAlreadyAdded = !ignoredPlatforms.includes(app); - const notDefaultIgnored = !isDefaultIgnored(app); - return matchesSearch && notAlreadyAdded && notDefaultIgnored; + const dropdownOptions = getIgnoredAppOptions({ + allInstalledApps, + ignoredPlatforms, + inputValue, + defaultIgnoredBundleIds, }); - const showCustomOption = - inputValue.trim() && - !filteredApps.some((app) => app.toLowerCase() === inputValue.toLowerCase()); - - const dropdownOptions = showCustomOption - ? [inputValue.trim(), ...filteredApps] - : filteredApps; - - const handleAddIgnoredApp = (appName: string) => { - const trimmedName = appName.trim(); + const handleAddIgnoredApp = (bundleId: string) => { if ( - !trimmedName || - ignoredPlatforms.includes(trimmedName) || - isDefaultIgnored(trimmedName) + !bundleId || + ignoredPlatforms.includes(bundleId) || + isDefaultIgnored(bundleId) ) { return; } - form.setFieldValue("ignored_platforms", [...ignoredPlatforms, trimmedName]); + form.setFieldValue("ignored_platforms", [...ignoredPlatforms, bundleId]); void form.handleSubmit(); setInputValue(""); setShowDropdown(false); setSelectedIndex(0); }; - const handleRemoveIgnoredApp = (app: string) => { - const updated = ignoredPlatforms.filter((a: string) => a !== app); + const handleRemoveIgnoredApp = (bundleId: string) => { + const updated = ignoredPlatforms.filter( + (appId: string) => appId !== bundleId, + ); form.setFieldValue("ignored_platforms", updated); void form.handleSubmit(); }; @@ -187,8 +173,9 @@ export function NotificationSettingsView() { const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && inputValue.trim()) { e.preventDefault(); - if (dropdownOptions.length > 0) { - handleAddIgnoredApp(dropdownOptions[selectedIndex]); + const selectedApp = dropdownOptions[selectedIndex]; + if (selectedApp) { + handleAddIgnoredApp(selectedApp.id); } } else if (e.key === "ArrowDown") { e.preventDefault(); @@ -206,9 +193,9 @@ export function NotificationSettingsView() { !inputValue && ignoredPlatforms.length > 0 ) { - const lastApp = ignoredPlatforms[ignoredPlatforms.length - 1]; - if (!isDefaultIgnored(lastApp)) { - handleRemoveIgnoredApp(lastApp); + const lastBundleId = ignoredPlatforms[ignoredPlatforms.length - 1]; + if (!isDefaultIgnored(lastBundleId)) { + handleRemoveIgnoredApp(lastBundleId); } } }; @@ -317,11 +304,11 @@ export function NotificationSettingsView() { className="flex min-h-[38px] w-full cursor-text flex-wrap items-center gap-2 rounded-md border p-2" onClick={() => inputRef.current?.focus()} > - {ignoredPlatforms.map((app: string) => { - const isDefault = isDefaultIgnored(app); + {ignoredPlatforms.map((bundleId: string) => { + const isDefault = isDefaultIgnored(bundleId); return ( - {app} + {bundleIdToName(bundleId)} {isDefault && ( (default) @@ -343,7 +330,7 @@ export function NotificationSettingsView() { variant="ghost" size="sm" className="ml-0.5 h-3 w-3 p-0 hover:bg-transparent" - onClick={() => handleRemoveIgnoredApp(app)} + onClick={() => handleRemoveIgnoredApp(bundleId)} > @@ -370,10 +357,9 @@ export function NotificationSettingsView() {
{dropdownOptions.map((app, index) => { - const isCustom = showCustomOption && index === 0; return ( ); })} From 0a95845e66efd0e3d0fe0ca82f9d38fc6f64cee8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 08:39:02 +0000 Subject: [PATCH 2/2] fix: wrap resolve_base test with env guard to prevent race condition Co-Authored-By: John --- crates/storage/src/vault/path.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/storage/src/vault/path.rs b/crates/storage/src/vault/path.rs index 818376cd5b..514a973160 100644 --- a/crates/storage/src/vault/path.rs +++ b/crates/storage/src/vault/path.rs @@ -365,9 +365,10 @@ mod tests { let global_base = temp.path().to_path_buf(); let default_base = temp.path().join("default"); - let result = resolve_base(&global_base, &default_base); - - assert_eq!(result, default_base); + with_env(VAULT_BASE_ENV_VAR, None, || { + let result = resolve_base(&global_base, &default_base); + assert_eq!(result, default_base); + }); } }