Skip to content

feat(agents): copy-to-clipboard session ID pill button in agents table#454

Open
jaeko44 wants to merge 5 commits intomainfrom
task/ac13245d3271-feat-agents-copy-to-clipboard-session-id-pill-bu
Open

feat(agents): copy-to-clipboard session ID pill button in agents table#454
jaeko44 wants to merge 5 commits intomainfrom
task/ac13245d3271-feat-agents-copy-to-clipboard-session-id-pill-bu

Conversation

@jaeko44
Copy link
Copy Markdown
Member

@jaeko44 jaeko44 commented Mar 26, 2026

Task-ID: ac13245d-3271-47a3-89b6-ea83f7456eb2\n\nAutomated PR for task ac13245d-3271-47a3-89b6-ea83f7456eb2\n\n---\n\nBosun-Origin: created

Copilot AI review requested due to automatic review settings March 26, 2026 09:44
@jaeko44 jaeko44 added the bosun-attached Bosun PR attachment marker label Mar 26, 2026
@github-actions github-actions bot added the bosun-pr-public PR observed by Bosun but not trusted for high-risk automation label Mar 26, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 26, 2026

Bosun CI signal: Bosun-created PR currently has failing checks.


const allSessions = sessionsData.value || [];

const copySessionId = (sessionId) => {

const allSessions = sessionsData.value || [];

const copySessionId = (sessionId) => {
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “session ID pill” UI affordance to the Agents/Fleet sessions list so users can copy a session ID to the clipboard with visual feedback, along with styling and regression tests.

Changes:

  • Render a session-id pill button in FleetSessionsPanel that copies the full session id and shows a copied state.
  • Add pill styling + copied animation to shared UI CSS (mirrored in ui/ and site/ui/).
  • Add/extend tests to cover copy behavior and enforce the render/markup pattern.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
ui/tabs/agents.js Adds clipboard-copy handler + renders the session-id pill in FleetSessionsPanel.
site/ui/tabs/agents.js Mirrors the same pill + copy behavior for the site/ UI build.
ui/styles.css Adds .fleet-session-id-pill styles and copied animation.
site/ui/styles.css Mirrors the same pill styling for the site/ UI build.
tests/ui-agents-session-pill.test.mjs New test validating clipboard copy and copied-state reset via animation end.
tests/fleet-tab-render.test.mjs Adds a source-level assertion ensuring the pill markup/accessibility hooks are present.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1319 to +1329
const copySessionId = (sessionId) => {
if (!sessionId) return;
setCopiedSessionId(sessionId);
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
};
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copySessionId references setCopiedSessionId, but AgentsTab does not define a copiedSessionId state (and this helper is not used anywhere in the file). This will throw if called and is likely leftover/unfinished; either add the missing useState + UI usage, or remove this block from AgentsTab.

Suggested change
const copySessionId = (sessionId) => {
if (!sessionId) return;
setCopiedSessionId(sessionId);
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
};

Copilot uses AI. Check for mistakes.
Comment on lines +2414 to +2427
? html`<button
type="button"
class="fleet-session-id-pill"
data-session-id=${sessionId}
data-copied=${copiedSessionId === sessionId ? "true" : "false"}
aria-label=${`Copy session ID ${sessionId}`}
onClick=${() => copySessionId(sessionId)}
onAnimationEnd=${(event) => {
if (event?.target !== event?.currentTarget) return;
if (copiedSessionId === sessionId) setCopiedSessionId("");
}}
>
<span class="fleet-session-id-pill-text mono">${sessionId.slice(0, 8)}</span>
<span class="fleet-session-id-pill-icon" aria-hidden="true">${copiedSessionId === sessionId ? "✓" : ICONS.copy}</span>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The session-id pill is rendered as a native <button> inside an MUI <Button> (which renders as a <button> by default). Nested buttons are invalid HTML and browsers will implicitly close the outer button, breaking click/keyboard behavior and accessibility. Refactor so the row container is not a <button> when it contains this pill (e.g., make the row a <div>/ListItemButton with appropriate keyboard handling, or move copy handling onto a non-nested element).

Copilot uses AI. Check for mistakes.
Comment on lines +2110 to +2116
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copySessionId calls navigator.clipboard.writeText without checking that the Clipboard API is available. In unsupported/insecure contexts this can throw synchronously (bypassing the Promise .catch) and will also leave data-copied stuck. Add a navigator?.clipboard?.writeText guard (consistent with other clipboard code in this file) and reset copiedSessionId / toast appropriately when unavailable.

Suggested change
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
const writeText = navigator?.clipboard?.writeText;
if (!writeText) {
setCopiedSessionId("");
showToast("Copy failed", "error");
return;
}
try {
writeText.call(navigator.clipboard, sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
} catch (_err) {
setCopiedSessionId("");
showToast("Copy failed", "error");
}

Copilot uses AI. Check for mistakes.
it("renders a keyboard-accessible session id pill with copy feedback state", () => {
expect(source).toContain("fleet-session-id-pill");
expect(source).toContain("type=\"button\"");
expect(source).toContain("aria-label=${`Copy session ID ${sessionId}`}\");
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion line has a syntax error: the string literal is not closed due to an extra backslash/quote (...${sessionId}}"). This will prevent the test file from parsing/running. Fix the string so it’s a valid JS literal and matches the intended source substring.

Suggested change
expect(source).toContain("aria-label=${`Copy session ID ${sessionId}`}\");
expect(source).toContain("aria-label={`Copy session ID ${sessionId}`}");

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +79
const clipboardWrite = vi.fn().mockResolvedValue();
Object.defineProperty(globalThis, "navigator", {
value: { clipboard: { writeText: clipboardWrite } },
configurable: true,
});
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test overwrites globalThis.navigator but never restores the original value, which can leak into other tests. Capture the prior navigator and restore it in afterEach, or use vi.stubGlobal('navigator', ...) / vi.unstubAllGlobals() to keep test isolation.

Copilot uses AI. Check for mistakes.
Comment on lines +1319 to +1329
const copySessionId = (sessionId) => {
if (!sessionId) return;
setCopiedSessionId(sessionId);
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
};
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copySessionId references setCopiedSessionId, but AgentsTab does not define a copiedSessionId state (and this helper is not used anywhere in the file). This will throw if called and is likely leftover/unfinished; either add the missing useState + UI usage, or remove this block from AgentsTab.

Suggested change
const copySessionId = (sessionId) => {
if (!sessionId) return;
setCopiedSessionId(sessionId);
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
};

Copilot uses AI. Check for mistakes.
Comment on lines +2414 to +2427
? html`<button
type="button"
class="fleet-session-id-pill"
data-session-id=${sessionId}
data-copied=${copiedSessionId === sessionId ? "true" : "false"}
aria-label=${`Copy session ID ${sessionId}`}
onClick=${() => copySessionId(sessionId)}
onAnimationEnd=${(event) => {
if (event?.target !== event?.currentTarget) return;
if (copiedSessionId === sessionId) setCopiedSessionId("");
}}
>
<span class="fleet-session-id-pill-text mono">${sessionId.slice(0, 8)}</span>
<span class="fleet-session-id-pill-icon" aria-hidden="true">${copiedSessionId === sessionId ? "✓" : ICONS.copy}</span>
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The session-id pill is rendered as a native <button> inside an MUI <Button> (which renders as a <button> by default). Nested buttons are invalid HTML and browsers will implicitly close the outer button, breaking click/keyboard behavior and accessibility. Refactor so the row container is not a <button> when it contains this pill (e.g., make the row a <div>/ListItemButton with appropriate keyboard handling, or move copy handling onto a non-nested element).

Copilot uses AI. Check for mistakes.
Comment on lines +2109 to +2116
setCopiedSessionId(sessionId);
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copySessionId calls navigator.clipboard.writeText without checking that the Clipboard API is available. In unsupported/insecure contexts this can throw synchronously (bypassing the Promise .catch) and will also leave data-copied stuck. Add a navigator?.clipboard?.writeText guard (consistent with other clipboard code in this file) and reset copiedSessionId / toast appropriately when unavailable.

Suggested change
setCopiedSessionId(sessionId);
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
// Guard against unsupported / insecure contexts where the Clipboard API
// is unavailable or cannot be used safely.
if (!navigator?.clipboard?.writeText) {
setCopiedSessionId("");
showToast("Clipboard not available", "error");
return;
}
setCopiedSessionId(sessionId);
try {
navigator.clipboard
.writeText(sessionId)
.then(() => showToast("Session ID copied", "success"))
.catch(() => {
setCopiedSessionId("");
showToast("Copy failed", "error");
});
} catch (err) {
setCopiedSessionId("");
showToast("Copy failed", "error");
}

Copilot uses AI. Check for mistakes.
@jaeko44 jaeko44 added bosun-pr-bosun-created PR created by Bosun and eligible for Bosun automation and removed bosun-pr-public PR observed by Bosun but not trusted for high-risk automation labels Mar 26, 2026
@github-actions github-actions bot added the bosun-needs-fix Attached PR with failing CI that Bosun should pick up for repair label Mar 26, 2026
@github-actions
Copy link
Copy Markdown

Bosun PR classification: Bosun-created.
This PR is tracked by Bosun attachment automation.

  • PR class label: bosun-pr-bosun-created
  • Attach label: bosun-attached (yes, because Bosun-created PRs remain attached regardless of human PR attach mode)
  • Attach mode policy: all
  • Trusted author: no
  • Bosun-created label present: yes
  • Automation scope: Eligible for Bosun repair and merge automation (Bosun-created PR).
  • CI failure signal label: bosun-needs-fix
  • CI failure marker:
  • Trigger: pull_request_target / synchronize

jaeko44 added 2 commits March 26, 2026 23:41
Co-authored-by: bosun-ve[bot] <262908237+bosun-ve[bot]@users.noreply.github.com>
Co-authored-by: bosun-ve[bot] <262908237+bosun-ve[bot]@users.noreply.github.com>
@jaeko44 jaeko44 added bosun-needs-fix Attached PR with failing CI that Bosun should pick up for repair and removed bosun-needs-fix Attached PR with failing CI that Bosun should pick up for repair labels Mar 26, 2026
@jaeko44 jaeko44 added bosun-needs-fix Attached PR with failing CI that Bosun should pick up for repair and removed bosun-needs-fix Attached PR with failing CI that Bosun should pick up for repair labels Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bosun-attached Bosun PR attachment marker bosun-needs-fix Attached PR with failing CI that Bosun should pick up for repair bosun-pr-bosun-created PR created by Bosun and eligible for Bosun automation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants