mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-26 18:45:12 +00:00
Add Git Worktree Explorer canvas (#2344)
* Add Git Worktree Explorer canvas Add an interactive repository, worktree, branch, and commit graph with GitHub PR enrichment, safe inspection actions, and shared lane visualization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 69dd5824-7094-4b82-8cfa-83c2fbe53307 * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Address review feedback for Git Worktree Explorer Security - Quote copied shell commands (git -C / git log) with shell-safe single quoting and --end-of-options via new public/shell-quote.mjs - Move repository path into the untrusted data block of the commit Ask Copilot prompt Performance - Compute branch divergence with a single `git for-each-ref %(ahead-behind:...)` query (Git 2.41+), falling back to rev-list with bounded concurrency (8) instead of one process per branch - Drop the unused `git show --stat` summary from commit details Correctness - Only attach same-repository pull requests to local branches (filter isCrossRepository / headRepositoryOwner) - Resolve the default branch by full name or upstream instead of a suffix match (server + client) - Preserve already-loaded commit pages when a load-more request fails; only reset requests show the error state - Size the lane graph from the widest row (lanes + branch badges + text) and collapse >3 branch tips into a "+N more" badge Accessibility / UX - Add aria-pressed to view toggles and graph nodes; replace the unimplemented ARIA tree with group/button semantics - preventDefault on Space/Enter for branch badges so keyboard activation does not scroll - Use a lane palette that meets 3:1 contrast in both light and dark themes (tested) - Mobile inspector no longer auto-opens on snapshot load and gains a close button Tests: 19 -> 29 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 18350039-5e2b-40f0-b537-bc22cabcbb1b * Migrate git-worktree-explorer to plugins/ manifest layout Move the manifest from extensions/git-worktree-explorer/.github/plugin/plugin.json to plugins/git-worktree-explorer/plugin.json with README and copilot-extension.json, matching the extensions-container migration (#2334). Regenerated marketplace.json and docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 18350039-5e2b-40f0-b537-bc22cabcbb1b * Address follow-up review: shell-specific quoting and sibling row controls - quoteShellArg/formatShellCommand take a target shell; PowerShell doubles apostrophes while POSIX uses '\\''. app.js detects the platform and reports which syntax was copied. - Commit rows no longer nest branch badge buttons inside the row button; the row button and badges are sibling controls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 18350039-5e2b-40f0-b537-bc22cabcbb1b * Make the closed mobile inspector inert and restore focus to the graph When the inspector overlay is dismissed on narrow viewports it is now marked inert/aria-hidden so its controls leave the tab order and accessibility tree, focus returns to the selected graph control, Escape closes it, and viewport changes re-sync the state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 18350039-5e2b-40f0-b537-bc22cabcbb1b --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Aaron Powell <me@aaron-powell.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Copilot-Session: 69dd5824-7094-4b82-8cfa-83c2fbe53307 Copilot-Session: 18350039-5e2b-40f0-b537-bc22cabcbb1b
This commit is contained in:
co-authored by
Copilot
Aaron Powell
Copilot Autofix powered by AI
parent
0976c94103
commit
ca87445cb2
@@ -0,0 +1,90 @@
|
||||
// Each color keeps at least 3:1 contrast against both the light (#ffffff)
|
||||
// and dark (#0d1117) canvas backgrounds so lanes stay traceable in either theme.
|
||||
const COLORS = [
|
||||
"#0969da",
|
||||
"#bf3989",
|
||||
"#bf8700",
|
||||
"#1a7f37",
|
||||
"#8250df",
|
||||
"#bc4c00",
|
||||
"#1b7c83",
|
||||
"#cf222e",
|
||||
];
|
||||
|
||||
export const LANE_COLORS = COLORS;
|
||||
|
||||
function nextColor(index) {
|
||||
return COLORS[index % COLORS.length];
|
||||
}
|
||||
|
||||
export function layoutCommitGraph(commits) {
|
||||
let lanes = [];
|
||||
let colorIndex = 0;
|
||||
let maxLanes = 1;
|
||||
|
||||
const rows = commits.map((commit) => {
|
||||
let laneIndex = lanes.findIndex((lane) => lane.sha === commit.sha);
|
||||
if (laneIndex === -1) {
|
||||
lanes.push({ sha: commit.sha, color: nextColor(colorIndex++) });
|
||||
laneIndex = lanes.length - 1;
|
||||
}
|
||||
|
||||
const before = lanes.map((lane) => ({ ...lane }));
|
||||
const current = before[laneIndex];
|
||||
const after = lanes.map((lane) => ({ ...lane }));
|
||||
const firstParent = commit.parents[0] || null;
|
||||
|
||||
if (!firstParent) {
|
||||
after.splice(laneIndex, 1);
|
||||
} else {
|
||||
const existingFirstParent = after.findIndex((lane, index) =>
|
||||
index !== laneIndex && lane.sha === firstParent
|
||||
);
|
||||
if (existingFirstParent >= 0) {
|
||||
after.splice(laneIndex, 1);
|
||||
} else {
|
||||
after[laneIndex] = { sha: firstParent, color: current.color };
|
||||
}
|
||||
}
|
||||
|
||||
for (const parent of commit.parents.slice(1)) {
|
||||
if (after.some((lane) => lane.sha === parent)) continue;
|
||||
const insertAt = Math.min(laneIndex + 1, after.length);
|
||||
after.splice(insertAt, 0, { sha: parent, color: nextColor(colorIndex++) });
|
||||
}
|
||||
|
||||
const transitions = [];
|
||||
before.forEach((lane, index) => {
|
||||
if (index === laneIndex) return;
|
||||
const target = after.findIndex((candidate) => candidate.sha === lane.sha);
|
||||
if (target >= 0) {
|
||||
transitions.push({ from: index, to: target, color: lane.color, kind: "pass" });
|
||||
}
|
||||
});
|
||||
|
||||
commit.parents.forEach((parent, parentIndex) => {
|
||||
const target = after.findIndex((lane) => lane.sha === parent);
|
||||
if (target >= 0) {
|
||||
transitions.push({
|
||||
from: laneIndex,
|
||||
to: target,
|
||||
color: parentIndex === 0 ? current.color : after[target].color,
|
||||
kind: parentIndex === 0 ? "first-parent" : "merge-parent",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
maxLanes = Math.max(maxLanes, before.length, after.length);
|
||||
lanes = after;
|
||||
return {
|
||||
commit,
|
||||
laneIndex,
|
||||
color: current.color,
|
||||
transitions,
|
||||
lanesBefore: before.length,
|
||||
lanesAfter: after.length,
|
||||
};
|
||||
});
|
||||
|
||||
return { rows, maxLanes };
|
||||
}
|
||||
Reference in New Issue
Block a user