feat(feedback): split anon contact into email + x.com, tighten copy

Address review:
- move 'all optional' wording up to the top header line
- drop the inline 'we'll tag you on the PR' helper (placeholder says it)
- split the single contact input into two separate inputs (email +
  x.com handle), each with its own placeholder

IPC field rename: anonymousContact \u2192 anonymousEmail + anonymousX. Backend
PR is updated to match before either side ships.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-05-06 20:03:40 -07:00
co-authored by Orca
parent 8fe55a1e5e
commit 8f40a6afe1
4 changed files with 39 additions and 35 deletions
+4 -3
View File
@@ -14,12 +14,13 @@ export type FeedbackSubmitArgs = {
githubLogin: string | null
githubEmail: string | null
// Why: when the user submits anonymously they can still volunteer a GitHub
// handle (so we can @-mention them on the fix PR) or a contact (email / x
// handle for follow-up). These are separate fields rather than reusing
// handle (so we can @-mention them on the fix PR), an email, or an x.com
// handle for follow-up. Three separate fields rather than reusing
// githubLogin/githubEmail so the backend can tell verified gh identity
// apart from a self-typed handle.
anonymousGithubLogin?: string | null
anonymousContact?: string | null
anonymousEmail?: string | null
anonymousX?: string | null
}
type FeedbackSubmitBody = FeedbackSubmitArgs & {
+2 -1
View File
@@ -507,7 +507,8 @@ export type PreloadApi = {
githubLogin: string | null
githubEmail: string | null
anonymousGithubLogin?: string | null
anonymousContact?: string | null
anonymousEmail?: string | null
anonymousX?: string | null
}) => Promise<{ ok: true } | { ok: false; status: number | null; error: string }>
}
export: ExportApi
+2 -1
View File
@@ -520,7 +520,8 @@ const api = {
githubLogin: string | null
githubEmail: string | null
anonymousGithubLogin?: string | null
anonymousContact?: string | null
anonymousEmail?: string | null
anonymousX?: string | null
}): Promise<{ ok: true } | { ok: false; status: number | null; error: string }> =>
ipcRenderer.invoke('feedback:submit', args)
},
@@ -63,7 +63,8 @@ function FeedbackDialog({
const [isViewerLoading, setIsViewerLoading] = useState(false)
const [submitAnonymously, setSubmitAnonymously] = useState(false)
const [anonGithubLogin, setAnonGithubLogin] = useState('')
const [anonContact, setAnonContact] = useState('')
const [anonEmail, setAnonEmail] = useState('')
const [anonX, setAnonX] = useState('')
React.useEffect(() => {
if (!open) {
@@ -119,7 +120,8 @@ function FeedbackDialog({
// anonymous. Otherwise the regular gh identity already covers tagging
// and contact, and we don't want to surprise non-anonymous submitters.
anonymousGithubLogin: submitAnonymously ? normalizeOptional(anonGithubLogin) : null,
anonymousContact: submitAnonymously ? normalizeOptional(anonContact) : null
anonymousEmail: submitAnonymously ? normalizeOptional(anonEmail) : null,
anonymousX: submitAnonymously ? normalizeOptional(anonX) : null
})
if (!result.ok) {
@@ -130,7 +132,8 @@ function FeedbackDialog({
setFeedback('')
setSubmitAnonymously(false)
setAnonGithubLogin('')
setAnonContact('')
setAnonEmail('')
setAnonX('')
onOpenChange(false)
} catch (err) {
toast.error('Failed to submit feedback. Please try again.')
@@ -246,34 +249,32 @@ function FeedbackDialog({
<div className="overflow-hidden">
<div className="space-y-2 rounded-md border border-border/70 bg-muted/30 p-3">
<div className="text-xs text-foreground">
If we fix the issue, we can&apos;t let you know 🥲
If we fix the issue, we can&apos;t let you know 🥲 (all optional)
</div>
<label className="block space-y-1">
<span className="text-[11px] text-muted-foreground">
If you leave your GitHub username, we&apos;ll tag you on the PR.
</span>
<input
type="text"
value={anonGithubLogin}
onChange={(event) => setAnonGithubLogin(event.target.value)}
placeholder="github-username (optional)"
tabIndex={submitAnonymously ? 0 : -1}
className="h-8 w-full rounded-md border border-border bg-background px-2 text-xs outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
/>
</label>
<label className="block space-y-1">
<span className="text-[11px] text-muted-foreground">
Optional: email or x.com handle. We&apos;ll reach out when it&apos;s fixed.
</span>
<input
type="text"
value={anonContact}
onChange={(event) => setAnonContact(event.target.value)}
placeholder="you@example.com or @handle (optional)"
tabIndex={submitAnonymously ? 0 : -1}
className="h-8 w-full rounded-md border border-border bg-background px-2 text-xs outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
/>
</label>
<input
type="text"
value={anonGithubLogin}
onChange={(event) => setAnonGithubLogin(event.target.value)}
placeholder="GitHub username — we'll tag you on the PR"
tabIndex={submitAnonymously ? 0 : -1}
className="h-8 w-full rounded-md border border-border bg-background px-2 text-xs outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
/>
<input
type="email"
value={anonEmail}
onChange={(event) => setAnonEmail(event.target.value)}
placeholder="Email — we'll reach out when fixed"
tabIndex={submitAnonymously ? 0 : -1}
className="h-8 w-full rounded-md border border-border bg-background px-2 text-xs outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
/>
<input
type="text"
value={anonX}
onChange={(event) => setAnonX(event.target.value)}
placeholder="x.com handle — we'll reach out when fixed"
tabIndex={submitAnonymously ? 0 : -1}
className="h-8 w-full rounded-md border border-border bg-background px-2 text-xs outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
/>
</div>
</div>
</div>