feat: min 2-char query gate, result caps at 10 per section

Don't fire search for single-character queries — show 'Keep typing…'
instead. Cap rendered results at 10 per section (artists, albums,
tracks) to reduce noise. Top results already capped at 3.
This commit is contained in:
2026-03-24 22:41:02 -04:00
parent 176ac26f91
commit 5f8f6d6a26
@@ -12,6 +12,8 @@ import '@awesome.me/webawesome/dist/components/icon/icon.js';
/* ── Constants ── */ /* ── Constants ── */
const DEBOUNCE_MS = 300; const DEBOUNCE_MS = 300;
const MIN_QUERY_LENGTH = 2;
const MAX_SECTION_RESULTS = 10;
const CAA_GROUP_BASE = 'https://coverartarchive.org/release-group'; const CAA_GROUP_BASE = 'https://coverartarchive.org/release-group';
const TOP_RESULTS_COUNT = 3; const TOP_RESULTS_COUNT = 3;
@@ -67,6 +69,7 @@ export class ExploreView extends LitElement {
@state() private results: MBSearchResult | null = null; @state() private results: MBSearchResult | null = null;
@state() private loading = false; @state() private loading = false;
@state() private error = ''; @state() private error = '';
@state() private queryTooShort = false;
/** Monotonic counter to discard stale responses. */ /** Monotonic counter to discard stale responses. */
private searchVersion = 0; private searchVersion = 0;
@@ -481,13 +484,26 @@ export class ExploreView extends LitElement {
this.debounceTimer = null; this.debounceTimer = null;
} }
if (!this.searchQuery.trim()) { const trimmed = this.searchQuery.trim();
if (!trimmed) {
this.results = null; this.results = null;
this.error = ''; this.error = '';
this.loading = false; this.loading = false;
this.queryTooShort = false;
return; return;
} }
if (trimmed.length < MIN_QUERY_LENGTH) {
this.results = null;
this.error = '';
this.loading = false;
this.queryTooShort = true;
return;
}
this.queryTooShort = false;
this.debounceTimer = setTimeout(() => { this.debounceTimer = setTimeout(() => {
this.debounceTimer = null; this.debounceTimer = null;
void this.executeSearch(); void this.executeSearch();
@@ -499,6 +515,7 @@ export class ExploreView extends LitElement {
this.results = null; this.results = null;
this.error = ''; this.error = '';
this.loading = false; this.loading = false;
this.queryTooShort = false;
if (this.debounceTimer !== null) { if (this.debounceTimer !== null) {
clearTimeout(this.debounceTimer); clearTimeout(this.debounceTimer);
this.debounceTimer = null; this.debounceTimer = null;
@@ -666,6 +683,13 @@ export class ExploreView extends LitElement {
} }
private renderBody() { private renderBody() {
// Query too short
if (this.queryTooShort) {
return html`<div class="status-message">
Keep typing\u2026
</div>`;
}
// No query entered yet // No query entered yet
if (!this.searchQuery.trim() && !this.results) { if (!this.searchQuery.trim() && !this.results) {
return html`<div class="status-message"> return html`<div class="status-message">
@@ -696,13 +720,13 @@ export class ExploreView extends LitElement {
? this.renderTopResults(topResults) ? this.renderTopResults(topResults)
: nothing} : nothing}
${hasArtists ${hasArtists
? this.renderArtistsSection(this.results.artists!) ? this.renderArtistsSection(this.results.artists!.slice(0, MAX_SECTION_RESULTS))
: nothing} : nothing}
${hasAlbums ${hasAlbums
? this.renderAlbumsSection(this.results.releaseGroups!) ? this.renderAlbumsSection(this.results.releaseGroups!.slice(0, MAX_SECTION_RESULTS))
: nothing} : nothing}
${hasTracks ${hasTracks
? this.renderTracksSection(this.results.recordings!) ? this.renderTracksSection(this.results.recordings!.slice(0, MAX_SECTION_RESULTS))
: nothing} : nothing}
</div> </div>
`; `;