···99 this.$currentUser = new Signal.State(null);
1010 this.$profileSearchResults = new Signal.State(null);
1111 this.$chatRecipientSearchResults = new Signal.State(null);
1212+ this.$searchTypeaheadResults = new Signal.State(null);
1213 this.$feedSearchResults = new Signal.State(null);
1314 this.$showLessInteractions = new Signal.State([]);
1415 this.$showMoreInteractions = new Signal.State([]);
···2324 this.$mutedProfiles = new Signal.State(null);
2425 this.$latestProfileSearchRequestTime = new Signal.State(null);
2526 this.$latestChatRecipientSearchRequestTime = new Signal.State(null);
2727+ this.$latestSearchTypeaheadRequestTime = new Signal.State(null);
2628 this.$latestFeedSearchRequestTime = new Signal.State(null);
2729 this.$postSearchResultsTop = new Signal.State(null);
2830 this.$postSearchResultsLatest = new Signal.State(null);
+5
src/js/dataLayer/derived.js
···281281 if (!data) return null;
282282 return data.actors.map((actor) => this.$hydratedProfiles.get(actor.did));
283283 });
284284+ this.$searchTypeaheadResults = new Signal.Computed(() => {
285285+ const data = this.dataStore.$searchTypeaheadResults.get();
286286+ if (!data) return null;
287287+ return data.actors.map((actor) => this.$hydratedProfiles.get(actor.did));
288288+ });
284289 this.$feedSearchResults = new Signal.Computed(() => {
285290 const data = this.dataStore.$feedSearchResults.get();
286291 if (!data) return null;
···6464 },
6565 // Mirrors enableStatus: record every failure in the status store,
6666 // clear it on success, swallow ApiErrors, reject with everything else.
6767- // The dialog's debounced search is fire-and-forget, so the rejection is
6767+ // The dialog's search is fire-and-forget, so the rejection is
6868 // pre-handled here — node:test fails the running test on any unhandled
6969 // rejection, even when a process-level listener is attached.
7070 loadChatRecipientSearch: (query, options) => {
···171171 );
172172 input.value = value;
173173 input.dispatchEvent(new window.InputEvent("input", { bubbles: true }));
174174- // One tick for the debounce, one for the re-render.
174174+ // One tick for the search promise, one for the re-render.
175175 await nextFrame();
176176 await nextFrame();
177177 }
···390390 });
391391392392 describe("NewChatDialog - searching", () => {
393393- it("should call the debounced loader with the trimmed query", async () => {
393393+ it("should call the loader with the trimmed query", async () => {
394394 const { dataLayer, searchCalls } = createFakeDataLayer();
395395 const element = createDialog(dataLayer);
396396 await typeQuery(element, " alice ");
···462462 );
463463 });
464464465465- it("should not fire a pending search after the query is cleared", async () => {
465465+ it("should search immediately and end with a clearing call when the query is emptied", async () => {
466466 const { dataLayer, searchCalls } = createFakeDataLayer();
467467 const element = createDialog(dataLayer);
468468 const input = element.querySelector(
···474474 input.dispatchEvent(new window.InputEvent("input", { bubbles: true }));
475475 await nextFrame();
476476 await nextFrame();
477477- assert.deepEqual(searchCalls.length, 1);
478478- assert.deepEqual(searchCalls[0].query, "");
477477+ assert.deepEqual(searchCalls.length, 2);
478478+ assert.deepEqual(searchCalls[0].query, "al");
479479+ assert.deepEqual(searchCalls[1].query, "");
479480 });
480481 });
481482