Skip to content

Commit 2afd506

Browse files
fix: gesture review canvas post-permission flow (#2037)
Load PRs from the active workspace repo instead of the extension directory, surface PR load errors in the canvas UI, and add MediaPipe CDN fallbacks for better runtime reliability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2f9d85e commit 2afd506

1 file changed

Lines changed: 58 additions & 11 deletions

File tree

extensions/gesture-review/extension.mjs

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import http from "node:http";
22
import { execFile } from "node:child_process";
3-
import { fileURLToPath } from "node:url";
4-
import { dirname } from "node:path";
53
import { createCanvas, joinSession } from "@github/copilot-sdk/extension";
64

7-
// This file lives inside the repo worktree, so its directory is a safe cwd for
8-
// git/gh regardless of where the extension host process was launched from.
9-
const extensionDir = dirname(fileURLToPath(import.meta.url));
5+
// The extension should query PRs from the active workspace repository.
106

117
// In-memory state
128
let currentPR = null;
139
let prList = [];
1410
let gestureState = "idle"; // idle | detecting | approved | rejected
1511
let lastDecision = null;
12+
let lastLoadError = null;
1613
const sseClients = new Set();
1714
let loadPRsPromise = null; // in-flight guard for loadOpenPRs
1815
let cachedHTML = null; // cached HTML string
@@ -23,6 +20,13 @@ function broadcast(event, data) {
2320
}
2421
}
2522

23+
function normalizeErrorMessage(error) {
24+
if (!error) return "Unknown error loading pull requests.";
25+
const message = typeof error === "string" ? error : error.message || String(error);
26+
const singleLine = message.split(/\r?\n/)[0].trim();
27+
return singleLine || "Unknown error loading pull requests.";
28+
}
29+
2630
// --- Load open PRs from the repo via the gh CLI ---
2731
function shortDescription(body) {
2832
if (!body) return "";
@@ -40,6 +44,7 @@ function loadOpenPRs() {
4044
if (loadPRsPromise) return loadPRsPromise;
4145

4246
loadPRsPromise = new Promise((resolve) => {
47+
const repoCwd = process.cwd();
4348
execFile(
4449
"gh",
4550
[
@@ -52,16 +57,22 @@ function loadOpenPRs() {
5257
"--json",
5358
"number,title,author,additions,deletions,body",
5459
],
55-
{ cwd: extensionDir, maxBuffer: 1024 * 1024 },
60+
{ cwd: repoCwd, maxBuffer: 1024 * 1024 },
5661
(err, stdout) => {
5762
loadPRsPromise = null;
5863
if (err) {
59-
console.error("gesture-review: failed to load PRs:", err.message);
64+
lastLoadError = normalizeErrorMessage(err);
65+
prList = [];
66+
currentPR = null;
67+
console.error("gesture-review: failed to load PRs:", lastLoadError);
68+
broadcast("prlist", prList);
69+
broadcast("load_error", { message: lastLoadError });
6070
resolve(false);
6171
return;
6272
}
6373
try {
6474
const raw = JSON.parse(stdout);
75+
lastLoadError = null;
6576
prList = raw.map((pr) => ({
6677
title: pr.title,
6778
number: pr.number,
@@ -76,9 +87,12 @@ function loadOpenPRs() {
7687
}
7788
broadcast("prlist", prList);
7889
if (currentPR) broadcast("pr", currentPR);
90+
broadcast("load_error", null);
7991
resolve(true);
8092
} catch (e) {
81-
console.error("gesture-review: failed to parse PRs:", e.message);
93+
lastLoadError = normalizeErrorMessage(e);
94+
console.error("gesture-review: failed to parse PRs:", lastLoadError);
95+
broadcast("load_error", { message: lastLoadError });
8296
resolve(false);
8397
}
8498
},
@@ -112,6 +126,11 @@ const server = http.createServer((req, res) => {
112126
res.write(`event: pr\ndata: ${JSON.stringify(currentPR)}\n\n`);
113127
}
114128
res.write(`event: state\ndata: ${JSON.stringify({ state: gestureState })}\n\n`);
129+
if (lastLoadError) {
130+
res.write(
131+
`event: load_error\ndata: ${JSON.stringify({ message: lastLoadError })}\n\n`,
132+
);
133+
}
115134
sseClients.add(res);
116135
req.on("close", () => sseClients.delete(res));
117136
return;
@@ -635,11 +654,13 @@ function getHTML() {
635654
let allPRs = [];
636655
let currentIndex = 0;
637656
let decisions = {}; // number -> 'approved' | 'rejected'
657+
let prLoadError = null;
638658
639659
// --- SSE ---
640660
const es = new EventSource('/events');
641661
es.addEventListener('prlist', (e) => {
642662
allPRs = JSON.parse(e.data);
663+
if (allPRs.length > 0) prLoadError = null;
643664
// Keep index in range, then show the current PR (or auto-select the first
644665
// undecided one) so the drawer is usable the moment the canvas loads.
645666
if (currentIndex >= allPRs.length) currentIndex = 0;
@@ -670,6 +691,11 @@ function getHTML() {
670691
updateUI();
671692
}
672693
});
694+
es.addEventListener('load_error', (e) => {
695+
const payload = e.data ? JSON.parse(e.data) : null;
696+
prLoadError = payload?.message || null;
697+
updateUI();
698+
});
673699
674700
function showPR(pr) {
675701
prEmpty.classList.add('hidden');
@@ -791,14 +817,33 @@ function getHTML() {
791817
});
792818
}
793819
820+
async function loadScriptWithFallback(sources, timeoutMs = 10000) {
821+
let lastErr;
822+
for (const src of sources) {
823+
try {
824+
await loadScript(src, timeoutMs);
825+
return;
826+
} catch (err) {
827+
lastErr = err;
828+
}
829+
}
830+
throw lastErr || new Error('Script load failed');
831+
}
832+
794833
async function initMediaPipe() {
795834
const INIT_TIMEOUT = 30000;
796835
try {
797836
// Load MediaPipe scripts dynamically with timeout
798837
setLoadingProgress(40, 'Downloading hand tracking library...');
799-
await loadScript('https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js', 15000);
838+
await loadScriptWithFallback([
839+
'https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js',
840+
'https://unpkg.com/@mediapipe/hands/hands.js'
841+
], 15000);
800842
setLoadingProgress(60, 'Downloading camera utilities...');
801-
await loadScript('https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js', 15000);
843+
await loadScriptWithFallback([
844+
'https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js',
845+
'https://unpkg.com/@mediapipe/camera_utils/camera_utils.js'
846+
], 15000);
802847
803848
setLoadingProgress(70, 'Initializing hand detection model...');
804849
@@ -1173,7 +1218,9 @@ function getHTML() {
11731218
function updateUI() {
11741219
if (!decided) {
11751220
cameraWrap.className = 'camera-wrap';
1176-
statusBar.textContent = currentPR ? 'Show thumbs up or thumbs down...' : 'Waiting for a PR...';
1221+
statusBar.textContent = currentPR
1222+
? 'Show thumbs up or thumbs down...'
1223+
: (prLoadError ? ('Unable to load PRs: ' + prLoadError) : 'Waiting for a PR...');
11771224
statusBar.className = 'status-bar';
11781225
}
11791226
}

0 commit comments

Comments
 (0)