From c2505c91bbddacbe7c0f78092ae21b0adc30ee0b Mon Sep 17 00:00:00 2001
From: "sentry-junior[bot]"
<264270552+sentry-junior[bot]@users.noreply.github.com>
Date: Sun, 1 Nov 2026 19:38:07 +0000
Subject: [PATCH] fix(dashboard): Make the user input note clearly add to the
answers
The free-text input after a form's fields was labeled "Other" with the
help text "an answer that is not listed". That reads like it replaces the
picked answers. It does not: the note goes with them. Rename it to
"Anything else?" and say that it is sent together with the answers.
Slack and the dashboard share the same copy through USER_INPUT_NOTE.
Each choice field now says "Pick one" or "Pick any", and a form with
more than one field shows how many fields have an answer next to Send.
The review items side panel uses the default drawer width, so option
descriptions wrap less.
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
---
.../junior-dashboard/e2e/review-desk.spec.ts | 8 ++++++-
.../src/client/components/UserInputForm.tsx | 24 +++++++++++++++----
.../review-desk/ConversationReviewBar.tsx | 1 -
packages/junior/src/api/schema.ts | 1 +
packages/junior/src/chat/slack/user-input.ts | 9 +++----
.../chat/tools/runtime/user-input-request.ts | 10 ++++++++
6 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/packages/junior-dashboard/e2e/review-desk.spec.ts b/packages/junior-dashboard/e2e/review-desk.spec.ts
index d52e4bfe58..250125306d 100644
--- a/packages/junior-dashboard/e2e/review-desk.spec.ts
+++ b/packages/junior-dashboard/e2e/review-desk.spec.ts
@@ -160,10 +160,16 @@ test("each review item gets a fresh answer form", async ({
detail.getByRole("heading", { name: license.title }),
).toBeVisible();
await detail.getByRole("radio", { name: "Reject" }).click();
+ await expect(detail.getByText("1 of 2 answered")).toBeVisible();
+ // The note goes with the picked answer. It must not replace it.
+ await detail.getByLabel("Anything else?").fill("Ask legal first.");
await detail.getByRole("button", { name: "Send" }).click();
await expect.poll(() => actions.length).toBe(2);
expect(actions[1]).toMatchObject({
- body: { action: "submit", answer: { values: { decision: ["Reject"] } } },
+ body: {
+ action: "submit",
+ answer: { other: "Ask legal first.", values: { decision: ["Reject"] } },
+ },
id: license.id,
});
});
diff --git a/packages/junior-dashboard/src/client/components/UserInputForm.tsx b/packages/junior-dashboard/src/client/components/UserInputForm.tsx
index 18b9cfc380..0c60998dae 100644
--- a/packages/junior-dashboard/src/client/components/UserInputForm.tsx
+++ b/packages/junior-dashboard/src/client/components/UserInputForm.tsx
@@ -3,6 +3,7 @@ import {
buttonUserInputField,
isEmptyUserInputAnswer,
normalizeUserInputAnswer,
+ USER_INPUT_NOTE,
type UserInputAnswer,
type UserInputField,
type UserInputRequest,
@@ -41,9 +42,13 @@ function ChoiceField(props: {
}) {
const id = useId();
const { field } = props;
+ // Say how many choices count, so a reader knows when a field is done.
const legend = (
);
const description = field.description ? (
@@ -179,11 +184,12 @@ export function UserInputForm(props: {
requestId: props.requestId,
values,
};
+ const normalized = normalizeUserInputAnswer(props.request, answer);
+ const answeredCount = Object.keys(normalized.values).length;
+ const fieldCount = props.request.fields.length;
function submit(event: FormEvent) {
event.preventDefault();
- if (
- !isEmptyUserInputAnswer(normalizeUserInputAnswer(props.request, answer))
- ) {
+ if (!isEmptyUserInputAnswer(normalized)) {
void send(answer);
}
}
@@ -259,9 +265,9 @@ export function UserInputForm(props: {
)}