Skip to content

canvas-extension: new chat cards extension - #2698

Open
jhauga wants to merge 8 commits into
github:mainfrom
jhauga:extension-chat-cards
Open

canvas-extension: new chat cards extension#2698
jhauga wants to merge 8 commits into
github:mainfrom
jhauga:extension-chat-cards

Conversation

@jhauga

@jhauga jhauga commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Pull Request Checklist

  • I have read and followed the CONTRIBUTING.md guidelines.
  • I have read and followed the Guidance for submissions involving paid services.
  • My contribution adds a new instruction, prompt, agent, skill, workflow, or canvas extension file in the correct directory.
  • The file follows the required naming convention.
  • The content is clearly structured and follows the example format.
  • I have tested my instructions, prompt, agent, skill, workflow, or canvas extension with GitHub Copilot.
  • I have run npm start and verified that README.md is up to date.
  • I am targeting the main branch for this pull request.

Description

Interactive HTML card deck for GitHub Copilot canvas.

  • Agent: Interactive
  • Model: Claude Sonnet 4.6
  • Number of Prompts: 1
  • Post Edits: None

Prompt

I need help with learning the programming language RUST. I want get get a
solid understanding of the language's base. Learn enough to apply the 80/20
rule of producing functional applications from learning the language, and when
to use it vs. when to use other languages. Use chat-cards to help out

Results

test.gif


Type of Contribution

  • New instruction file.
  • New prompt file.
  • New agent file.
  • New plugin.
  • New skill file.
  • New agentic workflow.
  • New canvas extension.
  • Update to existing instruction, prompt, agent, plugin, skill, workflow, or canvas extension.
  • Other (please specify):

By submitting this pull request, I confirm that my contribution abides by the Code of Conduct and will be licensed under the MIT License.

@jhauga
jhauga requested a review from aaronpowell as a code owner August 17, 2026 22:07
Copilot AI balanced review requested due to automatic review settings August 17, 2026 22:07
@github-actions github-actions Bot added canvas-extension PR touches canvas extensions new-submission PR adds at least one new contribution plugin PR touches plugins labels Aug 17, 2026
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

🔒 PR Risk Scan Results

Scanned 9 changed file(s).

Severity Count
🔴 High 0
🟠 Medium 4
ℹ️ Info 0
Severity Rule File Line Match
🟠 unpinned-version-indicator extensions/chat-cards/cards-core.mjs 1125 data.headers.length > 0
🟠 unpinned-version-indicator extensions/chat-cards/cards-core.mjs 1214 series.length > 1
🟠 unpinned-version-indicator extensions/chat-cards/cards-core.mjs 1438 headings.length > 0
🟠 unpinned-version-indicator extensions/chat-cards/package.json 8 "@​​github/copilot-sdk": "latest"
Skipped non-text or missing files
  • extensions/chat-cards/assets/icon.png
  • extensions/chat-cards/assets/preview.png

This is an automated soft-gate report. Findings indicate review targets and do not block merge by themselves.

@github-actions github-actions Bot added the needs-review:HIGH Contributor reputation check flagged HIGH risk label Aug 17, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds the Chat Cards canvas extension and packages it for the Awesome Copilot marketplace.

Changes:

  • Implements interactive cards, forms, charts, and deck management.
  • Adds the canvas UI, local HTTP/SSE transport, and visual assets.
  • Registers and documents the extension plugin.

Reviewed changes

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

Show a summary per file
File Description
plugins/chat-cards/README.md Documents plugin installation.
plugins/chat-cards/plugin.json Defines plugin metadata.
extensions/chat-cards/README.md Documents features and usage.
extensions/chat-cards/package.json Declares the extension package.
extensions/chat-cards/extension.mjs Implements actions, state, and transport.
extensions/chat-cards/copilot-extension.json Provides extension metadata.
extensions/chat-cards/cards-core.mjs Builds and sanitizes card content.
extensions/chat-cards/assets/canvas.html Implements the interactive canvas UI.
extensions/chat-cards/assets/icon.png Provides the extension icon.
extensions/chat-cards/assets/preview.png Provides the marketplace preview.
docs/README.plugins.md Adds the plugin to documentation.
.github/plugin/marketplace.json Registers the marketplace entry.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread extensions/chat-cards/cards-core.mjs
Comment on lines +1267 to +1269
return `<label class="mcc-choice" for="${choiceId}">
<input type="${type}" id="${choiceId}" name="${name}" value="${escapeHtml(option.value ?? option.label)}"${checked}>
${escapeHtml(option.label)}</label>`;
Comment on lines +747 to +750
function seriesScale(series) {
const values = series.flatMap((s) => s.values).filter((value) => Number.isFinite(value));
return niceScale(Math.max(...values, 0), 6, values.every((value) => Number.isInteger(value)));
}
Comment thread extensions/chat-cards/extension.mjs Outdated
Comment on lines +463 to +467
cardId: { type: "string", description: "The id returned when the card was created" },
kind: { type: "string", enum: Object.keys(CARD_BUILDERS) },
},
required: ["cardId", "kind"],
additionalProperties: true,
Comment on lines +708 to +710
document.addEventListener("mouseover", function (event) {
var term = event.target.closest(".mcc-term");
if (!term) return;
Comment thread extensions/chat-cards/README.md Outdated
Comment thread plugins/chat-cards/README.md Outdated
Comment thread extensions/chat-cards/README.md Outdated
Copilot AI review requested due to automatic review settings August 17, 2026 22:13
@jhauga
jhauga marked this pull request as draft August 17, 2026 22:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (6)

extensions/chat-cards/cards-core.mjs:1219

  • Chart cards expose kinds such as chart-bar, but update_card only accepts builder keys (chart, tabs, etc.) and tells callers to pass the card's returned kind. Consequently, a chart kind returned by create/list cannot be passed back to update. Keep the stored/public kind aligned with the chart builder key; the subtype already remains in the replacement spec's type.
      kind: `chart-${type}`,

extensions/chat-cards/cards-core.mjs:1268

  • required is calculated but never emitted for checkbox or radio inputs. Therefore form.checkValidity() accepts a required choice group with nothing selected, despite the required marker shown to the user. Apply native required semantics for radio groups and explicitly validate at least one selected value for multi-checkbox groups.
<input type="${type}" id="${choiceId}" name="${name}" value="${escapeHtml(option.value ?? option.label)}"${checked}>

plugins/chat-cards/README.md:14

  • This fence uses two backticks, so the installation command renders as literal delimiter text instead of a code block. Use a valid Markdown code block.
``bash
copilot plugin install chat-cards@awesome-copilot
``

extensions/chat-cards/cards-core.mjs:116

  • Caller-supplied HTML can retain extension-reserved classes, allowing sanitized content to trigger privileged delegated handlers. For example, an allowed <a class="mcc-remove-card" href="https://example.com"> survives sanitization, and clicking it removes the enclosing card. Reject reserved mcc- classes when rebuilding untrusted HTML.
  "*": { class: (v) => SAFE_CLASS_PATTERN.test(v), title: () => true },

extensions/chat-cards/extension.mjs:217

  • Bar/line series values allow negatives, but both renderers use a zero-only scale: negative bars are clamped to zero and negative line points are placed below the plot. This silently misrepresents valid schema input. Either reject negative values in both the action schema and core builder (including updates), or implement a signed min/max axis.
        values: { type: "array", items: { type: "number" } },

extensions/chat-cards/cards-core.mjs:313

  • Tutor definitions are exposed only through mouseover/mouseout, while this generated span is not focusable and has no accessible description. Keyboard and screen-reader users therefore cannot access an advertised card feature. Make terms keyboard-focusable and mirror tooltip behavior on focus/blur, or associate the tip through accessible descriptive markup.
        `<span class="mcc-term" data-tip="${escapeHtml(term.tip)}">${match[0]}</span>`;

@github-actions github-actions Bot removed the needs-review:HIGH Contributor reputation check flagged HIGH risk label Aug 18, 2026
@jhauga

jhauga commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Made edits based on code review, then thought I found a bug with this canvas extension; but turns out that GitHub Copilot has a bug. See github/app issue 2930. I tested several extensions from awesome-copilot. All with the same results that are stated in the issue - when canvas panel is left open, and app closed, then app has to be quit in order to reopen. Extensions tested include:

  • Agent Arcade
  • Token Pac-Man
  • Brainmaxxing

@jhauga
jhauga marked this pull request as ready for review August 18, 2026 02:50
Copilot AI review requested due to automatic review settings August 18, 2026 02:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated 4 comments.

Suppressed comments (6)

extensions/chat-cards/cards-core.mjs:1233

  • Chart cards report their kind as chart-bar, chart-line, etc., but update_card accepts only kind: "chart" and dispatches through the chart builder. Consequently, using the kind returned by creation or list_cards always makes chart updates fail as an unknown kind. Keep the public kind aligned with KIND_SCHEMAS/CARD_BUILDERS; the chart subtype is already present in the replacement spec.
      kind: `chart-${type}`,

extensions/chat-cards/README.md:130

  • This description incorrectly calls copilot-extension.json the marketplace plugin manifest. It is local extension discovery metadata; the Agent Plugin manifest for the Awesome Copilot submission is plugins/chat-cards/plugin.json.
  copilot-extension.json  Plugin manifest for the awesome-copilot submission

extensions/chat-cards/README.md:93

  • The copied directory has no nested extension/ folder, so this installation step points users at a path that does not exist. The local extension root is the copied chat-cards/ directory containing copilot-extension.json and extension.mjs.

This issue also appears on line 130 of the same file.

`npm install` pulls the extension's single dependency (`@github/copilot-sdk`).
Then register the `extension/` folder with your Copilot client as a local
extension, start a session, and ask the agent to open the Chat Cards canvas.

extensions/chat-cards/extension.mjs:166

  • A newly created instance leaves updatedAt undefined, so every unrelated flush serializes it with a fresh current timestamp without saving that timestamp back to the instance. Merely keeping an empty canvas in memory therefore makes it perpetually "newest" and can evict genuinely updated decks from the eight-deck retention set. Initialize the timestamp once when the instance is created.
      updatedAt: restored?.updatedAt,

extensions/chat-cards/assets/canvas.html:692

  • Template substitution is performed repeatedly over the progressively modified prompt, so a literal placeholder entered as one field's value is interpreted as another template token on a later iteration. For example, a = "{{b}}" turns {{a}} / {{b}} into the value of b twice. Replace tokens in a single pass so inserted user values remain literal.
      var prompt = template;
      Object.keys(values).forEach(function (name) {
        prompt = prompt.split("{{" + name + "}}").join(values[name]);
      });
      return prompt;

extensions/chat-cards/cards-core.mjs:1453

  • isPlayableMediaUrl explicitly accepts data:audio/*, so this video-card validation permits an audio-only data URI even though the action schema, documentation, error text, and <video> card contract all require data:video/*. Restrict this call to the advertised video schemes.
  if (!isPlayableMediaUrl(src)) {

Comment thread plugins/chat-cards/plugin.json
Comment thread extensions/chat-cards/copilot-extension.json
Comment thread extensions/chat-cards/cards-core.mjs Outdated
Comment thread extensions/chat-cards/extension.mjs Outdated
Comment on lines +121 to +123
mkdirSync(path.dirname(STATE_FILE), { recursive: true });
writeFileSync(`${STATE_FILE}.tmp`, JSON.stringify({ version: STATE_VERSION, instances: kept }));
renameSync(`${STATE_FILE}.tmp`, STATE_FILE);
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 18, 2026 03:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated 2 comments.

Suppressed comments (9)

extensions/chat-cards/cards-core.mjs:1237

  • Chart cards report kinds such as chart-bar, but update_card only accepts the chart discriminant and passes it to CARD_BUILDERS. As a result, the kind returned by create/list cannot be used to update a chart. Keep the stored/API kind aligned with the builder key.
      kind: `chart-${type}`,

extensions/chat-cards/extension.mjs:123

  • Multiple extension processes share both this fixed temporary path and a whole-store snapshot loaded only at startup. Concurrent sessions can race on state.json.tmp, and the last process to flush can overwrite newer decks written by another process. Use per-instance state files or an inter-process lock with a read/merge/write operation and unique temporary files.
    writeFileSync(`${STATE_FILE}.tmp`, JSON.stringify({ version: STATE_VERSION, instances: kept }));
    renameSync(`${STATE_FILE}.tmp`, STATE_FILE);

extensions/chat-cards/cards-core.mjs:882

  • This SVG is exposed as an image but has no accessible name; the nested data-point <title> elements do not name the root graphic. Add an aria-label so screen-reader users can identify the chart.
    `<svg viewBox="0 0 ${CHART_WIDTH} ${CHART_HEIGHT}" role="img" xmlns="http://www.w3.org/2000/svg">` +

extensions/chat-cards/cards-core.mjs:68

  • This validator also accepts data:audio/*, although the video action schema and its error message explicitly allow only data:video/*. Such input is therefore accepted into a video card contrary to the API contract.
  return isHttpUrl(v) || isDataUrlOfType(v, "video") || isDataUrlOfType(v, "audio") || isBlobUrl(v);

extensions/chat-cards/extension.mjs:389

  • items: {} accepts values such as null and numbers, but normalizeFieldOptions dereferences option.value for every non-string option, so schema-valid action input can throw instead of producing a form. Encode the documented string-or-object union in the schema.
          items: {},

extensions/chat-cards/README.md:93

  • The copied folder contains extension.mjs at its root; there is no child extension/ directory to register. This installation step therefore directs users to a nonexistent path.
Then register the `extension/` folder with your Copilot client as a local
extension, start a session, and ask the agent to open the Chat Cards canvas.

extensions/chat-cards/README.md:11

  • Use “inline in the conversation” rather than “inline of the conversation.”
inline of the conversation, instead of rendering the HTML in a separate panel.

extensions/chat-cards/cards-core.mjs:845

  • This SVG is exposed as an image but has no accessible name; the nested data-point <title> elements do not name the root graphic. Add an aria-label so screen-reader users can identify the chart.

This issue also appears on line 882 of the same file.

    `<svg viewBox="0 0 ${CHART_WIDTH} ${CHART_HEIGHT}" role="img" xmlns="http://www.w3.org/2000/svg">` +

extensions/chat-cards/cards-core.mjs:925

  • This pie/donut SVG has role="img" but no accessible name. The slice-level tooltips do not name the root graphic, so add a label for the generated chart type.
  return `<svg viewBox="0 0 ${size} ${size}" role="img" xmlns="http://www.w3.org/2000/svg">${paths}</svg>`;

Comment thread extensions/chat-cards/extension.mjs Outdated
Comment thread extensions/chat-cards/extension.mjs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 18, 2026 03:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated 1 comment.

Suppressed comments (7)

extensions/chat-cards/cards-core.mjs:1237

  • Chart creation and list_cards return kinds such as chart-bar, but update_card only accepts the builder key chart. Passing the kind returned by the public API therefore fails schema validation or buildCard. Keep the card kind stable and carry the chart subtype in type.
      kind: `chart-${type}`,

extensions/chat-cards/cards-core.mjs:505

  • A same-indent switch between unordered and ordered markers is emitted as another <li> in the currently open list. For example, - first followed by 1. second renders both items in one <ul>, changing the document semantics. Close and reopen the list when the marker type changes at the current indentation.
    if (stack.length === 0 || indent > stack[stack.length - 1].indent + 1) {
      stack.push({ indent, tag });
      html += `<${tag}><li>${renderInline(match[3])}`;
    } else {
      html += `</li><li>${renderInline(match[3])}`;

extensions/chat-cards/cards-core.mjs:1300

  • The text labeling a checkbox/radio group is a plain <label> with no associated control, so assistive technology does not receive it as the fieldset's group name. A fieldset must use a <legend> for this purpose.
    return `<fieldset class="mcc-field" style="border:none;padding:0;margin:0"${requireOne}>
<label>${label}${requiredMark}</label>
${choices}${help}</fieldset>`;

extensions/chat-cards/README.md:93

  • There is no child extension/ directory in the copied layout; extension.mjs is directly inside chat-cards/. This instruction sends manual installers to a nonexistent folder. Refer to the copied chat-cards/ folder itself.
Then register the `extension/` folder with your Copilot client as a local
extension, start a session, and ask the agent to open the Chat Cards canvas.

extensions/chat-cards/extension.mjs:392

  • The schema description restricts choices to strings or { label, value } objects, but items: {} accepts every JSON value. A value such as null passes the action schema and then throws in normalizeFieldOptions when option.value is read. Encode the documented union in the schema so invalid action input is rejected predictably.
        options: {
          type: "array",
          description: "Choices for select/checkbox/radio; strings or { label, value } objects",
          items: {},
        },

extensions/chat-cards/extension.mjs:382

  • required: ["name"] only requires the property to exist, so an empty name is accepted. The browser then creates a nameless control and collectValues explicitly skips it, silently dropping that answer and leaving any matching prompt token unresolved. Require a non-empty field name.
        name: { type: "string" },

extensions/chat-cards/README.md:130

  • This file is the canvas extension manifest; the plugin manifest is plugins/chat-cards/plugin.json. Calling it the plugin manifest makes the documented layout misleading for contributors and manual installers.
  copilot-extension.json  Plugin manifest for the awesome-copilot submission

Comment thread extensions/chat-cards/extension.mjs Outdated
Comment on lines +122 to +125
writeFileSync(`${STATE_FILE}.tmp`, JSON.stringify({ version: STATE_VERSION, instances: kept }), {
mode: 0o600,
});
renameSync(`${STATE_FILE}.tmp`, STATE_FILE);
@jhauga
jhauga marked this pull request as draft August 18, 2026 03:26
@jhauga
jhauga marked this pull request as ready for review August 18, 2026 03:39
Copilot AI review requested due to automatic review settings August 18, 2026 03:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated 1 comment.

Suppressed comments (5)

extensions/chat-cards/cards-core.mjs:1237

  • Chart cards are returned as chart-bar, chart-line, etc., but update_card and CARD_BUILDERS only accept the kind chart. Because list_cards exposes this stored value and the update action asks callers to pass the card's kind, chart updates will be rejected. Keep the persisted kind aligned with the dispatcher and retain the subtype in the chart spec.
      kind: `chart-${type}`,

extensions/chat-cards/extension.mjs:508

  • The empty item schema accepts values such as null and numbers even though the contract says strings or {label, value} objects; normalizeFieldOptions then dereferences option.value, so schema-valid input can fail. It also permits select/radio fields with no choices, producing unusable controls. Encode the option union and non-empty requirement in the schema and validate it in the builder.
        options: {
          type: "array",
          description: "Choices for select/checkbox/radio; strings or { label, value } objects",
          items: {},
        },

extensions/chat-cards/cards-core.mjs:1299

  • A <label> without a for attribute does not provide an accessible name for this fieldset, so screen readers lose the checkbox/radio group's question. Use the fieldset's native <legend> element.
<label>${label}${requiredMark}</label>

extensions/chat-cards/assets/canvas.html:818

  • Model-defined actions are exposed only through the pointer context-menu event. Unlike the other card actions, there is no visible focusable trigger, and opening this popup does not move keyboard focus into it. Add an Actions button plus menu semantics and focus management so keyboard and touch users can invoke these prompts.
  document.addEventListener("contextmenu", function (event) {
    var article = event.target.closest(".mcc-card");
    if (!article) { hideMenu(); return; }
    var config = configs[article.getAttribute("data-card-id")] || {};
    var actions = config.contextActions || [];
    event.preventDefault();

extensions/chat-cards/assets/canvas.html:867

  • Card ordering is available only through native drag events, so keyboard users cannot perform this persisted operation. Provide focusable Move up/Move down controls or an equivalent keyboard interaction and announce the resulting order change.
  document.addEventListener("dragstart", function (event) {
    var head = event.target.closest('.mcc-head[draggable="true"]');
    if (!head) return;
    draggingCard = head.closest(".mcc-card");
    draggingCard.classList.add("mcc-dragging");
    event.dataTransfer.effectAllowed = "move";

Comment on lines +233 to +236
for (const { name } of ranked.slice(MAX_PERSISTED_DECKS)) {
// A deck this process holds open outranks the cap: dropping its file
// would lose cards the user can still see in the canvas.
if (owned.has(name)) continue;
@jhauga
jhauga marked this pull request as draft August 18, 2026 03:45
@jhauga
jhauga marked this pull request as ready for review August 18, 2026 03:57
Copilot AI review requested due to automatic review settings August 18, 2026 03:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated 1 comment.

Suppressed comments (8)

extensions/chat-cards/cards-core.mjs:1237

  • Chart cards report kinds such as chart-bar, but update_card and CARD_BUILDERS only recognize chart. An agent following list_cards will therefore pass a kind that the update schema rejects (or that buildCard reports as unknown). Keep the public kind stable as chart; the subtype already remains in the replacement spec's type field.
      kind: `chart-${type}`,

extensions/chat-cards/cards-core.mjs:1176

  • Pie and donut values are not runtime-validated like bar/line values. A negative slice is silently clamped to zero in the SVG and legend while the data table still displays the negative value, so the card presents contradictory data. Reject non-finite and negative slice values before rendering.
  if ((type === "pie" || type === "donut") && (!options.values || options.values.length === 0)) {
    throw new Error("Pie and donut charts require values.");

extensions/chat-cards/cards-core.mjs:505

  • A list-marker change at the same indentation never closes and reopens the list. For example, - bullet followed by 1. numbered is rendered entirely as a <ul>, losing the ordered-list semantics. Handle a same-depth ul/ol transition by closing the current list and opening the new tag.
    if (stack.length === 0 || indent > stack[stack.length - 1].indent + 1) {
      stack.push({ indent, tag });
      html += `<${tag}><li>${renderInline(match[3])}`;
    } else {
      html += `</li><li>${renderInline(match[3])}`;

extensions/chat-cards/cards-core.mjs:1457

  • This video-specific API uses a general media predicate that also accepts data:audio/*, despite the schema, README, and error message allowing only data:video/*. Such input produces an audio resource inside a video card rather than being rejected. Use a video-specific check here.
  if (!isPlayableMediaUrl(src)) {

extensions/chat-cards/README.md:93

  • The installation steps copy this directory directly to .../extensions/chat-cards/, so there is no nested extension/ folder to register. Following this instruction leaves users pointing the client at a nonexistent path; direct them to the copied chat-cards/ directory instead.
Then register the `extension/` folder with your Copilot client as a local
extension, start a session, and ask the agent to open the Chat Cards canvas.

extensions/chat-cards/cards-core.mjs:1076

  • The tabs expose ARIA tab roles but implement only mouse/click activation. All tabs remain in the page tab order and there is no Left/Right/Home/End keyboard navigation or focus transfer, so the widget does not provide the expected keyboard interaction for a tablist. Implement roving tabindex and keyboard selection alongside the click handler.
        `<button type="button" class="mcc-tab" role="tab" id="${groupId}-tab-${index}" ` +
        `aria-selected="${selected}" aria-controls="${groupId}-panel-${index}">` +
        `${escapeHtml(tab.label ?? `Tab ${index + 1}`)}</button>`

extensions/chat-cards/extension.mjs:1021

  • Context-action prompts are also silently shortened to 16,000 characters. Because this endpoint returns success and the UI ignores delivered, a long selection can change the instruction sent to the conversation with no indication to the user. Reject oversized input rather than silently changing it.
      const prompt = String(body.prompt ?? "").slice(0, MAX_PROMPT_CHARS);

extensions/chat-cards/extension.mjs:995

  • Form prompts longer than 16,000 characters are silently truncated before sending and persistence, yet the client receives delivered: true and tells the user the full response was sent. This loses form data without warning. Reject over-limit prompts (so the existing client fallback exposes the complete prompt) or return an explicit truncation result instead of slicing.
      const prompt = String(body.prompt ?? "").slice(0, MAX_PROMPT_CHARS);

Comment thread extensions/chat-cards/extension.mjs Outdated
const existing = canvasServers.get(instanceId);
if (existing) return existing;

const server = http.createServer(handleCanvasRequest);
Copilot AI review requested due to automatic review settings August 18, 2026 04:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (5)

extensions/chat-cards/cards-core.mjs:1237

  • Chart cards report kinds such as chart-bar, but update_card and CARD_BUILDERS only accept chart. Following the documented instruction to pass the card's returned/listed kind therefore makes every chart update fail. Keep the stored/returned kind aligned with the update discriminator; the chart subtype is already carried by type.
      kind: `chart-${type}`,

extensions/chat-cards/cards-core.mjs:67

  • This predicate accepts every HTTP(S) URL, including streaming-platform page URLs that the action explicitly rejects. Such URLs pass validation but cannot be played by the generated <video> element, resulting in a broken card. Either validate a supported direct-media URL contract or stop claiming this check enforces one.
export function isPlayableMediaUrl(value) {
  const v = String(value ?? "").trim();
  return isHttpUrl(v) || isDataUrlOfType(v, "video") || isDataUrlOfType(v, "audio") || isBlobUrl(v);

extensions/chat-cards/cards-core.mjs:1300

  • The checkbox/radio group has a <fieldset> but no <legend>; the unassociated <label> does not provide the group an accessible name. Screen-reader users therefore lose the question/context shared by these choices. Use a legend for the group label.
    return `<fieldset class="mcc-field" style="border:none;padding:0;margin:0"${requireOne}>
<label>${label}${requiredMark}</label>
${choices}${help}</fieldset>`;

extensions/chat-cards/README.md:140

  • This says the server uses an ephemeral port, but startCanvasServer normally binds within the fixed 21750–21789 range and only falls back to an ephemeral port. Describe it as a loopback-only port so the security documentation matches the implementation.
- The card server binds to `127.0.0.1` on an ephemeral port; every request must carry the
  per-canvas token, and request bodies are size-capped.

extensions/chat-cards/cards-core.mjs:1194

  • A series may contain more or fewer values than there are labels. The SVG renderer silently drops extra values with slice(0, labels.length), while missing values become blank table cells, so accepted input can be misrepresented. Reject mismatched lengths before rendering.
    for (const s of options.series ?? []) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

canvas-extension PR touches canvas extensions new-submission PR adds at least one new contribution plugin PR touches plugins

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants