diff --git a/web/src/app/pattern-lab/panels/LayersPanel.test.tsx b/web/src/app/pattern-lab/panels/LayersPanel.test.tsx
new file mode 100644
index 00000000..ab8c98e5
--- /dev/null
+++ b/web/src/app/pattern-lab/panels/LayersPanel.test.tsx
@@ -0,0 +1,81 @@
+// Del in the layer stack. There is no undo for it, so a layer with work in it
+// asks first; a blank one goes straight away.
+
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { fireEvent, render, screen } from "@testing-library/react";
+import LayersPanel from "./LayersPanel";
+import { useLabStore } from "@/lib/lab/store";
+
+const store = () => useLabStore.getState();
+const names = () => store().layers.map((layer) => layer.name);
+const del = () => screen.getByRole("button", { name: "Del" });
+
+function drawOnePixel() {
+ const layer = store().layers.find((entry) => entry.id === store().activeLayerId);
+ if (!layer || layer.type !== "pixel") throw new Error("active layer is not pixel");
+ const data = new Uint8ClampedArray(layer.data);
+ data.set([255, 77, 0, 255], 0);
+ store().replacePixelData(layer.id, data);
+}
+
+beforeEach(() => {
+ store().discardProject();
+});
+
+describe("LayersPanel Del", () => {
+ it("is disabled while the stack has one layer", () => {
+ render();
+ expect(del()).toBeDisabled();
+ });
+
+ it("deletes a pixel layer nobody has drawn on without asking", () => {
+ store().addPixelLayer();
+ const confirm = vi.spyOn(window, "confirm");
+ render();
+ fireEvent.click(del());
+ expect(confirm).not.toHaveBeenCalled();
+ expect(names()).toEqual(["Code 1"]);
+ });
+
+ it("deletes a code layer still holding the starter pattern without asking", () => {
+ store().addCodeLayer();
+ const confirm = vi.spyOn(window, "confirm");
+ render();
+ fireEvent.click(del());
+ expect(confirm).not.toHaveBeenCalled();
+ expect(names()).toEqual(["Code 1"]);
+ });
+
+ it("asks before deleting a drawn pixel layer, and Cancel keeps it", () => {
+ store().addPixelLayer();
+ drawOnePixel();
+ const confirm = vi.spyOn(window, "confirm").mockReturnValue(false);
+ render();
+ fireEvent.click(del());
+ expect(confirm).toHaveBeenCalledTimes(1);
+ expect(confirm.mock.calls[0][0]).toContain('"Pixel 1"');
+ expect(names()).toEqual(["Pixel 1", "Code 1"]);
+ const kept = store().layers[0];
+ expect(kept.type === "pixel" ? kept.data[3] : -1).toBe(255);
+ });
+
+ it("deletes the drawn pixel layer once confirmed", () => {
+ store().addPixelLayer();
+ drawOnePixel();
+ vi.spyOn(window, "confirm").mockReturnValue(true);
+ render();
+ fireEvent.click(del());
+ expect(names()).toEqual(["Code 1"]);
+ expect(store().activeLayerId).toBe(store().layers[0].id);
+ });
+
+ it("asks before deleting a code layer whose code was changed", () => {
+ store().addPixelLayer();
+ store().selectLayer(store().layers[1].id);
+ const confirm = vi.spyOn(window, "confirm").mockReturnValue(false);
+ render();
+ fireEvent.click(del());
+ expect(confirm.mock.calls[0][0]).toContain('"Code 1"');
+ expect(names()).toEqual(["Pixel 1", "Code 1"]);
+ });
+});
diff --git a/web/src/app/pattern-lab/panels/LayersPanel.tsx b/web/src/app/pattern-lab/panels/LayersPanel.tsx
index 15d51895..c091f416 100644
--- a/web/src/app/pattern-lab/panels/LayersPanel.tsx
+++ b/web/src/app/pattern-lab/panels/LayersPanel.tsx
@@ -5,11 +5,22 @@
// The active layer's opacity + blend live at the bottom of the panel.
import { useState } from "react";
-import { BLEND_MODES } from "@/lib/lab/types";
+import { BLEND_MODES, type Layer } from "@/lib/lab/types";
import { revealEditor } from "@/lib/lab/editorReveal";
import { useLabStore } from "@/lib/lab/store";
+import { STARTER_CODE } from "@/lib/lab/store/shared";
import dock from "../LabPanels.module.css";
+// A pixel layer nobody has drawn on, or a code layer still holding the
+// starter pattern, has nothing to lose, so Del takes it without asking.
+function holdsWork(layer: Layer): boolean {
+ if (layer.type === "code") return layer.code !== STARTER_CODE;
+ for (let i = 3; i < layer.data.length; i += 4) {
+ if (layer.data[i] !== 0) return true;
+ }
+ return false;
+}
+
export default function LayersPanel() {
const layers = useLabStore((state) => state.layers);
const activeLayerId = useLabStore((state) => state.activeLayerId);
@@ -77,7 +88,18 @@ export default function LayersPanel() {