feat(ui): display copyable goal ID in goal detail page#1910
feat(ui): display copyable goal ID in goal detail page#1910bluzername wants to merge 2 commits intopaperclipai:masterfrom
Conversation
The goal detail page was not showing the goal ID anywhere. Unlike issue detail which show the issue identifier (like PAP-123) and let you click to copy it, goal detail only show the level badge and status badge. Added the first 8 characters of the goal ID as a small monospaced text button next to the status badge. Click it to copy the full goal ID to clipboard with success toast. Same visual pattern as the issue identifier copy button - muted text that get darker on hover, cursor pointer, and title tooltip saying "Click to copy goal ID".
Greptile SummaryThis PR adds a small copyable goal ID button (showing the first 8 characters of Confidence Score: 4/5Safe to merge after fixing the unhandled clipboard Promise that gives users false-positive 'copied' feedback on failure. One P1 finding: the success toast fires regardless of whether the clipboard write actually succeeds, which is a real UX defect. Once that is fixed (wrap in async/try/catch as shown in the suggestion) and the PR description is updated with the required thinking path and screenshots, this is a clean, well-scoped change. ui/src/pages/GoalDetail.tsx — the onClick handler for the copy button needs error handling. Important Files Changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: ui/src/pages/GoalDetail.tsx
Line: 131-134
Comment:
**Success toast fires regardless of clipboard outcome**
`navigator.clipboard.writeText()` returns a Promise that can be rejected (e.g., clipboard permission denied, non-HTTPS context, document not focused). Because the call is neither `await`-ed nor given a `.catch()` handler, the "Goal ID copied" toast fires unconditionally — giving the user false positive feedback even when the copy silently failed.
`RoutineDetail.tsx` shows the established codebase pattern for this:
```suggestion
onClick={async () => {
try {
await navigator.clipboard.writeText(goal.id);
pushToast({ title: "Goal ID copied", tone: "success" });
} catch {
pushToast({ title: "Failed to copy goal ID", tone: "error" });
}
}}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: ui/src/pages/GoalDetail.tsx
Line: 127-137
Comment:
**Button lacks accessible label for screen readers**
The `title` attribute provides a tooltip on hover but is not reliably announced by screen readers. Since the button's visible text (`a1b2c3d4`) doesn't describe its action, adding an `aria-label` would improve accessibility and be consistent with how the rest of the UI handles icon-only or abbreviated controls.
```suggestion
<button
type="button"
className="text-xs font-mono text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
title="Click to copy goal ID"
aria-label="Copy goal ID"
onClick={async () => {
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat(ui): show copyable goal ID in goal ..." | Re-trigger Greptile |
ui/src/pages/GoalDetail.tsx
Outdated
| onClick={() => { | ||
| navigator.clipboard.writeText(goal.id); | ||
| pushToast({ title: "Goal ID copied", tone: "success" }); | ||
| }} |
There was a problem hiding this comment.
Success toast fires regardless of clipboard outcome
navigator.clipboard.writeText() returns a Promise that can be rejected (e.g., clipboard permission denied, non-HTTPS context, document not focused). Because the call is neither await-ed nor given a .catch() handler, the "Goal ID copied" toast fires unconditionally — giving the user false positive feedback even when the copy silently failed.
RoutineDetail.tsx shows the established codebase pattern for this:
| onClick={() => { | |
| navigator.clipboard.writeText(goal.id); | |
| pushToast({ title: "Goal ID copied", tone: "success" }); | |
| }} | |
| onClick={async () => { | |
| try { | |
| await navigator.clipboard.writeText(goal.id); | |
| pushToast({ title: "Goal ID copied", tone: "success" }); | |
| } catch { | |
| pushToast({ title: "Failed to copy goal ID", tone: "error" }); | |
| } | |
| }} |
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/pages/GoalDetail.tsx
Line: 131-134
Comment:
**Success toast fires regardless of clipboard outcome**
`navigator.clipboard.writeText()` returns a Promise that can be rejected (e.g., clipboard permission denied, non-HTTPS context, document not focused). Because the call is neither `await`-ed nor given a `.catch()` handler, the "Goal ID copied" toast fires unconditionally — giving the user false positive feedback even when the copy silently failed.
`RoutineDetail.tsx` shows the established codebase pattern for this:
```suggestion
onClick={async () => {
try {
await navigator.clipboard.writeText(goal.id);
pushToast({ title: "Goal ID copied", tone: "success" });
} catch {
pushToast({ title: "Failed to copy goal ID", tone: "error" });
}
}}
```
How can I resolve this? If you propose a fix, please make it concise.| <button | ||
| type="button" | ||
| className="text-xs font-mono text-muted-foreground hover:text-foreground transition-colors cursor-pointer" | ||
| title="Click to copy goal ID" | ||
| onClick={() => { | ||
| navigator.clipboard.writeText(goal.id); | ||
| pushToast({ title: "Goal ID copied", tone: "success" }); | ||
| }} | ||
| > | ||
| {goal.id.slice(0, 8)} | ||
| </button> |
There was a problem hiding this comment.
Button lacks accessible label for screen readers
The title attribute provides a tooltip on hover but is not reliably announced by screen readers. Since the button's visible text (a1b2c3d4) doesn't describe its action, adding an aria-label would improve accessibility and be consistent with how the rest of the UI handles icon-only or abbreviated controls.
| <button | |
| type="button" | |
| className="text-xs font-mono text-muted-foreground hover:text-foreground transition-colors cursor-pointer" | |
| title="Click to copy goal ID" | |
| onClick={() => { | |
| navigator.clipboard.writeText(goal.id); | |
| pushToast({ title: "Goal ID copied", tone: "success" }); | |
| }} | |
| > | |
| {goal.id.slice(0, 8)} | |
| </button> | |
| <button | |
| type="button" | |
| className="text-xs font-mono text-muted-foreground hover:text-foreground transition-colors cursor-pointer" | |
| title="Click to copy goal ID" | |
| aria-label="Copy goal ID" | |
| onClick={async () => { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/pages/GoalDetail.tsx
Line: 127-137
Comment:
**Button lacks accessible label for screen readers**
The `title` attribute provides a tooltip on hover but is not reliably announced by screen readers. Since the button's visible text (`a1b2c3d4`) doesn't describe its action, adding an `aria-label` would improve accessibility and be consistent with how the rest of the UI handles icon-only or abbreviated controls.
```suggestion
<button
type="button"
className="text-xs font-mono text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
title="Click to copy goal ID"
aria-label="Copy goal ID"
onClick={async () => {
```
How can I resolve this? If you propose a fix, please make it concise.navigator.clipboard.writeText() return Promise that was not awaited, so success toast was showing even when clipboard write fail. Now we await the Promise inside try/catch and show error toast on failure.
Thinking Path
Problem
When looking at a goal detail page, the goal ID was not visible anywhere. The header show the level badge (like "TEAM" or "COMPANY") and the status badge, but no ID.
This is inconsistent with the issue detail page where we show the issue identifier (like PAP-123) and user can click it to copy. When working with API or sharing goal reference with teammate, user had to inspect the URL or look in browser dev tools to find the goal ID. Very annoying.
What I changed
Added the first 8 characters of the goal ID as a small monospaced text button next to the level and status badges. Same visual pattern as the issue identifier we added before:
Also added useToast import since it wasn't in this file yet.
How to test
1 file, 13 lines added.