chore: publish from main

This commit is contained in:
github-actions[bot]
2026-10-14 00:57:39 +00:00
parent 9f7877f5cf
commit b22933fdc1
7 changed files with 339 additions and 12 deletions
@@ -10,6 +10,7 @@ Open it by asking Copilot to "open the plugin review board" (canvas id `external
- **Kanban board** with buckets: Unreviewed → Reviewing → Straight reject / Probably reject / Needs review /
Accept → Actioned. Drag cards between buckets (or use the *Bucket* dropdown in the details panel).
Dragging a card back to *Unreviewed* clears its AI review so it's picked up by the next review.
Cards in each bucket are sorted oldest to newest by creation date.
- **Perform review** asks the agent to review every item without an AI review, using
[`review-guidance.md`](./review-guidance.md) and the decisions you've made from the board. Results are
recorded with the `record_review` canvas action and move cards into their recommended bucket.
@@ -26,6 +27,8 @@ Open it by asking Copilot to "open the plugin review board" (canvas id `external
Fast-track never overrides an existing review or a bucket you chose; drag a card to *Unreviewed* to opt it
out and have it reviewed normally. Cards show *First party* / *Third party* pills from the intake labels.
- **Details panel** shows the AI review, the rendered issue body, and all comments.
Clicking outside the panel or successfully submitting an item action closes it;
failed actions leave it open so you can correct or retry them.
- **Quick actions** post `/approve` (with an optional note) or `/reject <reason>` straight to the issue
after a confirm click. The reject reason is prefilled from the AI's suggested comment.
- **Column actions** approve, reject, or re-review every eligible item in a column, including
@@ -56,7 +59,9 @@ not imported; boards start fresh.
## Regression tests
Run `node --test eng/board-column-order.test.mjs` to check oldest-first column ordering.
Run `node --experimental-vm-modules --test eng/board-state-storage.test.mjs` from the repository root
to check both canvases' session-scoped persistence without accessing GitHub.
Run `node --test eng/board-bulk-actions.test.mjs` to check column confirmation, staged feedback,
queue exclusions, and partial failures using in-process fakes.
Run `node --test eng/board-preview.test.mjs` to check preview dismissal and in-flight actions.
@@ -179,7 +179,9 @@ function renderBoard() {
);
const columns = board.columns.map((column) => {
const items = board.items.filter((item) => item.column === column.id && matchesFilter(item));
const items = board.items
.filter((item) => item.column === column.id && matchesFilter(item))
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt) || a.number - b.number);
const cards = h(
"div",
{ class: "column-cards" },
@@ -261,6 +263,7 @@ async function bulkAction(columnId, kind) {
}
await api("POST", "/api/decision", { ...item, kind });
completed++;
if (selected === item.number) closeDrawer();
} catch (error) {
failures.push(`#${item.number}: ${error.message}`);
}
@@ -343,6 +346,7 @@ async function moveItem(number, column) {
const hadReview = column === "unreviewed" && Boolean(findItem(number)?.review);
try {
await api("POST", "/api/move", { number, column });
if (selected === number) closeDrawer();
if (hadReview) toast(`Cleared the AI review for #${number}; it will be included in the next review.`);
} catch (error) {
toast(error.message, { error: true });
@@ -616,10 +620,11 @@ async function decide(kind) {
: `Posted ${result.body.split("\n")[0]} on #${item.number}`,
{ link: result.commentUrl },
);
if (selected === item.number) closeDrawer();
} catch (error) {
toast(error.message, { error: true });
} finally {
resetConfirm();
if (selected === item.number) resetConfirm();
}
}
@@ -672,6 +677,9 @@ ui.search.addEventListener("input", () => {
});
ui.drawerClose.addEventListener("click", closeDrawer);
document.addEventListener("pointerdown", (event) => {
if (selected !== null && !ui.drawer.contains(event.target)) closeDrawer();
}, true);
ui.drawerMove.addEventListener("change", () => moveItem(selected, ui.drawerMove.value));
function hideRereview() {
ui.rereviewPanel.hidden = true;
@@ -711,7 +719,7 @@ ui.rereviewPanel.addEventListener("submit", async (event) => {
try {
const result = await api("POST", "/api/rereview", { number, guidance: ui.rereviewGuidance.value });
toast(result.message);
hideRereview();
if (selected === number) closeDrawer();
} catch (error) {
toast(error.message, { error: true });
} finally {
+7 -1
View File
@@ -10,7 +10,8 @@ authenticated `gh` CLI.
- **Buckets** — `Unreviewed`, `Reviewing`, `Close`, `Request changes`,
`Needs insight`, `Approve`, `Actioned`. The AI recommendation picks the bucket;
dragging a card overrides it.
dragging a card overrides it. Cards in each bucket are sorted oldest to newest
by creation date.
- **AI reviews** — "Review pending" asks the agent to review every PR that has never
been reviewed or whose review is stale. The agent records structured results
(rationale, repo fit, compliance, differentiation, risks, validation, confidence,
@@ -32,6 +33,8 @@ authenticated `gh` CLI.
- **Detail drawer** — AI review, description, conversation, changed files, and checks.
Keyboard focus stays inside the modal drawer and returns to its card on close.
Use Left/Right arrows or Home/End to switch detail tabs.
Clicking outside the drawer or successfully submitting an item action closes it;
failed actions leave it open so you can correct or retry them.
- **GitHub actions** — submit feedback, request changes, close a PR, or approve it and
enable auto-merge with a merge commit. Each action needs two clicks to confirm and
is also recorded in local board history. Approval is pinned to the board's commit
@@ -88,9 +91,12 @@ Results arriving after a manual move, reset, or replacement review are rejected.
## Regression tests
Run `node --test eng/board-column-order.test.mjs` to check oldest-first column ordering.
Run `node --test eng/open-pr-board.test.mjs` from the repository root. GitHub writes
are tested with injected in-process fakes, never the authenticated CLI.
Run `node --test eng/board-bulk-actions.test.mjs` to check column confirmation, staged feedback,
queue exclusions, and partial failures using in-process fakes.
Run `node --test eng/board-preview.test.mjs` to check preview dismissal and in-flight actions.
Run `node --test eng/open-pr-board-drag.test.mjs` to check native card dragging and transfer data.
Run `node --experimental-vm-modules --test eng/board-state-storage.test.mjs` to check
both canvases' session-scoped persistence without accessing GitHub.
+19 -8
View File
@@ -241,7 +241,7 @@ function renderCard(item) {
"article",
{
class: item.number === selectedNumber ? "card selected" : "card",
draggable: !item.decision,
draggable: item.decision ? "false" : "true",
dataset: { number: String(item.number) },
tabindex: "0",
role: "button",
@@ -280,7 +280,9 @@ function renderBoard() {
for (const list of el.board.querySelectorAll(".column-cards")) scroll.set(list.dataset.column, list.scrollTop);
const columns = board.columns.map((column) => {
const items = board.items.filter((item) => item.column === column.id && matchesFilter(item));
const items = board.items
.filter((item) => item.column === column.id && matchesFilter(item))
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt) || a.number - b.number);
const list = h(
"div",
{
@@ -402,6 +404,7 @@ async function bulkAction(columnId, kind) {
method: "POST", body: { number: item.number, kind, comment: item.comment },
});
completed++;
if (selectedNumber === item.number) closeDrawer();
if (result.warning) failures.push(`#${item.number}: ${result.warning}`);
} catch (error) {
failures.push(`#${item.number}: ${error.message}`);
@@ -436,12 +439,14 @@ function reviewTab(item) {
{
class: "btn btn-small btn-primary",
type: "button",
onclick: (event) =>
run(
onclick: async (event) => {
const result = await run(
event.currentTarget,
() => api("/api/review", { method: "POST", body: { numbers: [item.number] } }),
(result) => result.message ?? "Review requested.",
),
);
if (result && selectedNumber === item.number) closeDrawer();
},
},
"Review this PR",
),
@@ -728,12 +733,12 @@ async function submitDecision(button, kind) {
api("/api/decision", { method: "POST", body: { number: item.number, kind, comment } }),
);
decisionsInFlight.delete(item.number);
renderDrawerHeader();
if (selectedNumber === item.number) renderDrawerHeader();
button.textContent = DECISION_BUTTONS[kind];
if (result) {
if (result.warning) toast(result.warning, { error: true, url: result.url });
else toast(`#${item.number}: ${result.status}.`, { url: result.url });
if (currentItem()) loadDetail(item.number, { force: true });
if (selectedNumber === item.number) closeDrawer();
}
}
@@ -778,6 +783,10 @@ el.review.addEventListener("click", (event) =>
el.drawerClose.addEventListener("click", closeDrawer);
document.addEventListener("pointerdown", (event) => {
if (selectedNumber !== null && !el.drawer.contains(event.target)) closeDrawer();
}, true);
document.addEventListener("keydown", (event) => {
if (!el.drawer.classList.contains("open")) return;
if (event.key === "Escape") {
@@ -806,6 +815,7 @@ el.drawerColumn.addEventListener("change", async (event) => {
if (column === item.column) return;
try {
await api("/api/move", { method: "POST", body: { number: item.number, column } });
if (selectedNumber === item.number) closeDrawer();
} catch (error) {
toast(error.message, { error: true });
event.target.value = item.column;
@@ -855,9 +865,10 @@ el.rereviewStart.addEventListener("click", async (event) => {
}),
(value) => value.message ?? "Re-review requested.",
);
if (result) {
if (result && selectedNumber === item.number) {
el.rereviewGuidance.value = "";
el.rereviewPanel.hidden = true;
closeDrawer();
}
});