+
- Dead code
+ Reference coverage
-
- {deadCount.toLocaleString()} dead
+
+ {deadCount.toLocaleString()} with no known inbound references
@@ -215,7 +215,7 @@ export function FilterPanel({
+
+ No known inbound references does not prove code is unused. Static analysis
+ may miss constructors, implicit or framework calls, and callbacks.
+
+
{/* Legend (only meaningful while colored by status) */}
{deadCodeView && (
diff --git a/graph-ui/src/components/GraphTab.deadcode.test.tsx b/graph-ui/src/components/GraphTab.deadcode.test.tsx
index 43f880f30..d9dac29d6 100644
--- a/graph-ui/src/components/GraphTab.deadcode.test.tsx
+++ b/graph-ui/src/components/GraphTab.deadcode.test.tsx
@@ -11,15 +11,69 @@ vi.mock("./GraphScene", () => ({
computeCameraTarget: () => null,
}));
+/* Server-classified layout: three unreferenced-coverage candidates (constructor,
+ * framework method, callback) plus nodes that must not enter that set just
+ * because in_calls is zero or absent. Edges match the reported degrees. */
const SAMPLE: GraphData = {
nodes: [
{
- id: 1, x: 0, y: 0, z: 0, label: "Function", name: "orphan",
- file_path: "src/orphan.ts", size: 1, color: "#fff", status: "dead", in_calls: 0,
+ id: 1, x: 0, y: 0, z: 0, label: "Method", name: "constructor",
+ file_path: "src/widget.ts", size: 1, color: "#fff", status: "dead", in_calls: 0,
},
{
- id: 2, x: 1, y: 0, z: 0, label: "Function", name: "used",
- file_path: "src/used.ts", size: 1, color: "#fff", status: "normal", in_calls: 3,
+ id: 2, x: 1, y: 0, z: 0, label: "Method", name: "onCreate",
+ file_path: "src/activity.ts", size: 1, color: "#fff", status: "dead", in_calls: 0,
+ },
+ {
+ id: 3, x: 2, y: 0, z: 0, label: "Function", name: "handleClick",
+ file_path: "src/ui.ts", size: 1, color: "#fff", status: "dead", in_calls: 0,
+ },
+ {
+ id: 4, x: 3, y: 0, z: 0, label: "Function", name: "processRequest",
+ file_path: "src/used.ts", size: 1, color: "#fff", status: "normal", in_calls: 2,
+ },
+ {
+ id: 5, x: 4, y: 0, z: 0, label: "Function", name: "formatName",
+ file_path: "src/format.ts", size: 1, color: "#fff", status: "normal", in_calls: 0,
+ },
+ {
+ id: 6, x: 5, y: 0, z: 0, label: "Function", name: "main",
+ file_path: "src/main.ts", size: 1, color: "#fff", status: "entry", in_calls: 0,
+ },
+ {
+ id: 7, x: 6, y: 0, z: 0, label: "Function", name: "testProcess",
+ file_path: "src/used.test.ts", size: 1, color: "#fff", status: "test", in_calls: 0,
+ },
+ {
+ id: 8, x: 7, y: 0, z: 0, label: "Function", name: "publicApi",
+ file_path: "src/api.ts", size: 1, color: "#fff", status: "exported", in_calls: 0,
+ },
+ {
+ id: 9, x: 8, y: 0, z: 0, label: "Class", name: "App",
+ file_path: "src/app.ts", size: 1, color: "#fff", status: "structural",
+ },
+ {
+ id: 10, x: 9, y: 0, z: 0, label: "Function", name: "mystery",
+ file_path: "src/mystery.ts", size: 1, color: "#fff", in_calls: 0,
+ },
+ ],
+ edges: [
+ { source: 6, target: 4, type: "CALLS" },
+ { source: 8, target: 4, type: "CALLS" },
+ { source: 4, target: 5, type: "USAGE" },
+ ],
+ total_nodes: 10,
+};
+
+const NO_CANDIDATES: GraphData = {
+ nodes: [
+ {
+ id: 1, x: 0, y: 0, z: 0, label: "Function", name: "used",
+ file_path: "src/used.ts", size: 1, color: "#fff", status: "single", in_calls: 1,
+ },
+ {
+ id: 2, x: 1, y: 0, z: 0, label: "Function", name: "main",
+ file_path: "src/main.ts", size: 1, color: "#fff", status: "entry", in_calls: 0,
},
],
edges: [{ source: 2, target: 1, type: "CALLS" }],
@@ -46,19 +100,92 @@ describe("GraphTab dead-code filters", () => {
vi.unstubAllGlobals();
});
- it("shows the dead count and filters to only dead code on toggle", async () => {
+ it("reports no known inbound references without calling the code dead or live", async () => {
+ mockLayoutFetch(SAMPLE);
+ render();
+
+ expect(await screen.findByText("Filters")).toBeInTheDocument();
+ expect(screen.getByText("Reference coverage")).toBeInTheDocument();
+ expect(screen.getByText("3 with no known inbound references")).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ "No known inbound references does not prove code is unused. Static analysis may miss constructors, implicit or framework calls, and callbacks.",
+ ),
+ ).toBeInTheDocument();
+ expect(screen.queryByText("Dead code")).not.toBeInTheDocument();
+ expect(screen.queryByText(/^\d+ dead$/)).not.toBeInTheDocument();
+ expect(screen.queryByText("Show only dead code")).not.toBeInTheDocument();
+ expect(screen.queryByText("Dead (0 callers)")).not.toBeInTheDocument();
+ /* Caveat is visible before status coloring; legend is not. */
+ expect(screen.queryByText("No known inbound references")).not.toBeInTheDocument();
+ expect(screen.queryByText(/filtered from/)).not.toBeInTheDocument();
+ });
+
+ it("colors the legend as coverage, not as proven dead code", async () => {
+ mockLayoutFetch(SAMPLE);
+ render();
+
+ expect(await screen.findByText("Filters")).toBeInTheDocument();
+ fireEvent.click(screen.getByRole("button", { name: "Color by status" }));
+
+ expect(screen.getByText("No known inbound references")).toBeInTheDocument();
+ expect(screen.getByText("One caller")).toBeInTheDocument();
+ expect(screen.queryByText("Dead code")).not.toBeInTheDocument();
+ expect(screen.queryByText(/^\d+ dead$/)).not.toBeInTheDocument();
+ expect(screen.queryByText("Show only dead code")).not.toBeInTheDocument();
+ expect(screen.queryByText("Dead (0 callers)")).not.toBeInTheDocument();
+ });
+
+ it("filters to the backend dead category and restores the full layout", async () => {
mockLayoutFetch(SAMPLE);
render();
- /* Panel loaded; the dead-code section reports one dead node. */
expect(await screen.findByText("Filters")).toBeInTheDocument();
- expect(screen.getByText("1 dead")).toBeInTheDocument();
+ expect(screen.getByText(/10 nodes/)).toBeInTheDocument();
+
+ fireEvent.click(
+ screen.getByRole("button", {
+ name: /Show only nodes with no known inbound references/,
+ }),
+ );
+ expect(await screen.findByText(/filtered from 10/)).toBeInTheDocument();
+ expect(screen.getByText(/3 nodes/)).toBeInTheDocument();
+
+ const search = screen.getByPlaceholderText("Search...");
+ for (const name of ["constructor", "onCreate", "handleClick"]) {
+ fireEvent.change(search, { target: { value: name } });
+ expect(screen.getByRole("button", { name: new RegExp(name) })).toBeInTheDocument();
+ }
+ /* Server-provided non-dead statuses stay out, even with in_calls of 0. */
+ for (const name of ["processRequest", "formatName", "main", "testProcess", "publicApi", "App", "mystery"]) {
+ fireEvent.change(search, { target: { value: name } });
+ expect(screen.queryByRole("button", { name: new RegExp(name) })).not.toBeInTheDocument();
+ }
- /* Both nodes visible initially — no "filtered from" notice. */
+ fireEvent.click(
+ screen.getByRole("button", {
+ name: /Show only nodes with no known inbound references/,
+ }),
+ );
expect(screen.queryByText(/filtered from/)).not.toBeInTheDocument();
+ expect(screen.getByText(/10 nodes/)).toBeInTheDocument();
+ });
+
+ it("shows a zero count and empty candidate selection when none are classified dead", async () => {
+ mockLayoutFetch(NO_CANDIDATES);
+ render();
+
+ expect(await screen.findByText("Filters")).toBeInTheDocument();
+ expect(screen.getByText("0 with no known inbound references")).toBeInTheDocument();
+ expect(screen.getByText(/2 nodes/)).toBeInTheDocument();
- /* Toggling "Show only dead code" hides the non-dead node. */
- fireEvent.click(screen.getByRole("button", { name: /Show only dead code/ }));
- expect(await screen.findByText(/filtered from 2/)).toBeInTheDocument();
+ fireEvent.click(
+ screen.getByRole("button", {
+ name: /Show only nodes with no known inbound references/,
+ }),
+ );
+ expect(screen.getByText("All nodes filtered out")).toBeInTheDocument();
+ expect(screen.getByText("Filters")).toBeInTheDocument();
+ expect(screen.getByText("0 with no known inbound references")).toBeInTheDocument();
});
});
diff --git a/graph-ui/src/lib/colors.ts b/graph-ui/src/lib/colors.ts
index 64ef0e990..7ff58945b 100644
--- a/graph-ui/src/lib/colors.ts
+++ b/graph-ui/src/lib/colors.ts
@@ -20,13 +20,14 @@ export function colorForLabel(label: string): string {
return LABEL_COLORS[label] ?? DEFAULT_COLOR;
}
-/* Dead-code status → color (matches layout3d.c status strings).
- * dead zero callers + zero usages, not entry/test/exported
+/* Status → color (matches layout3d.c status strings).
+ * dead no known inbound CALLS/USAGE/CALL_REFERENCE; not proven unused
+ * (constructors, framework dispatch, and callbacks may be missed)
* single exactly one caller
* entry entry points / routes
* test test code
* normal healthy (>=2 callers)
- * exported/structural → dimmed grey (not dead-code candidates) */
+ * exported/structural → dimmed grey (not unreferenced-coverage candidates) */
const STATUS_COLORS: Record = {
dead: "#ef4444",
single: "#f97316",
@@ -44,7 +45,7 @@ export function colorForStatus(status?: string): string {
}
export const STATUS_LEGEND: { status: string; label: string; color: string }[] = [
- { status: "dead", label: "Dead (0 callers)", color: STATUS_COLORS.dead },
+ { status: "dead", label: "No known inbound references", color: STATUS_COLORS.dead },
{ status: "single", label: "One caller", color: STATUS_COLORS.single },
{ status: "entry", label: "Entry / route", color: STATUS_COLORS.entry },
{ status: "test", label: "Test", color: STATUS_COLORS.test },