diff --git a/apps/desktop/src/lib/sidebarTypeSearch.ts b/apps/desktop/src/lib/sidebarTypeSearch.ts
new file mode 100644
index 0000000000..2b14e39e5e
--- /dev/null
+++ b/apps/desktop/src/lib/sidebarTypeSearch.ts
@@ -0,0 +1,25 @@
+export interface SidebarTypeSearchKeyEvent {
+ key: string;
+ ctrlKey?: boolean;
+ metaKey?: boolean;
+ altKey?: boolean;
+ isComposing?: boolean;
+}
+
+export function isEditableSidebarTypeSearchTarget(target: EventTarget | null): boolean {
+ if (!(target instanceof HTMLElement)) return false;
+ return !!target.closest("input, textarea, select, [contenteditable='true'], .cm-editor");
+}
+
+export function sidebarTypeSearchNextQuery(currentQuery: string, event: SidebarTypeSearchKeyEvent): string | null {
+ if (event.ctrlKey || event.metaKey || event.altKey || event.isComposing) return null;
+
+ if (event.key === "Backspace") {
+ if (!currentQuery) return null;
+ return currentQuery.slice(0, -1);
+ }
+
+ if (event.key.length !== 1) return null;
+ if (event.key === " " && !currentQuery.trim()) return null;
+ return `${currentQuery}${event.key}`;
+}
diff --git a/packages/app-tests/sidebarTypeSearch.test.ts b/packages/app-tests/sidebarTypeSearch.test.ts
new file mode 100644
index 0000000000..152d5c4dbe
--- /dev/null
+++ b/packages/app-tests/sidebarTypeSearch.test.ts
@@ -0,0 +1,20 @@
+import { strict as assert } from "node:assert";
+import { test } from "vitest";
+import { sidebarTypeSearchNextQuery } from "../../apps/desktop/src/lib/sidebarTypeSearch.ts";
+
+test("starts sidebar search from printable keys", () => {
+ assert.equal(sidebarTypeSearchNextQuery("", { key: "e" }), "e");
+ assert.equal(sidebarTypeSearchNextQuery("ec", { key: "m" }), "ecm");
+});
+
+test("removes one search character with Backspace", () => {
+ assert.equal(sidebarTypeSearchNextQuery("ecm", { key: "Backspace" }), "ec");
+ assert.equal(sidebarTypeSearchNextQuery("", { key: "Backspace" }), null);
+});
+
+test("ignores shortcuts and non-searchable initial space", () => {
+ assert.equal(sidebarTypeSearchNextQuery("", { key: "f", metaKey: true }), null);
+ assert.equal(sidebarTypeSearchNextQuery("", { key: "ArrowDown" }), null);
+ assert.equal(sidebarTypeSearchNextQuery("", { key: " " }), null);
+ assert.equal(sidebarTypeSearchNextQuery("order", { key: " " }), "order ");
+});