diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index e1d5b7ac..3bcd498c 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -187,6 +187,24 @@ export function App() {
} />
} />
} />
+ {/* Legacy 1.x color tools were consolidated into adjust-colors;
+ redirect old bookmarks to the section route. */}
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
} />
} />
} />
diff --git a/tests/e2e/ai-canvas-expand.spec.ts b/tests/e2e/ai-canvas-expand.spec.ts
index 6d7f6315..fe99060b 100644
--- a/tests/e2e/ai-canvas-expand.spec.ts
+++ b/tests/e2e/ai-canvas-expand.spec.ts
@@ -16,7 +16,7 @@ async function uploadFile(page: import("@playwright/test").Page, filePath: strin
test.describe("AI Canvas Expand", () => {
test("standalone /ai-canvas-expand route loads", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await expect(page.getByText("Tool not found")).not.toBeVisible();
await expect(page.getByText("AI Canvas Expand")).toBeVisible();
@@ -25,21 +25,21 @@ test.describe("AI Canvas Expand", () => {
});
test("standalone submit disabled without file", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await expect(page.getByTestId("ai-canvas-expand-submit")).toBeDisabled();
});
test("standalone submit disabled when all extensions are zero", async ({
loggedInPage: page,
}) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await uploadFile(page, fixturePath("test-200x150.png"));
await expect(page.getByTestId("ai-canvas-expand-submit")).toBeDisabled();
});
test("standalone submit enables with extension set", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await uploadFile(page, fixturePath("test-200x150.png"));
await page.locator("#cac-top").fill("50");
@@ -48,7 +48,7 @@ test.describe("AI Canvas Expand", () => {
});
test("standalone aspect ratio preset fills extension values", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await uploadFile(page, fixturePath("test-200x150.png"));
await page.waitForTimeout(500);
@@ -65,7 +65,7 @@ test.describe("AI Canvas Expand", () => {
});
test("standalone shows new size display", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await uploadFile(page, fixturePath("test-200x150.png"));
await page.waitForTimeout(500);
@@ -76,7 +76,7 @@ test.describe("AI Canvas Expand", () => {
});
test("tier buttons render with Balanced selected by default", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await expect(page.getByTestId("tier-fast")).toBeVisible();
await expect(page.getByTestId("tier-balanced")).toBeVisible();
@@ -87,7 +87,7 @@ test.describe("AI Canvas Expand", () => {
});
test("clicking tier button changes selection", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await page.getByTestId("tier-fast").click();
await expect(page.getByTestId("tier-fast")).toHaveClass(/bg-primary/);
@@ -97,7 +97,7 @@ test.describe("AI Canvas Expand", () => {
});
test("tier description updates on selection", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
await expect(page.getByText("Good quality, moderate speed")).toBeVisible();
@@ -108,7 +108,7 @@ test.describe("AI Canvas Expand", () => {
// ── Processing (requires AI sidecar) ────────────────────────────────
test("processes image (AI sidecar required)", async ({ loggedInPage: page }) => {
- await page.goto("/ai-canvas-expand");
+ await page.goto("/image/ai-canvas-expand");
if (!(await isAiSidecarRunning(page))) {
test.skip(true, "AI sidecar not running");
diff --git a/tests/e2e/batch-preview.spec.ts b/tests/e2e/batch-preview.spec.ts
index dc36344f..566465a6 100644
--- a/tests/e2e/batch-preview.spec.ts
+++ b/tests/e2e/batch-preview.spec.ts
@@ -110,7 +110,7 @@ test.describe("Favicon download button", () => {
test("favicon shows download button instead of auto-downloading", async ({
loggedInPage: page,
}) => {
- await page.goto("/favicon");
+ await page.goto("/image/favicon");
// Upload a test image
const fileChooserPromise = page.waitForEvent("filechooser");
diff --git a/tests/e2e/beautify.spec.ts b/tests/e2e/beautify.spec.ts
index 07b128d8..0d7ab3cc 100644
--- a/tests/e2e/beautify.spec.ts
+++ b/tests/e2e/beautify.spec.ts
@@ -6,12 +6,12 @@ import { expect, test, uploadTestImage } from "./helpers";
test.describe("Beautify Screenshot", () => {
test("renders tool page with settings", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await expect(page.getByText("Beautify Screenshot").first()).toBeVisible();
});
test("uploads image and shows preview", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -20,7 +20,7 @@ test.describe("Beautify Screenshot", () => {
});
test("shows preset cards", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
// Quick Presets section is open by default
await expect(page.getByText("Quick Presets").first()).toBeVisible();
@@ -30,7 +30,7 @@ test.describe("Beautify Screenshot", () => {
});
test("shows background, frame, spacing, and shadow sections", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await expect(page.getByText("Background").first()).toBeVisible();
await expect(page.getByText("Device Frame").first()).toBeVisible();
@@ -39,14 +39,14 @@ test.describe("Beautify Screenshot", () => {
});
test("submit button uses data-testid", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await expect(page.getByTestId("beautify-submit")).toBeVisible();
});
test("processes image with default settings", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1500);
@@ -64,7 +64,7 @@ test.describe("Beautify Screenshot", () => {
});
test("applies preset and processes", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1500);
@@ -97,7 +97,7 @@ test.describe("Beautify Screenshot", () => {
// ---------------------------------------------------------------------------
test("live preview shows gradient background on wrapper", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -109,7 +109,7 @@ test.describe("Beautify Screenshot", () => {
});
test("live preview renders macOS frame chrome", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -118,7 +118,7 @@ test.describe("Beautify Screenshot", () => {
});
test("switching to Windows frame shows Windows title bar", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -130,7 +130,7 @@ test.describe("Beautify Screenshot", () => {
});
test("switching to Browser frame shows tab bar and URL bar", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -141,7 +141,7 @@ test.describe("Beautify Screenshot", () => {
});
test("switching to None frame removes frame preview", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -159,7 +159,7 @@ test.describe("Beautify Screenshot", () => {
});
test("device frame shows label indicator", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -170,7 +170,7 @@ test.describe("Beautify Screenshot", () => {
});
test("custom shadow values reflect in preview", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -189,7 +189,7 @@ test.describe("Beautify Screenshot", () => {
});
test("watermark text appears in preview overlay", async ({ loggedInPage: page }) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -212,7 +212,7 @@ test.describe("Beautify Screenshot", () => {
test("image stays visible for every preset (no zero-height collapse)", async ({
loggedInPage: page,
}) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -241,7 +241,7 @@ test.describe("Beautify Screenshot", () => {
test("image stays visible when watermark is added without a frame", async ({
loggedInPage: page,
}) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
@@ -270,7 +270,7 @@ test.describe("Beautify Screenshot", () => {
test("image stays visible when watermark is added with a frame", async ({
loggedInPage: page,
}) => {
- await page.goto("/beautify");
+ await page.goto("/image/beautify");
await uploadTestImage(page);
await page.waitForTimeout(1000);
diff --git a/tests/e2e/blur-faces.spec.ts b/tests/e2e/blur-faces.spec.ts
index 8e93afcf..f1c04653 100644
--- a/tests/e2e/blur-faces.spec.ts
+++ b/tests/e2e/blur-faces.spec.ts
@@ -16,7 +16,7 @@ async function uploadFile(page: import("@playwright/test").Page, filePath: strin
test.describe("Blur Faces tool", () => {
async function skipIfFeatureNotInstalled(page: import("@playwright/test").Page) {
- await page.goto("/blur-faces");
+ await page.goto("/image/blur-faces");
try {
await page.getByTestId("blur-faces-submit").waitFor({ state: "visible", timeout: 15_000 });
} catch {
diff --git a/tests/e2e/color-blindness.spec.ts b/tests/e2e/color-blindness.spec.ts
index dbba91b8..d51cf600 100644
--- a/tests/e2e/color-blindness.spec.ts
+++ b/tests/e2e/color-blindness.spec.ts
@@ -4,7 +4,7 @@ import { expect, test, uploadTestImage, waitForProcessing } from "./helpers";
test.describe("Color Blindness Simulation tool", () => {
test("upload -> simulate -> download cycle", async ({ loggedInPage: page }) => {
- await page.goto("/color-blindness");
+ await page.goto("/image/color-blindness");
await expect(page.getByText("Color Blindness Simulation").first()).toBeVisible();
await uploadTestImage(page);
@@ -39,7 +39,7 @@ test.describe("Color Blindness Simulation tool", () => {
});
test("shows type description when selection changes", async ({ loggedInPage: page }) => {
- await page.goto("/color-blindness");
+ await page.goto("/image/color-blindness");
await uploadTestImage(page);
const dropdown = page.locator("#cb-simulation-type");
diff --git a/tests/e2e/colorize.spec.ts b/tests/e2e/colorize.spec.ts
index 756f1fce..09ba223e 100644
--- a/tests/e2e/colorize.spec.ts
+++ b/tests/e2e/colorize.spec.ts
@@ -21,7 +21,7 @@ async function colorizeAndWait(page: import("@playwright/test").Page) {
test.describe("Colorize tool", () => {
async function skipIfFeatureNotInstalled(page: import("@playwright/test").Page) {
- await page.goto("/colorize");
+ await page.goto("/image/colorize");
try {
await page.getByTestId("colorize-submit").waitFor({ state: "visible", timeout: 15_000 });
} catch {
diff --git a/tests/e2e/content-aware-resize.spec.ts b/tests/e2e/content-aware-resize.spec.ts
index 205336bb..2dfbd895 100644
--- a/tests/e2e/content-aware-resize.spec.ts
+++ b/tests/e2e/content-aware-resize.spec.ts
@@ -36,7 +36,7 @@ test.describe("Content-Aware Resize", () => {
test("direct /content-aware-resize route loads tool page (regression #131)", async ({
loggedInPage: page,
}) => {
- await page.goto("/content-aware-resize");
+ await page.goto("/image/content-aware-resize");
// Must NOT show "Tool not found"
await expect(page.getByText("Tool not found")).not.toBeVisible();
@@ -50,12 +50,12 @@ test.describe("Content-Aware Resize", () => {
});
test("direct route submit disabled without file", async ({ loggedInPage: page }) => {
- await page.goto("/content-aware-resize");
+ await page.goto("/image/content-aware-resize");
await expect(page.getByTestId("content-aware-resize-submit")).toBeDisabled();
});
test("direct route submit enables with width and file", async ({ loggedInPage: page }) => {
- await page.goto("/content-aware-resize");
+ await page.goto("/image/content-aware-resize");
await uploadFile(page, fixturePath("test-200x150.png"));
const widthInput = page.locator("input[placeholder='Auto']").first();
@@ -65,7 +65,7 @@ test.describe("Content-Aware Resize", () => {
});
test("content-aware toggle reveals seam carving controls", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await expect(page.getByText("Content-aware")).toBeVisible();
@@ -85,7 +85,7 @@ test.describe("Content-Aware Resize", () => {
test("standard resize tabs hidden when content-aware is active", async ({
loggedInPage: page,
}) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
// Standard tabs visible before toggle
await expect(page.getByRole("button", { name: "Custom" })).toBeVisible();
@@ -97,14 +97,14 @@ test.describe("Content-Aware Resize", () => {
});
test("submit disabled without file", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
await expect(page.getByTestId("resize-submit")).toBeDisabled();
});
test("submit disabled without dimensions or square mode", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
await uploadFile(page, fixturePath("test-200x150.png"));
@@ -118,7 +118,7 @@ test.describe("Content-Aware Resize", () => {
});
test("submit enables with width specified", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
await uploadFile(page, fixturePath("test-200x150.png"));
@@ -129,7 +129,7 @@ test.describe("Content-Aware Resize", () => {
});
test("submit enables with square mode checked", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
await uploadFile(page, fixturePath("test-200x150.png"));
@@ -139,7 +139,7 @@ test.describe("Content-Aware Resize", () => {
});
test("square mode disables width and height inputs", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
await page.getByText("Resize to square").click();
@@ -152,7 +152,7 @@ test.describe("Content-Aware Resize", () => {
});
test("smoothing slider has correct range", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
const slider = page.locator("#blur-radius");
@@ -161,7 +161,7 @@ test.describe("Content-Aware Resize", () => {
});
test("edge sensitivity slider has correct range", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
const slider = page.locator("#sobel-threshold");
@@ -170,7 +170,7 @@ test.describe("Content-Aware Resize", () => {
});
test("PNG - content-aware resize processes and shows result", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
await uploadFile(page, fixturePath("test-200x150.png"));
@@ -187,7 +187,7 @@ test.describe("Content-Aware Resize", () => {
});
test("HEIC input with content-aware resize", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await enableContentAware(page);
await uploadFile(page, fixturePath("test-200x150.heic"));
diff --git a/tests/e2e/document-mode.spec.ts b/tests/e2e/document-mode.spec.ts
index e902983b..222a0722 100644
--- a/tests/e2e/document-mode.spec.ts
+++ b/tests/e2e/document-mode.spec.ts
@@ -14,7 +14,7 @@ test.describe("Document display mode (rotate-pdf)", () => {
test("uploads a PDF, rotates it, and shows the document canvas with processed result", async ({
loggedInPage: page,
}) => {
- await page.goto("/rotate-pdf");
+ await page.goto("/pdf/rotate-pdf");
// Upload test-3page.pdf via file chooser
const fileChooserPromise = page.waitForEvent("filechooser");
@@ -48,7 +48,7 @@ test.describe("Document display mode (rotate-pdf)", () => {
test("document viewer shows page navigation for multi-page PDF", async ({
loggedInPage: page,
}) => {
- await page.goto("/rotate-pdf");
+ await page.goto("/pdf/rotate-pdf");
// Upload test-3page.pdf
const fileChooserPromise = page.waitForEvent("filechooser");
diff --git a/tests/e2e/enhance-faces.spec.ts b/tests/e2e/enhance-faces.spec.ts
index df0c2f06..3db96bf5 100644
--- a/tests/e2e/enhance-faces.spec.ts
+++ b/tests/e2e/enhance-faces.spec.ts
@@ -21,7 +21,7 @@ async function enhanceFacesAndWait(page: import("@playwright/test").Page) {
test.describe("Enhance Faces tool", () => {
async function skipIfFeatureNotInstalled(page: import("@playwright/test").Page) {
- await page.goto("/enhance-faces");
+ await page.goto("/image/enhance-faces");
try {
await page.getByTestId("enhance-faces-submit").waitFor({ state: "visible", timeout: 15_000 });
} catch {
diff --git a/tests/e2e/gui-accessibility.spec.ts b/tests/e2e/gui-accessibility.spec.ts
index 5b6b74b5..60d9f615 100644
--- a/tests/e2e/gui-accessibility.spec.ts
+++ b/tests/e2e/gui-accessibility.spec.ts
@@ -13,7 +13,7 @@ test.describe("Semantic HTML - Landmarks", () => {
});
test("tool page has exactly one main landmark", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await expect(page.locator("main")).toHaveCount(1);
});
@@ -22,7 +22,7 @@ test.describe("Semantic HTML - Landmarks", () => {
});
test("tool page has a sidebar landmark (aside)", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await expect(page.locator("aside")).toBeVisible();
});
});
@@ -57,7 +57,7 @@ test.describe("Semantic HTML - Buttons", () => {
});
test("all visible buttons on tool page have accessible names", async ({ loggedInPage: page }) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await page.waitForLoadState("networkidle");
const buttons = page.getByRole("button");
@@ -111,7 +111,7 @@ test.describe("Semantic HTML - Form Inputs", () => {
test.describe("Semantic HTML - Form Inputs on Tool Pages", () => {
test("QR generate form inputs have associated labels", async ({ loggedInPage: page }) => {
- await page.goto("/qr-generate");
+ await page.goto("/image/qr-generate");
await page.waitForLoadState("domcontentloaded");
// The URL input should be findable by its label
@@ -160,7 +160,7 @@ test.describe("Heading Hierarchy - Tool Pages", () => {
test("tool page has headings including an h2 for the tool name", async ({
loggedInPage: page,
}) => {
- await page.goto("/resize");
+ await page.goto("/image/resize");
await page.waitForLoadState("domcontentloaded");
// The tool page should have an h2 heading for the tool name
@@ -309,7 +309,7 @@ test.describe("Dropzone Accessibility", () => {
loggedInPage: page,
}) => {
// Navigate to a tool page to ensure the dropzone is present
- await page.goto("/resize");
+ await page.goto("/image/resize");
await page.waitForLoadState("domcontentloaded");
// The upload button is a real