Skip to content

feat: add plugin to copy issues as Cacoo cards#75

Open
lollipop-onl wants to merge 8 commits intomainfrom
feat/copy-issue-in-cacoo-format
Open

feat: add plugin to copy issues as Cacoo cards#75
lollipop-onl wants to merge 8 commits intomainfrom
feat/copy-issue-in-cacoo-format

Conversation

@lollipop-onl
Copy link
Collaborator

@lollipop-onl lollipop-onl commented Feb 16, 2026

Summary

  • Add a new plugin that copies Backlog issues as Cacoo card shapes to clipboard (both cacoo/shape MIME type and plain text fallback)
  • Inject copy button on both issue detail pages (#copyKey-help) and issue list pages (.cell-hover-actions)
  • Add main-world-bridge entrypoint (world: "MAIN") for StatusBar feedback via CustomEvent, bypassing isolated world CSP restrictions
  • Extract injectIssueKeyButton utility for detail page button positioning with CSS-based nth-child stacking
  • Dynamic card color based on issue type/priority, auto-calculated card height from title text wrapping

Limitations

  • Due to technical constraints, assignees cannot be set for embedded Cacoo cards

Captures

CleanShot 2026-02-16 at 19 24 51@2x CleanShot 2026-02-16 at 19 26 05@2x

🤖 Generated with Claude Code

lollipop-onl and others added 8 commits February 16, 2026 17:44
Add copyIssueCacooFormat plugin that copies Backlog issues to clipboard
in Cacoo card format. Supports both issue detail pages and issue list
pages with per-row copy buttons using the Cacoo logo icon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use CSS transform to position the detail page button relative to the
existing copy-key button. Add viewBox padding to the Cacoo logo SVG
for better visual spacing within the button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move issue key button injection logic from plugin-specific code into
a reusable utility at utils/inject-issue-key-button.ts with its own
CSS for positioning. Supports up to 5 buttons with deduplication by
key. Remove the now-unnecessary plugin CSS module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the Cacoo copy button from beside the issue link to the
cell-hover-actions container so it appears alongside the existing
action buttons. Add custom styling (transparent bg, currentColor
border, 20×20 size) and block mousedown propagation to prevent
parent anchor navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation

Extract StatusBar access into a generic main-world bridge pattern so
content scripts in isolated world can call page-context APIs via
CustomEvent dispatch. The bridge entrypoint runs in world: MAIN and
the utility provides typed dispatchMainWorld() for extensibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… utility

Replace separate detail/list injection logic with a single
injectIssueButton<T> arrow function that observes both #copyKey-help
and .cell-hover-actions, accepting typed options for SVG, tooltip,
and click handlers. Simplifies the Cacoo plugin to a single call.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Revert the over-abstracted injectIssueButton utility back to direct
observeQuerySelector calls in the plugin. Merge button positioning
CSS from utils/ into the plugin's index.module.css and remove the
now-unused utils/inject-issue-key-button.css.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

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 new “Copy issue as Cacoo card” feature to the Backlog Power Ups extension, enabling users to copy Backlog issues into the clipboard in Cacoo’s cacoo/shape format (with a plain-text fallback), including UI injection on issue detail and list/search pages.

Changes:

  • Introduces copyIssueCacooFormat plugin with buttons on issue detail (#copyKey-help) and issue list rows (.cell-hover-actions) to copy Cacoo-shape JSON + plain text.
  • Adds a MAIN-world content-script bridge (CustomEvent-based) to display StatusBar feedback despite isolated-world restrictions.
  • Adds injectIssueKeyButton utility + CSS stacking rules for positioning multiple buttons next to the issue key.

Reviewed changes

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

Show a summary per file
File Description
utils/main-world-bridge.ts Dispatch helper for isolated-world → MAIN-world actions (StatusBar messaging).
entrypoints/main-world-bridge.content/index.ts MAIN-world listener that calls window.Backlog.StatusBar APIs.
utils/inject-issue-key-button.ts Utility for inserting keyed buttons next to #copyKey-help with dedupe/limit logic.
plugins/index.ts Exports the new copyIssueCacooFormat plugin.
plugins/copyIssueCacooFormat/index.ts Injects UI buttons and wires click handlers to extract issue data and copy to clipboard.
plugins/copyIssueCacooFormat/index.module.css Styles list button + positions stacked issue-key buttons via CSS.
plugins/copyIssueCacooFormat/extract-issue-data.ts Extracts issue data from detail pages and list rows.
plugins/copyIssueCacooFormat/build-cacoo-json.ts Builds Cacoo “card shape” JSON payload (color + height calculation).
plugins/copyIssueCacooFormat/copy-to-clipboard.ts Uses execCommand('copy') + copy event to write custom MIME types.
locales/ja.yaml Adds i18n strings for the new plugin (JA).
locales/en.yaml Adds i18n strings for the new plugin (EN).

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

Comment on lines +72 to +74
<svg role="image" viewBox="-4.5 -6.9 27.9 36.8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.01 22.8995C13.8387 22.8995 12.4668 22.804 10.0092 22.1775C6.50191 21.2945 3.62081 19.421 1.87901 16.8853C-0.67999 13.1503 -0.620291 8.944 2.02811 5.3343C3.79971 2.9119 6.72261 1.18759 10.2419 0.465695C12.6159 -0.0236051 13.9461 -0.0117044 15.9503 0.00619558C16.3142 0.00619558 16.7079 0.0181957 17.1374 0.0181957V5.44169C16.684 5.44169 16.2844 5.4417 15.9145 5.4297C14.0594 5.4178 13.1349 5.4118 11.3215 5.7877C9.11451 6.2412 7.36681 7.2256 6.38851 8.5621C5.11201 10.2924 5.10601 12.0167 6.35271 13.8305C7.34291 15.2684 9.10261 16.3662 11.3335 16.9211C13.6359 17.4998 14.5665 17.4879 17.0479 17.47L17.0896 22.8935C16.6959 22.8935 16.3321 22.8995 16.01 22.8995Z" fill="currentColor"/>
</svg>
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

role="image" is not a valid ARIA role for SVGs (the correct role is img). This may be ignored by assistive tech and can fail accessibility validation. Use role="img" (and ideally add an accessible name via aria-label/title) or drop the role if not needed.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +18
document.execCommand("copy");
document.removeEventListener("copy", handler);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

document.execCommand('copy') can throw (or be blocked) and in that case the copy event handler would remain attached because removal happens after the call. Wrap the execCommand call in a try/finally so removeEventListener always runs, and consider handling the boolean return value to surface a failure to the user.

Suggested change
document.execCommand("copy");
document.removeEventListener("copy", handler);
try {
const success = document.execCommand("copy");
if (!success) {
console.warn("Copy command was blocked or did not succeed.");
}
} finally {
document.removeEventListener("copy", handler);
}

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +107
type: 1,
to: issue.url,
startIndex: 0,
endIndex: issue.key.length - 1,
},
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

endIndex: issue.key.length - 1 becomes -1 when issue.key is empty (which can happen because extraction falls back to "" instead of failing). This produces invalid link ranges in the generated Cacoo payload. Add validation (e.g., throw if key/url/summary are missing) or clamp indices so the payload is always valid.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
if (type === "バグ" || priority === "高") return COLOR_RED;
if (priority === "低") return COLOR_GREEN;
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

resolveColor compares type/priority against Japanese UI labels only ("バグ", "高", "低"). On English Backlog UIs these values will differ, so the color will silently fall back to blue and the feature won't behave as intended. Prefer comparing against language-independent identifiers (if available) or handle both JA/EN label variants.

Suggested change
if (type === "バグ" || priority === "高") return COLOR_RED;
if (priority === "低") return COLOR_GREEN;
const normalizedType = type.trim().toLowerCase();
const normalizedPriority = priority.trim().toLowerCase();
const isBug =
normalizedType === "バグ" || normalizedType === "bug";
const isHighPriority =
normalizedPriority === "高" || normalizedPriority === "high";
const isLowPriority =
normalizedPriority === "低" || normalizedPriority === "low";
if (isBug || isHighPriority) return COLOR_RED;
if (isLowPriority) return COLOR_GREEN;

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +85
return {
key,
summary,
assignee: "",
dueDate: null,
type: "",
priority: "",
url,
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

extractIssueDataFromRow always returns empty type and priority, so cards copied from list/search pages will always use the default color (blue) regardless of the issue's actual type/priority. If dynamic coloring is expected for list pages too, extract these fields from the row (or fetch the issue JSON/HTML) before building the Cacoo payload.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
const EVENT_PREFIX = "backlog-power-ups:main-world";

Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

EVENT_PREFIX is duplicated here and in entrypoints/main-world-bridge.content/index.ts. If one side changes, the bridge silently breaks. Consider exporting the prefix (or a small helper like getMainWorldEventName(action)) from a single shared module and importing it from both places.

Copilot uses AI. Check for mistakes.
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.

2 participants