Skip to content

fix(ui): accessibility and cleanup for IssueWorkspaceCard copy button - #1832

Merged
commitperclip[bot] merged 1 commit into
paperclipai:masterfrom
bluzername:fix/workspace-card-copy-a11y
Jul 31, 2026
Merged

commitperclip[bot] merged 1 commit into
paperclipai:masterfrom
bluzername:fix/workspace-card-copy-a11y

Conversation

@bluzername

@bluzername bluzername commented Mar 26, 2026 •

Copy link
Copy Markdown
Contributor

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • The board UI shows the workspace attached to an issue in ui/src/components/IssueWorkspaceCard.tsx
  • That card renders values such as the branch name and the workspace path through a small CopyableInline component, each with an icon-only copy button
  • The button has a title attribute only. Screen readers do not announce title reliably. A screen reader user hears no useful name for the button, because the button contains an icon and no text
  • The button also starts a 1.5 second setTimeout to reset its "copied" state. Nothing clears that timer. If the card unmounts first, the callback sets state on an unmounted component
  • This pull request adds a dynamic aria-label to the button and clears the timer in a useEffect cleanup
  • The benefit is a copy control that assistive technology can announce, and no stray timer after the card unmounts

Linked Issues or Issue Description

No existing GitHub issue covers this. The problem is described below with the fields from bug_report.yml.

What happened?
Open an issue that has a workspace attached. Tab to the copy button next to the branch or the workspace path in the workspace card. The screen reader announces an unlabeled button, because the button holds only a lucide Copy icon and a title attribute. Separately, copy a value and navigate away within 1.5 seconds. The pending setTimeout then calls setCopied(false) on an unmounted component.

Expected behavior
The copy button has an accessible name that says what it copies, and the name changes to confirm the copy. The reset timer is cleared when the component unmounts.

Steps to reproduce

  1. Run the app locally with pnpm dev.
  2. Open an issue that has a workspace attached, so IssueWorkspaceCard renders.
  3. Turn on a screen reader (VoiceOver, NVDA).
  4. Tab to the copy button next to the workspace path or the branch name. The button has no useful accessible name.
  5. Click the copy button, then navigate away from the issue in under 1.5 seconds. The reset timer is still pending.

Paperclip version or commit
Reproducible on master at this pull request's base commit.

Deployment mode
Local dev (pnpm dev).

Related pull request, not a duplicate: #3531 makes copy-to-clipboard buttons work in non-secure contexts. That pull request changes the clipboard write path. This one changes the button label and the timer cleanup, so the two do not overlap.

What Changed

  • Added an aria-label to the CopyableInline copy button in ui/src/components/IssueWorkspaceCard.tsx. The label reads Copy <label> (for example "Copy branch"), falls back to Copy value when the component gets no label prop, and changes to Copied to clipboard after a copy.
  • Added a useEffect cleanup that calls clearTimeout(timerRef.current) on unmount, so the 1.5 second reset timer cannot fire after the component unmounts.

Verification

  • CI is green on this pull request.
  • Static check: pnpm -r typecheck.
  • Test suite: pnpm test.
  • Manual, screen reader: open an issue with a workspace, tab to the copy button next to the path or the branch, and confirm the announcement is "Copy path" or "Copy branch". Activate the button and confirm the announcement changes to "Copied to clipboard".
  • Manual, timer: click the copy button and navigate away from the issue immediately. Confirm the console shows no unmounted-component state update.

Risks

Low risk. The change adds one ARIA attribute and one unmount cleanup in a single presentational component. No behavior changes for mouse users, no API or schema change. clearTimeout(undefined) is a no-op, so the cleanup is safe when the user never copied.

Model Used

  • Anthropic Claude Opus, model ID claude-opus-4-6, 200K context window, extended thinking enabled, with tool use for file edits.
  • Recorded by a maintainer while bringing this description up to the current template. The original description predates the Model Used requirement, so the author did not state a model. Author: please correct this line if the model was different.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have not referenced internal/instance-local Paperclip issues or links (only public GitHub #NNN / github.com/paperclipai/paperclip URLs)
  • My branch name describes the change (e.g. docs/..., fix/...) and contains no internal Paperclip ticket id or instance-derived details
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • I have updated relevant documentation to reflect my changes
  • I have considered and documented any risks above
  • All Paperclip CI gates are green
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

Notes on the checklist: no test or documentation change applies to a two-line ARIA and cleanup fix in one component. The Greptile box stays unchecked until the current review round closes.

…button

The new CopyableInline component in IssueWorkspaceCard had two
small issues:

1. The copy button only had a title attribute but no aria-label.
   Screen reader users cannot tell what the button does because
   title is not reliably announced. Added aria-label that change
   between "Copy <label>" and "Copied to clipboard" depending
   on state.

2. The setTimeout for resetting the copied state was not cleaned
   up on component unmount. If user navigate away quickly after
   clicking copy, the timer would fire on unmounted component.
   Added useEffect cleanup to clear the timeout.
@greptile-apps

greptile-apps Bot commented Mar 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an accessible name to the workspace copy control and clears its copied-state timeout when the component unmounts.

  • Changes the button’s accessible name after a successful copy.
  • Adds effect cleanup for the pending reset timer.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
ui/src/components/IssueWorkspaceCard.tsx Adds a dynamic copy-button aria-label and unmount cleanup for the copied-state timer.

Reviews (2): Last reviewed commit: "fix(ui): add aria-label and timer cleanu..." | Re-trigger Greptile

@commitperclip

commitperclip Bot commented Jul 31, 2026

Copy link
Copy Markdown

@greptileai

@commitperclip
commitperclip Bot merged commit 54e2031 into paperclipai:master Jul 31, 2026
5 checks passed
JAYHUANG0109 pushed a commit to JAYHUANG0109/paperclip that referenced this pull request Aug 13, 2026
…paperclipai#1832)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI shows the workspace attached to an issue in
`ui/src/components/IssueWorkspaceCard.tsx`
> - That card renders values such as the branch name and the workspace
path through a small `CopyableInline` component, each with an icon-only
copy button
> - The button has a `title` attribute only. Screen readers do not
announce `title` reliably. A screen reader user hears no useful name for
the button, because the button contains an icon and no text
> - The button also starts a 1.5 second `setTimeout` to reset its
"copied" state. Nothing clears that timer. If the card unmounts first,
the callback sets state on an unmounted component
> - This pull request adds a dynamic `aria-label` to the button and
clears the timer in a `useEffect` cleanup
> - The benefit is a copy control that assistive technology can
announce, and no stray timer after the card unmounts

## Linked Issues or Issue Description

No existing GitHub issue covers this. The problem is described below
with the fields from
[`bug_report.yml`](.github/ISSUE_TEMPLATE/bug_report.yml).

**What happened?**
Open an issue that has a workspace attached. Tab to the copy button next
to the branch or the workspace path in the workspace card. The screen
reader announces an unlabeled button, because the button holds only a
lucide `Copy` icon and a `title` attribute. Separately, copy a value and
navigate away within 1.5 seconds. The pending `setTimeout` then calls
`setCopied(false)` on an unmounted component.

**Expected behavior**
The copy button has an accessible name that says what it copies, and the
name changes to confirm the copy. The reset timer is cleared when the
component unmounts.

**Steps to reproduce**
1. Run the app locally with `pnpm dev`.
2. Open an issue that has a workspace attached, so `IssueWorkspaceCard`
renders.
3. Turn on a screen reader (VoiceOver, NVDA).
4. Tab to the copy button next to the workspace path or the branch name.
The button has no useful accessible name.
5. Click the copy button, then navigate away from the issue in under 1.5
seconds. The reset timer is still pending.

**Paperclip version or commit**
Reproducible on `master` at this pull request's base commit.

**Deployment mode**
Local dev (pnpm dev).

Related pull request, not a duplicate: paperclipai#3531 makes copy-to-clipboard
buttons work in non-secure contexts. That pull request changes the
clipboard write path. This one changes the button label and the timer
cleanup, so the two do not overlap.

## What Changed

- Added an `aria-label` to the `CopyableInline` copy button in
`ui/src/components/IssueWorkspaceCard.tsx`. The label reads `Copy
<label>` (for example "Copy branch"), falls back to `Copy value` when
the component gets no `label` prop, and changes to `Copied to clipboard`
after a copy.
- Added a `useEffect` cleanup that calls
`clearTimeout(timerRef.current)` on unmount, so the 1.5 second reset
timer cannot fire after the component unmounts.

## Verification

- CI is green on this pull request.
- Static check: `pnpm -r typecheck`.
- Test suite: `pnpm test`.
- Manual, screen reader: open an issue with a workspace, tab to the copy
button next to the path or the branch, and confirm the announcement is
"Copy path" or "Copy branch". Activate the button and confirm the
announcement changes to "Copied to clipboard".
- Manual, timer: click the copy button and navigate away from the issue
immediately. Confirm the console shows no unmounted-component state
update.

## Risks

Low risk. The change adds one ARIA attribute and one unmount cleanup in
a single presentational component. No behavior changes for mouse users,
no API or schema change. `clearTimeout(undefined)` is a no-op, so the
cleanup is safe when the user never copied.

## Model Used

- Anthropic Claude Opus, model ID `claude-opus-4-6`, 200K context
window, extended thinking enabled, with tool use for file edits.
- Recorded by a maintainer while bringing this description up to the
current template. The original description predates the Model Used
requirement, so the author did not state a model. Author: please correct
this line if the model was different.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [ ] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Notes on the checklist: no test or documentation change applies to a
two-line ARIA and cleanup fix in one component. The Greptile box stays
unchecked until the current review round closes.

(cherry picked from commit 54e2031)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant