diff --git a/web/e2e/reviews-visual.spec.ts b/web/e2e/reviews-visual.spec.ts
new file mode 100644
index 0000000..9eec487
--- /dev/null
+++ b/web/e2e/reviews-visual.spec.ts
@@ -0,0 +1,97 @@
+import { expect, test, type Page, type Route } from "@playwright/test";
+
+function accessToken() {
+ const payload = Buffer.from(JSON.stringify({ exp: 4102444800 })).toString("base64url");
+ return `test.${payload}.token`;
+}
+
+async function seedAuth(page: Page) {
+ await page.addInitScript((token) => {
+ localStorage.setItem("notelett_auth_user", JSON.stringify({ id: "user-1", email: "user@example.com", name: "Review Tester" }));
+ localStorage.setItem("notelett_access_token", token);
+ localStorage.setItem("notelett_refresh_token", "refresh-token");
+ }, accessToken());
+}
+
+function json(route: Route, body: unknown, status = 200) {
+ return route.fulfill({
+ status,
+ contentType: "application/json",
+ body: JSON.stringify(body),
+ });
+}
+
+function actionDoc(id: string, afterSummary: string, state: string) {
+ return {
+ id,
+ productId: "notelett",
+ workspaceId: "ws-1",
+ noteId: "note-1",
+ actorId: "agent-1",
+ actorType: "agent",
+ actionType: "update",
+ state,
+ afterSummary,
+ beforeSummary: "Before text",
+ updatedAt: "2026-05-05T00:00:00.000Z",
+ updatedBy: "agent-1",
+ };
+}
+
+async function mockReviewApis(page: Page) {
+ await page.route("**/api/auth/refresh", (route) =>
+ json(route, { accessToken: accessToken(), refreshToken: "refresh-token" }),
+ );
+ await page.route("**/api/kill-switch**", (route) =>
+ json(route, { disabled: false, message: null }),
+ );
+ await page.route("**/api/**", (route) => {
+ const url = new URL(route.request().url());
+ const path = url.pathname.replace(/^\/api/, "");
+
+ if (path === "/note-agent-actions/pending") {
+ return json(route, {
+ items: [
+ actionDoc("act-1", "Approve release summary", "proposed"),
+ actionDoc("act-2", "Reject stale task", "proposed"),
+ ],
+ total: 2,
+ });
+ }
+ if (path === "/note-agent-actions") {
+ return json(route, {
+ items: [actionDoc("act-3", "Previous review", "approved")],
+ total: 1,
+ });
+ }
+ if (path === "/broadcasts/active" || path === "/surveys/active") {
+ return json(route, { items: [], total: 0 });
+ }
+
+ return json(route, {});
+ });
+}
+
+test.beforeEach(async ({ page }) => {
+ await seedAuth(page);
+ await mockReviewApis(page);
+});
+
+for (const viewport of [
+ { name: "desktop", width: 1440, height: 1000 },
+ { name: "mobile", width: 390, height: 900 },
+]) {
+ test(`reviews visual smoke - ${viewport.name}`, async ({ page }) => {
+ await page.setViewportSize({ width: viewport.width, height: viewport.height });
+ await page.goto("/reviews");
+ await expect(page.getByRole("heading", { name: "Agent review" })).toBeVisible();
+ await expect(page.getByRole("heading", { name: "Approval queue" })).toBeVisible();
+ await expect(page.getByRole("button", { name: /Approve release summary/ })).toBeVisible();
+
+ const overflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth);
+ expect(overflow).toBe(false);
+
+ const screenshot = await page.screenshot({ fullPage: true });
+ expect(screenshot.byteLength).toBeGreaterThan(10_000);
+ });
+}
diff --git a/web/src/app/(app)/reviews/page.tsx b/web/src/app/(app)/reviews/page.tsx
index aac6b99..21b6995 100644
--- a/web/src/app/(app)/reviews/page.tsx
+++ b/web/src/app/(app)/reviews/page.tsx
@@ -274,7 +274,7 @@ export default function ReviewsPage() {
description="Approval queue, proposal comparison, and audit-oriented review surfaces for agent-mediated edits."
actions={Operator workflow shell}
>
-
+
diff --git a/web/src/app/globals.css b/web/src/app/globals.css
index cf54e43..9013571 100644
--- a/web/src/app/globals.css
+++ b/web/src/app/globals.css
@@ -136,6 +136,16 @@ button {
gap: var(--nl-space-6);
}
+.review-workflow-grid {
+ display: grid;
+ grid-template-columns: minmax(260px, 320px) minmax(0, 1fr);
+ gap: var(--nl-space-4);
+}
+
+.review-workflow-grid > * {
+ min-width: 0;
+}
+
@media (max-width: 980px) {
.app-shell {
padding-left: 0;
@@ -151,7 +161,8 @@ button {
}
.note-detail-grid,
- .workspace-layout-grid {
+ .workspace-layout-grid,
+ .review-workflow-grid {
grid-template-columns: minmax(0, 1fr) !important;
}
}