feat: request a tool when home search finds nothing (#385)

Adds a prefilled 'Request a tool' affordance to the home search empty state and beneath weak results. Opens the in-app feedback dialog with a new search_miss source and a structured search_query when analytics is on; links to a prefilled GitHub Discussions (Ideas) post when off, so a request is never silently dropped. Reuses the existing feedback pipe, dialog, and analytics gate; no new storage. i18n across all 21 locales.
This commit is contained in:
SnapOtter
2026-07-01 18:50:55 +08:00
committed by GitHub
parent 174001d384
commit c68297d5a4
32 changed files with 531 additions and 30 deletions
+28
View File
@@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { buildToolRequestDiscussionUrl } from "@/lib/tool-request";
describe("buildToolRequestDiscussionUrl", () => {
it("targets the Ideas discussions category on the SnapOtter repo", () => {
const url = new URL(buildToolRequestDiscussionUrl("convert to dicom"));
expect(`${url.origin}${url.pathname}`).toBe(
"https://github.com/snapotter-hq/snapotter/discussions/new",
);
expect(url.searchParams.get("category")).toBe("ideas");
});
it("embeds the query in the title and body", () => {
const url = new URL(buildToolRequestDiscussionUrl("convert to dicom"));
expect(url.searchParams.get("title")).toContain("convert to dicom");
expect(url.searchParams.get("body")).toContain("convert to dicom");
});
it("clamps an over-long query to 200 characters", () => {
const url = new URL(buildToolRequestDiscussionUrl("a".repeat(500)));
expect(url.searchParams.get("title")).toBe(`Tool request: ${"a".repeat(200)}`);
});
it("collapses newlines in the query", () => {
const url = new URL(buildToolRequestDiscussionUrl("line one\nline two"));
expect(url.searchParams.get("title")).toBe("Tool request: line one line two");
});
});