From cbab5b580376526643e587ce8c7f2e53138c1080 Mon Sep 17 00:00:00 2001 From: fanghaonan Date: Thu, 18 Dec 2025 21:37:12 +0800 Subject: [PATCH 1/2] fix: harden external links and stabilize lint - Add rel="noopener noreferrer" to links opened in new tabs. - Use noopener/noreferrer features for window.open. - Run ultracite via pnpm exec and address lint/format findings. --- components/ai-elements/open-in-chat.tsx | 12 ++++----- components/ai-elements/sources.tsx | 2 +- components/chat-header.tsx | 4 +-- components/chat.tsx | 3 ++- components/elements/source.tsx | 2 +- components/multimodal-input.tsx | 1 - package.json | 4 +-- tests/e2e/model-selector.test.ts | 34 ++++++++++++++++++++----- 8 files changed, 41 insertions(+), 21 deletions(-) diff --git a/components/ai-elements/open-in-chat.tsx b/components/ai-elements/open-in-chat.tsx index dffc61c5ee..9baf0ba3e2 100644 --- a/components/ai-elements/open-in-chat.tsx +++ b/components/ai-elements/open-in-chat.tsx @@ -253,7 +253,7 @@ export const OpenInChatGPT = (props: OpenInChatGPTProps) => { {providers.chatgpt.icon} @@ -273,7 +273,7 @@ export const OpenInClaude = (props: OpenInClaudeProps) => { {providers.claude.icon} @@ -293,7 +293,7 @@ export const OpenInT3 = (props: OpenInT3Props) => { {providers.t3.icon} @@ -313,7 +313,7 @@ export const OpenInScira = (props: OpenInSciraProps) => { {providers.scira.icon} @@ -333,7 +333,7 @@ export const OpenInv0 = (props: OpenInv0Props) => { {providers.v0.icon} @@ -353,7 +353,7 @@ export const OpenInCursor = (props: OpenInCursorProps) => { {providers.cursor.icon} diff --git a/components/ai-elements/sources.tsx b/components/ai-elements/sources.tsx index 2405c3dfdd..e6a979b9c9 100644 --- a/components/ai-elements/sources.tsx +++ b/components/ai-elements/sources.tsx @@ -63,7 +63,7 @@ export const Source = ({ href, title, children, ...props }: SourceProps) => ( diff --git a/components/chat-header.tsx b/components/chat-header.tsx index a330805b5e..6b29e7a9ee 100644 --- a/components/chat-header.tsx +++ b/components/chat-header.tsx @@ -56,8 +56,8 @@ function PureChatHeader({ > Deploy with Vercel diff --git a/components/chat.tsx b/components/chat.tsx index 4dd30a4703..0c3182a621 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -239,7 +239,8 @@ export function Chat({ onClick={() => { window.open( "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dadd-credit-card", - "_blank" + "_blank", + "noopener,noreferrer" ); window.location.href = "/"; }} diff --git a/components/elements/source.tsx b/components/elements/source.tsx index 68d71a30c6..34127ad5a6 100644 --- a/components/elements/source.tsx +++ b/components/elements/source.tsx @@ -60,7 +60,7 @@ export const Source = ({ href, title, children, ...props }: SourceProps) => ( diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index 7a93b3b40f..3848c1c359 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -11,7 +11,6 @@ import { type SetStateAction, useCallback, useEffect, - useMemo, useRef, useState, } from "react"; diff --git a/package.json b/package.json index 3c77d814b2..4e49e72c61 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "dev": "next dev --turbo", "build": "tsx lib/db/migrate && next build", "start": "next start", - "lint": "npx ultracite@latest check", - "format": "npx ultracite@latest fix", + "lint": "pnpm exec ultracite check", + "format": "pnpm exec ultracite fix", "db:generate": "drizzle-kit generate", "db:migrate": "npx tsx lib/db/migrate.ts", "db:studio": "drizzle-kit studio", diff --git a/tests/e2e/model-selector.test.ts b/tests/e2e/model-selector.test.ts index 4943796b39..548aba93ca 100644 --- a/tests/e2e/model-selector.test.ts +++ b/tests/e2e/model-selector.test.ts @@ -9,12 +9,18 @@ test.describe("Model Selector", () => { test("displays a model button", async ({ page }) => { // Look for any button with model-related content - const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); + const modelButton = page + .locator("button") + .filter({ hasText: MODEL_BUTTON_REGEX }) + .first(); await expect(modelButton).toBeVisible(); }); test("opens model selector popover on click", async ({ page }) => { - const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); + const modelButton = page + .locator("button") + .filter({ hasText: MODEL_BUTTON_REGEX }) + .first(); await modelButton.click(); // Search input should be visible in the popover @@ -22,7 +28,10 @@ test.describe("Model Selector", () => { }); test("can search for models", async ({ page }) => { - const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); + const modelButton = page + .locator("button") + .filter({ hasText: MODEL_BUTTON_REGEX }) + .first(); await modelButton.click(); const searchInput = page.getByPlaceholder("Search models..."); @@ -33,7 +42,10 @@ test.describe("Model Selector", () => { }); test("can close model selector by clicking outside", async ({ page }) => { - const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); + const modelButton = page + .locator("button") + .filter({ hasText: MODEL_BUTTON_REGEX }) + .first(); await modelButton.click(); await expect(page.getByPlaceholder("Search models...")).toBeVisible(); @@ -45,7 +57,10 @@ test.describe("Model Selector", () => { }); test("shows model provider groups", async ({ page }) => { - const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); + const modelButton = page + .locator("button") + .filter({ hasText: MODEL_BUTTON_REGEX }) + .first(); await modelButton.click(); // Should show provider group headers @@ -54,7 +69,10 @@ test.describe("Model Selector", () => { }); test("can select a different model", async ({ page }) => { - const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); + const modelButton = page + .locator("button") + .filter({ hasText: MODEL_BUTTON_REGEX }) + .first(); await modelButton.click(); // Select a specific model @@ -64,6 +82,8 @@ test.describe("Model Selector", () => { await expect(page.getByPlaceholder("Search models...")).not.toBeVisible(); // Model button should now show the selected model - await expect(page.locator("button").filter({ hasText: "Claude Haiku" }).first()).toBeVisible(); + await expect( + page.locator("button").filter({ hasText: "Claude Haiku" }).first() + ).toBeVisible(); }); }); From aa7834599b3de9b2bec6116ec415cd7472ca8789 Mon Sep 17 00:00:00 2001 From: fanghaonan Date: Thu, 18 Dec 2025 21:43:26 +0800 Subject: [PATCH 2/2] chore: revert lint script adjustments Restore upstream lint/format scripts and revert incidental lint-driven edits. --- components/multimodal-input.tsx | 1 + package.json | 4 ++-- tests/e2e/model-selector.test.ts | 34 +++++++------------------------- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index 3848c1c359..7a93b3b40f 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -11,6 +11,7 @@ import { type SetStateAction, useCallback, useEffect, + useMemo, useRef, useState, } from "react"; diff --git a/package.json b/package.json index 4e49e72c61..3c77d814b2 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "dev": "next dev --turbo", "build": "tsx lib/db/migrate && next build", "start": "next start", - "lint": "pnpm exec ultracite check", - "format": "pnpm exec ultracite fix", + "lint": "npx ultracite@latest check", + "format": "npx ultracite@latest fix", "db:generate": "drizzle-kit generate", "db:migrate": "npx tsx lib/db/migrate.ts", "db:studio": "drizzle-kit studio", diff --git a/tests/e2e/model-selector.test.ts b/tests/e2e/model-selector.test.ts index 548aba93ca..4943796b39 100644 --- a/tests/e2e/model-selector.test.ts +++ b/tests/e2e/model-selector.test.ts @@ -9,18 +9,12 @@ test.describe("Model Selector", () => { test("displays a model button", async ({ page }) => { // Look for any button with model-related content - const modelButton = page - .locator("button") - .filter({ hasText: MODEL_BUTTON_REGEX }) - .first(); + const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); await expect(modelButton).toBeVisible(); }); test("opens model selector popover on click", async ({ page }) => { - const modelButton = page - .locator("button") - .filter({ hasText: MODEL_BUTTON_REGEX }) - .first(); + const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); await modelButton.click(); // Search input should be visible in the popover @@ -28,10 +22,7 @@ test.describe("Model Selector", () => { }); test("can search for models", async ({ page }) => { - const modelButton = page - .locator("button") - .filter({ hasText: MODEL_BUTTON_REGEX }) - .first(); + const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); await modelButton.click(); const searchInput = page.getByPlaceholder("Search models..."); @@ -42,10 +33,7 @@ test.describe("Model Selector", () => { }); test("can close model selector by clicking outside", async ({ page }) => { - const modelButton = page - .locator("button") - .filter({ hasText: MODEL_BUTTON_REGEX }) - .first(); + const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); await modelButton.click(); await expect(page.getByPlaceholder("Search models...")).toBeVisible(); @@ -57,10 +45,7 @@ test.describe("Model Selector", () => { }); test("shows model provider groups", async ({ page }) => { - const modelButton = page - .locator("button") - .filter({ hasText: MODEL_BUTTON_REGEX }) - .first(); + const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); await modelButton.click(); // Should show provider group headers @@ -69,10 +54,7 @@ test.describe("Model Selector", () => { }); test("can select a different model", async ({ page }) => { - const modelButton = page - .locator("button") - .filter({ hasText: MODEL_BUTTON_REGEX }) - .first(); + const modelButton = page.locator("button").filter({ hasText: MODEL_BUTTON_REGEX }).first(); await modelButton.click(); // Select a specific model @@ -82,8 +64,6 @@ test.describe("Model Selector", () => { await expect(page.getByPlaceholder("Search models...")).not.toBeVisible(); // Model button should now show the selected model - await expect( - page.locator("button").filter({ hasText: "Claude Haiku" }).first() - ).toBeVisible(); + await expect(page.locator("button").filter({ hasText: "Claude Haiku" }).first()).toBeVisible(); }); });