feat(frontend): simplify feedback report fields

This commit is contained in:
swyam sharma 2026-07-06 10:08:29 +05:30
parent b71dc9b479
commit 429753b7da
4 changed files with 51 additions and 98 deletions

View File

@ -41,12 +41,10 @@ export function ReportProblemDialog({ open, onOpenChange }: ReportProblemDialogP
const typeId = useId(); const typeId = useId();
const summaryId = useId(); const summaryId = useId();
const detailsId = useId(); const detailsId = useId();
const expectedId = useId();
const diagnosticsId = useId(); const diagnosticsId = useId();
const [type, setType] = useState<ReportProblemType>("bug"); const [type, setType] = useState<ReportProblemType>("bug");
const [summary, setSummary] = useState(""); const [summary, setSummary] = useState("");
const [details, setDetails] = useState(""); const [details, setDetails] = useState("");
const [expected, setExpected] = useState("");
const [includeDiagnostics, setIncludeDiagnostics] = useState(true); const [includeDiagnostics, setIncludeDiagnostics] = useState(true);
const [previewOutput, setPreviewOutput] = useState<ReportProblemOutput>("github"); const [previewOutput, setPreviewOutput] = useState<ReportProblemOutput>("github");
const [copiedOutput, setCopiedOutput] = useState<ReportProblemOutput | null>(null); const [copiedOutput, setCopiedOutput] = useState<ReportProblemOutput | null>(null);
@ -73,10 +71,9 @@ export function ReportProblemDialog({ open, onOpenChange }: ReportProblemDialogP
type, type,
summary, summary,
details, details,
expected,
includeDiagnostics, includeDiagnostics,
}), }),
[type, summary, details, expected, includeDiagnostics], [type, summary, details, includeDiagnostics],
); );
const preview = useMemo( const preview = useMemo(
@ -145,7 +142,7 @@ export function ReportProblemDialog({ open, onOpenChange }: ReportProblemDialogP
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-[12px] font-medium text-muted-foreground" htmlFor={summaryId}> <label className="text-[12px] font-medium text-muted-foreground" htmlFor={summaryId}>
{fieldCopy.summaryLabel} Summary
</label> </label>
<Input <Input
id={summaryId} id={summaryId}
@ -157,30 +154,17 @@ export function ReportProblemDialog({ open, onOpenChange }: ReportProblemDialogP
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-[12px] font-medium text-muted-foreground" htmlFor={detailsId}> <label className="text-[12px] font-medium text-muted-foreground" htmlFor={detailsId}>
{fieldCopy.detailsLabel} Details
</label> </label>
<textarea <textarea
id={detailsId} id={detailsId}
className="min-h-[112px] w-full resize-y rounded-md border border-border bg-transparent px-3 py-2 text-[13px] leading-relaxed text-foreground outline-none transition placeholder:text-passive focus-visible:border-accent focus-visible:ring-2 focus-visible:ring-accent-weak" className="min-h-[156px] w-full resize-y rounded-md border border-border bg-transparent px-3 py-2 text-[13px] leading-relaxed text-foreground outline-none transition placeholder:text-passive focus-visible:border-accent focus-visible:ring-2 focus-visible:ring-accent-weak"
value={details} value={details}
onChange={(event) => setDetails(event.target.value)} onChange={(event) => setDetails(event.target.value)}
placeholder={fieldCopy.detailsPlaceholder} placeholder={fieldCopy.detailsPlaceholder}
/> />
</div> </div>
<div className="space-y-1.5">
<label className="text-[12px] font-medium text-muted-foreground" htmlFor={expectedId}>
{fieldCopy.expectedLabel}
</label>
<textarea
id={expectedId}
className="min-h-[84px] w-full resize-y rounded-md border border-border bg-transparent px-3 py-2 text-[13px] leading-relaxed text-foreground outline-none transition placeholder:text-passive focus-visible:border-accent focus-visible:ring-2 focus-visible:ring-accent-weak"
value={expected}
onChange={(event) => setExpected(event.target.value)}
placeholder={fieldCopy.expectedPlaceholder}
/>
</div>
<label <label
className="flex items-start gap-2 rounded-md border border-border bg-background/40 px-3 py-2 text-[12px] text-muted-foreground" className="flex items-start gap-2 rounded-md border border-border bg-background/40 px-3 py-2 text-[12px] text-muted-foreground"
htmlFor={diagnosticsId} htmlFor={diagnosticsId}

View File

@ -324,12 +324,12 @@ describe("Sidebar", () => {
await user.click(feedbackButton); await user.click(feedbackButton);
expect(await screen.findByRole("dialog", { name: "Report a problem" })).toBeInTheDocument(); expect(await screen.findByRole("dialog", { name: "Report a problem" })).toBeInTheDocument();
await user.type(screen.getByLabelText("Bug summary"), "Create project fails in /Users/alice/private-repo"); await user.type(screen.getByLabelText("Summary"), "Create project fails in /Users/alice/private-repo");
await user.type( await user.type(
screen.getByLabelText("Steps to reproduce"), screen.getByLabelText("Details"),
"Open http://127.0.0.1:5173/projects/demo?access_token=local-secret and click Create.", "Open http://127.0.0.1:5173/projects/demo?access_token=local-secret and click Create. Show a clear prerequisite error.",
); );
await user.type(screen.getByLabelText("Expected behavior"), "Show a clear prerequisite error."); expect(screen.queryByLabelText("Expected behavior")).not.toBeInTheDocument();
expect(screen.getByLabelText("Report preview")).toHaveTextContent("[redacted-local-path]"); expect(screen.getByLabelText("Report preview")).toHaveTextContent("[redacted-local-path]");
await user.click(screen.getByRole("button", { name: "Copy and open GitHub" })); await user.click(screen.getByRole("button", { name: "Copy and open GitHub" }));
@ -361,7 +361,7 @@ describe("Sidebar", () => {
await user.click(screen.getAllByRole("button", { name: "Feedback" })[0]); await user.click(screen.getAllByRole("button", { name: "Feedback" })[0]);
expect(await screen.findByRole("dialog", { name: "Report a problem" })).toBeInTheDocument(); expect(await screen.findByRole("dialog", { name: "Report a problem" })).toBeInTheDocument();
await user.type(screen.getByLabelText("Bug summary"), "Need help with setup"); await user.type(screen.getByLabelText("Summary"), "Need help with setup");
await user.click(screen.getByRole("button", { name: "Copy and open Discord" })); await user.click(screen.getByRole("button", { name: "Copy and open Discord" }));
await user.click(screen.getByRole("button", { name: "Copy email draft" })); await user.click(screen.getByRole("button", { name: "Copy email draft" }));
@ -373,36 +373,36 @@ describe("Sidebar", () => {
expect(open).toHaveBeenCalledWith("https://discord.com/invite/UZv7JjxbwG", "_blank", "noopener,noreferrer"); expect(open).toHaveBeenCalledWith("https://discord.com/invite/UZv7JjxbwG", "_blank", "noopener,noreferrer");
}); });
it("shows report-type specific prompts instead of one generic questionnaire", async () => { it("keeps the report form to summary and details while tailoring placeholder guidance", async () => {
const user = userEvent.setup(); const user = userEvent.setup();
renderSidebar(); renderSidebar();
await user.click(screen.getAllByRole("button", { name: "Feedback" })[0]); await user.click(screen.getAllByRole("button", { name: "Feedback" })[0]);
expect(await screen.findByRole("dialog", { name: "Report a problem" })).toBeInTheDocument(); expect(await screen.findByRole("dialog", { name: "Report a problem" })).toBeInTheDocument();
expect(screen.getByLabelText("Bug summary")).toHaveAttribute("placeholder", "Short description of the bug"); expect(screen.getByLabelText("Summary")).toHaveAttribute("placeholder", "Brief title");
expect(screen.getByLabelText("Steps to reproduce")).toHaveAttribute( expect(screen.getByLabelText("Details")).toHaveAttribute(
"placeholder", "placeholder",
"1. Open...\n2. Click...\n3. See...", "What happened, how to reproduce it, and what you expected.",
);
expect(screen.getByLabelText("Expected behavior")).toHaveAttribute(
"placeholder",
"What should have happened instead?",
); );
expect(screen.queryByLabelText("Expected behavior")).not.toBeInTheDocument();
await chooseOption(screen.getByRole("combobox", { name: "Report type" }), "Feature request"); await chooseOption(screen.getByRole("combobox", { name: "Report type" }), "Feature request");
expect(screen.getByLabelText("Feature summary")).toHaveAttribute("placeholder", "Short description of the idea"); expect(screen.getByLabelText("Summary")).toHaveAttribute("placeholder", "Brief title");
expect(screen.getByLabelText("Problem / use case")).toBeInTheDocument(); expect(screen.getByLabelText("Details")).toHaveAttribute(
expect(screen.getByLabelText("Requested behavior")).toBeInTheDocument(); "placeholder",
"The problem, use case, and requested behavior.",
);
await chooseOption(screen.getByRole("combobox", { name: "Report type" }), "General feedback"); await chooseOption(screen.getByRole("combobox", { name: "Report type" }), "General feedback");
expect(screen.getByLabelText("Feedback summary")).toBeInTheDocument(); expect(screen.getByLabelText("Summary")).toHaveAttribute("placeholder", "Brief title");
expect(screen.getByLabelText("Message")).toBeInTheDocument(); expect(screen.getByLabelText("Details")).toHaveAttribute("placeholder", "What should the AO team know?");
expect(screen.getByLabelText("Optional context")).toBeInTheDocument();
await chooseOption(screen.getByRole("combobox", { name: "Report type" }), "Setup question"); await chooseOption(screen.getByRole("combobox", { name: "Report type" }), "Setup question");
expect(screen.getByLabelText("Question summary")).toBeInTheDocument(); expect(screen.getByLabelText("Summary")).toHaveAttribute("placeholder", "Brief title");
expect(screen.getByLabelText("What are you trying to do?")).toBeInTheDocument(); expect(screen.getByLabelText("Details")).toHaveAttribute(
expect(screen.getByLabelText("What did you try?")).toBeInTheDocument(); "placeholder",
"What you are trying to do, what you tried, and any error/output.",
);
}); });
it("renames a session inline and persists via the daemon", async () => { it("renames a session inline and persists via the daemon", async () => {

View File

@ -20,8 +20,8 @@ const diagnostics: ReportProblemDiagnostics = {
const completeInput: ReportProblemInput = { const completeInput: ReportProblemInput = {
type: "bug", type: "bug",
summary: "Terminal keeps reconnecting after daemon restart", summary: "Terminal keeps reconnecting after daemon restart",
details: "Open /Users/alice/work/secret-app and visit http://127.0.0.1:5173/?token=secret-token.", details:
expected: "The app should reconnect without losing the current route.", "Open /Users/alice/work/secret-app and visit http://127.0.0.1:5173/?token=secret-token. The app should reconnect without losing the current route.",
includeDiagnostics: true, includeDiagnostics: true,
}; };
@ -50,8 +50,8 @@ describe("report problem drafts", () => {
{ {
type: "question", type: "question",
summary: "Setup fails with OPENAI_API_KEY=sk-proj-secret and password=hunter2", summary: "Setup fails with OPENAI_API_KEY=sk-proj-secret and password=hunter2",
details: "Repo is C:\\Users\\alice\\repo and file:///Users/alice/private/index.html?api_key=abc failed.", details:
expected: "Tell me what prerequisite is missing.", "Repo is C:\\Users\\alice\\repo and file:///Users/alice/private/index.html?api_key=abc failed. Tell me what prerequisite is missing.",
includeDiagnostics: true, includeDiagnostics: true,
}, },
{ {
@ -73,7 +73,7 @@ describe("report problem drafts", () => {
it("produces a useful draft when user input is partial", () => { it("produces a useful draft when user input is partial", () => {
const draft = formatReportProblemDraft( const draft = formatReportProblemDraft(
{ type: "feedback", summary: "", details: "", expected: "", includeDiagnostics: false }, { type: "feedback", summary: "", details: "", includeDiagnostics: false },
diagnostics, diagnostics,
"email", "email",
); );
@ -83,34 +83,35 @@ describe("report problem drafts", () => {
expect(draft).toContain("No diagnostics included"); expect(draft).toContain("No diagnostics included");
}); });
it("uses report-type specific field labels in generated drafts", () => { it("keeps generated drafts to summary and details for every report type", () => {
const featureDraft = formatReportProblemDraft( const featureDraft = formatReportProblemDraft(
{ {
type: "feature", type: "feature",
summary: "Make feedback reports easier to send", summary: "Make feedback reports easier to send",
details: "Users want fewer generic bug-report questions for non-bug reports.", details:
expected: "Show feature-specific prompts and output labels.", "Users want fewer generic bug-report questions for non-bug reports. Show fewer fields and clearer placeholder guidance.",
includeDiagnostics: false, includeDiagnostics: false,
}, },
diagnostics, diagnostics,
"github", "github",
); );
expect(featureDraft).toContain("## Problem / use case"); expect(featureDraft).toContain("## Summary");
expect(featureDraft).toContain("## Requested behavior"); expect(featureDraft).toContain("## Details");
expect(featureDraft).not.toContain("## Expected");
expect(featureDraft).not.toContain("## Requested behavior");
const questionDraft = formatReportProblemDraft( const questionDraft = formatReportProblemDraft(
{ {
type: "question", type: "question",
summary: "Need help setting up Claude Code", summary: "Need help setting up Claude Code",
details: "Trying to create my first project.", details: "Trying to create my first project. I installed the CLI and checked PATH.",
expected: "I installed the CLI and checked PATH.",
includeDiagnostics: false, includeDiagnostics: false,
}, },
diagnostics, diagnostics,
"email", "email",
); );
expect(questionDraft).toContain("What are you trying to do?:"); expect(questionDraft).toContain("Details:");
expect(questionDraft).toContain("What did you try?:"); expect(questionDraft).not.toContain("What did you try?:");
}); });
it("builds copy handoff destinations for GitHub and Discord while leaving email copy-only", () => { it("builds copy handoff destinations for GitHub and Discord while leaving email copy-only", () => {

View File

@ -8,17 +8,12 @@ export type ReportProblemInput = {
type: ReportProblemType; type: ReportProblemType;
summary: string; summary: string;
details: string; details: string;
expected: string;
includeDiagnostics: boolean; includeDiagnostics: boolean;
}; };
export type ReportProblemFieldCopy = { export type ReportProblemFieldCopy = {
summaryLabel: string;
summaryPlaceholder: string; summaryPlaceholder: string;
detailsLabel: string;
detailsPlaceholder: string; detailsPlaceholder: string;
expectedLabel: string;
expectedPlaceholder: string;
}; };
export type ReportProblemDiagnostics = { export type ReportProblemDiagnostics = {
@ -46,36 +41,20 @@ const REPORT_TYPE_LABELS: Record<ReportProblemType, string> = {
const REPORT_FIELD_COPY: Record<ReportProblemType, ReportProblemFieldCopy> = { const REPORT_FIELD_COPY: Record<ReportProblemType, ReportProblemFieldCopy> = {
bug: { bug: {
summaryLabel: "Bug summary", summaryPlaceholder: "Brief title",
summaryPlaceholder: "Short description of the bug", detailsPlaceholder: "What happened, how to reproduce it, and what you expected.",
detailsLabel: "Steps to reproduce",
detailsPlaceholder: "1. Open...\n2. Click...\n3. See...",
expectedLabel: "Expected behavior",
expectedPlaceholder: "What should have happened instead?",
}, },
feature: { feature: {
summaryLabel: "Feature summary", summaryPlaceholder: "Brief title",
summaryPlaceholder: "Short description of the idea", detailsPlaceholder: "The problem, use case, and requested behavior.",
detailsLabel: "Problem / use case",
detailsPlaceholder: "What problem are you trying to solve?",
expectedLabel: "Requested behavior",
expectedPlaceholder: "What should AO do?",
}, },
feedback: { feedback: {
summaryLabel: "Feedback summary", summaryPlaceholder: "Brief title",
summaryPlaceholder: "Short summary",
detailsLabel: "Message",
detailsPlaceholder: "What should the AO team know?", detailsPlaceholder: "What should the AO team know?",
expectedLabel: "Optional context",
expectedPlaceholder: "Anything else that would help?",
}, },
question: { question: {
summaryLabel: "Question summary", summaryPlaceholder: "Brief title",
summaryPlaceholder: "Short description of the setup question", detailsPlaceholder: "What you are trying to do, what you tried, and any error/output.",
detailsLabel: "What are you trying to do?",
detailsPlaceholder: "Describe the setup or workflow you are attempting.",
expectedLabel: "What did you try?",
expectedPlaceholder: "Commands, docs, or fixes you already tried.",
}, },
}; };
@ -134,8 +113,7 @@ export function formatReportProblemDraft(
return [ return [
`**AO ${fields.typeLabel}**`, `**AO ${fields.typeLabel}**`,
`Summary: ${fields.summary}`, `Summary: ${fields.summary}`,
`${fields.detailsLabel}: ${fields.details}`, `Details: ${fields.details}`,
`${fields.expectedLabel}: ${fields.expected}`,
"", "",
"Safe diagnostics:", "Safe diagnostics:",
diagnosticsBlock, diagnosticsBlock,
@ -153,12 +131,9 @@ export function formatReportProblemDraft(
`Type: ${fields.typeLabel}`, `Type: ${fields.typeLabel}`,
`Summary: ${fields.summary}`, `Summary: ${fields.summary}`,
"", "",
`${fields.detailsLabel}:`, "Details:",
fields.details, fields.details,
"", "",
`${fields.expectedLabel}:`,
fields.expected,
"",
"Safe diagnostics:", "Safe diagnostics:",
diagnosticsBlock, diagnosticsBlock,
"", "",
@ -175,12 +150,9 @@ export function formatReportProblemDraft(
"## Summary", "## Summary",
fields.summary, fields.summary,
"", "",
`## ${fields.detailsLabel}`, "## Details",
fields.details, fields.details,
"", "",
`## ${fields.expectedLabel}`,
fields.expected,
"",
"## Safe diagnostics", "## Safe diagnostics",
diagnosticsBlock, diagnosticsBlock,
"", "",
@ -206,14 +178,10 @@ export function reportProblemDestinationUrl(
} }
function normalizeInput(input: ReportProblemInput) { function normalizeInput(input: ReportProblemInput) {
const fieldCopy = REPORT_FIELD_COPY[input.type];
return { return {
typeLabel: REPORT_TYPE_LABELS[input.type], typeLabel: REPORT_TYPE_LABELS[input.type],
detailsLabel: fieldCopy.detailsLabel,
expectedLabel: fieldCopy.expectedLabel,
summary: valueOrPlaceholder(input.summary), summary: valueOrPlaceholder(input.summary),
details: valueOrPlaceholder(input.details), details: valueOrPlaceholder(input.details),
expected: valueOrPlaceholder(input.expected),
}; };
} }