Change review
A change list with risk flags and reasons, propagation to each replica, and inline or side-by-side diffs.
2 demos
A change list with risk flags and reasons, propagation to each replica, and inline or side-by-side diffs.
One large file's diff with windowed rows, folded regions and word marks, still quick to scroll.
Build one like it
The smallest complete version, in each language CanopyX supports for this view. The demo above is the same API with more data.
using CanopyX;
using CanopyX.Html;
var seen = new DateTimeOffset(2026, 10, 21, 10, 42, 0, TimeSpan.Zero);
var list = new CanopyChangeList("share-changes", "Changes on the deployment share");
list.Action("copy-path", "Copy the path", CanopyChangeActionIcon.Copy, copy: "unc")
.Action("acknowledge", "Acknowledge", CanopyChangeActionIcon.Check);
var payload = list.Add(
"payload", CanopyChangeKind.Added, "Scripts/Logon/agent.exe");
payload.After = new CanopyChangeVersion {
Size = 1_248_256,
Hash = "9f2c41d07be35a18c6e0d4f9a2b7c3e15d8f60a4b9c2e7d1f3a5b8c0d2e4f6a8",
Time = seen
};
payload.Flag("unsigned", "Unsigned", "The file has no embedded signature.",
CanopyTone.Danger);
payload.Values["unc"] = @"\\fs01\deploy\Scripts\Logon\agent.exe";
var settings = list.Add(
"settings", CanopyChangeKind.Modified, "Config/settings.ini");
settings.Before = new CanopyChangeVersion { Size = 612, Time = seen.AddDays(-40) };
settings.After = new CanopyChangeVersion { Size = 618, Time = seen };
settings.Values["unc"] = @"\\fs01\deploy\Config\settings.ini";
settings.Diff = CanopyTextDiff.FromTexts("settings-diff", "settings.ini",
"[agent]\nchannel=stable\ninterval=60\n",
"[agent]\nchannel=preview\ninterval=60\n",
compare: true);
var removed = list.Add(
"legacy", CanopyChangeKind.Removed, "Tools/legacy-inventory.vbs");
removed.Before = new CanopyChangeVersion {
Size = 3_904,
Time = seen.AddDays(-400)
};
removed.Values["unc"] = @"\\fs01\deploy\Tools\legacy-inventory.vbs";
list.SaveHtmlPage("changes.html");Run this snippet (change review, C#)Its output, generated with the site.
import { createChangeList } from "@evotecit/canopyx/grid";
import "@evotecit/canopyx/grid.css";
const unc = path => "\\\\fs01\\deploy\\" + path.replaceAll("/", "\\");
const seen = "2026-10-21T10:42:00Z";
createChangeList(document.getElementById("changes"), {
view: {
protocol: "canopyx/2", type: "changes", id: "share-changes",
label: "Changes on the deployment share",
actions: [
{ id: "copy-path", label: "Copy the path", icon: "copy", copy: "unc" },
{ id: "acknowledge", label: "Acknowledge", icon: "check" }
],
items: [
{
id: "payload", kind: "added", path: "Scripts/Logon/agent.exe",
after: {
size: 1248256,
hash: "9f2c41d07be35a18c6e0d4f9a2b7c3e15d8f60a4b9c2e7d1f3a5b8c0d2e4f6a8",
time: seen
},
flags: [{
id: "unsigned", label: "Unsigned", tone: "danger",
reason: "The file has no embedded signature."
}],
values: { unc: unc("Scripts/Logon/agent.exe") }
},
{
id: "settings", kind: "modified", path: "Config/settings.ini",
before: { size: 612, time: "2026-09-11T10:42:00Z" },
after: { size: 618, time: seen },
values: { unc: unc("Config/settings.ini") },
// With only the two texts, the browser computes the diff.
diff: {
id: "settings-diff",
label: "settings.ini",
before: "[agent]\nchannel=stable\ninterval=60\n",
after: "[agent]\nchannel=preview\ninterval=60\n"
}
},
{
id: "legacy", kind: "removed", path: "Tools/legacy-inventory.vbs",
before: { size: 3904, time: "2025-09-16T10:42:00Z" },
values: { unc: unc("Tools/legacy-inventory.vbs") }
}
]
}
});Run this snippet (change review, JavaScript)Its output, generated with the site.
Try this in the demo
- Flags with reasons
Turn on Flagged only, then open agent.exe.
The details list why each flag is raised, with the versions side by side.
- Diffs in place
Open map-drives.ps1 and switch between Inline and Side by side.
The changed words are marked inside each replaced line; folded lines open where they are.
- Propagation
Look at the Lyon servers.
FS04 has not got the change yet, FS05 holds a different file and FS06 has not reported: three different states, each with its own symbol.
- Host actions
Use Copy the network path on a row.
CanopyX copies the value and tells the host; every other action is the host's to carry out.