From 62bc55b97b64f4a1af3113374c968bee2d720020 Mon Sep 17 00:00:00 2001 From: tirth707 Date: Thu, 12 Mar 2026 20:14:23 +0530 Subject: [PATCH 1/5] fix(ui): add visual error toast notification for corrupted share links Signed-off-by: tirth707 --- src/App.tsx | 57 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 15839668..bcc30cf0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,9 @@ import { useEffect, useState } from "react"; -import { App as AntdApp, Layout, Spin } from "antd"; +import { App as AntdApp, Layout, Spin, message } from "antd"; import { LoadingOutlined } from "@ant-design/icons"; import { Routes, Route, useSearchParams, useNavigate } from "react-router-dom"; +import { shallow } from "zustand/shallow"; +import { useStoreWithEqualityFn } from "zustand/traditional"; import Navbar from "./components/Navbar"; import tour from "./components/Tour"; import LearnNow from "./pages/LearnNow"; @@ -18,41 +20,47 @@ const App = () => { const navigate = useNavigate(); const init = useAppStore((state) => state.init); const loadFromLink = useAppStore((state) => state.loadFromLink); - const { isAIConfigOpen, setAIConfigOpen } = - useAppStore((state) => ({ + const globalError = useAppStore((state) => state.error); + + const { isAIConfigOpen, setAIConfigOpen } = useStoreWithEqualityFn( + useAppStore, + (state) => ({ isAIConfigOpen: state.isAIConfigOpen, setAIConfigOpen: state.setAIConfigOpen, - })); + }), + shallow + ); + const backgroundColor = useAppStore((state) => state.backgroundColor); const textColor = useAppStore((state) => state.textColor); const [loading, setLoading] = useState(true); const [searchParams] = useSearchParams(); + useEffect(() => { + if (globalError && globalError.includes("Failed to decompress")) { + message.error("Failed to load shared workspace. The link data may be corrupted."); + } + }, [globalError]); useEffect(() => { const initializeApp = async () => { - try { - setLoading(true); - // Prioritize hash for new links, fallback to searchParams for old links - let compressedData: string | null = null; - if (window.location.hash.startsWith("#data=")) { - compressedData = window.location.hash.substring(6); - } else { - compressedData = searchParams.get("data"); - } - if (compressedData) { - await loadFromLink(compressedData); - if (window.location.pathname !== "/") { - navigate("/", { replace: true }); - } - } else { - await init(); + setLoading(true); + let compressedData: string | null = null; + if (window.location.hash.startsWith("#data=")) { + compressedData = window.location.hash.substring(6); + } else { + compressedData = searchParams.get("data"); + } + + if (compressedData) { + await loadFromLink(compressedData); + if (window.location.pathname !== "/") { + navigate("/", { replace: true }); } - } catch (error) { - console.error("Initialization error:", error); - } finally { - setLoading(false); + } else { + await init(); } + setLoading(false); }; void initializeApp(); }, [init, loadFromLink, searchParams, navigate]); @@ -93,7 +101,6 @@ const App = () => { } }, [searchParams]); - // Set data-theme attribute on initial load and when theme changes useEffect(() => { const theme = backgroundColor === "#121212" ? "dark" : "light"; document.documentElement.setAttribute("data-theme", theme); From 41c61397ef7ff61df8ae2550160e8dfa9ea1578d Mon Sep 17 00:00:00 2001 From: tirth707 Date: Sat, 14 Mar 2026 20:27:18 +0530 Subject: [PATCH 2/5] fix(ui): broaden error matcher, add init fallback, and add e2e test for corrupted links Signed-off-by: tirth707 --- e2e/share.spec.ts | 28 +++++++++++++--------------- src/App.tsx | 5 +++-- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/e2e/share.spec.ts b/e2e/share.spec.ts index 55fe344c..8f5600aa 100644 --- a/e2e/share.spec.ts +++ b/e2e/share.spec.ts @@ -12,38 +12,30 @@ test.describe('Share Functionality', () => { }); test('should copy shareable link to clipboard on Share click', async ({ page, context }) => { - // Grant clipboard permissions await context.grantPermissions(['clipboard-read', 'clipboard-write']); const shareButton = page.getByRole('button', { name: 'Share' }); await shareButton.click(); - // Should show success message await expect(page.getByText('Link copied to clipboard')).toBeVisible({ timeout: 5000 }); - // Verify clipboard contains the link with data parameter const clipboardText = await page.evaluate(() => navigator.clipboard.readText()); expect(clipboardText).toContain('data='); }); test('should load template from shared URL', async ({ page }) => { - // First, get a shareable link by clicking share await page.context().grantPermissions(['clipboard-read', 'clipboard-write']); const shareButton = page.getByRole('button', { name: 'Share' }); await shareButton.click(); - // Wait for clipboard to be populated await expect(page.getByText('Link copied to clipboard')).toBeVisible({ timeout: 5000 }); - // Get the shareable link from clipboard const shareableLink = await page.evaluate(() => navigator.clipboard.readText()); - // Validate that we got a non-empty string expect(shareableLink, 'Shareable link should be a non-empty string').toBeTruthy(); expect(typeof shareableLink, 'Shareable link should be a string').toBe('string'); - // Parse URL with validation let url: URL; try { url = new URL(shareableLink); @@ -51,22 +43,17 @@ test.describe('Share Functionality', () => { throw new Error(`Failed to parse shareable link as URL: "${shareableLink}". Error: ${error}`); } - // The app uses hash-based routing (#data=...) not query params (?data=...) - // Extract data from hash fragment - const hashParams = new URLSearchParams(url.hash.slice(1)); // Remove leading # + const hashParams = new URLSearchParams(url.hash.slice(1)); const dataParam = hashParams.get('data'); expect( dataParam, `URL should contain a "data" parameter in hash. Received URL: "${shareableLink}"` ).toBeTruthy(); - // Navigate to the shareable link using the hash await page.goto(`/#data=${dataParam}`); - // Wait for app to load await expect(page.locator('.app-spinner-container')).toBeHidden({ timeout: 30000 }); - // Verify the app loaded successfully with content await expect(page.locator('.main-container-agreement')).toBeVisible(); }); @@ -79,4 +66,15 @@ test.describe('Share Functionality', () => { const settingsButton = page.getByRole('button', { name: 'Settings' }); await expect(settingsButton).toBeVisible(); }); -}); + + test('should display error toast and fallback to default state on corrupted share link', async ({ page }) => { + await page.goto('/#data=invalid_garbage_base64_string'); + + await expect(page.locator('.app-spinner-container')).toBeHidden({ timeout: 10000 }); + + const errorMessage = page.locator('.ant-message-notice-content', { hasText: 'Failed to load shared workspace. The link data may be corrupted.' }); + await expect(errorMessage).toBeVisible(); + + await expect(page.frameLocator('iframe[title="PDF Preview"]').locator('text="Acme Corp"')).toBeVisible({ timeout: 15000 }); + }); +}); \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index bcc30cf0..ed6815d7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,10 +37,11 @@ const App = () => { const [searchParams] = useSearchParams(); useEffect(() => { - if (globalError && globalError.includes("Failed to decompress")) { + if (globalError && globalError.includes("Failed to load shared content:")) { message.error("Failed to load shared workspace. The link data may be corrupted."); + void init(); } - }, [globalError]); + }, [globalError, init]); useEffect(() => { const initializeApp = async () => { From b4eb227542c2e6864602cfa8ae3a018925a623c0 Mon Sep 17 00:00:00 2001 From: tirth707 Date: Sun, 15 Mar 2026 21:20:05 +0530 Subject: [PATCH 3/5] fix(test): correct preview selector and add void to antd message promise Signed-off-by: tirth707 --- e2e/share.spec.ts | 2 +- src/App.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/share.spec.ts b/e2e/share.spec.ts index 8f5600aa..51ee0de1 100644 --- a/e2e/share.spec.ts +++ b/e2e/share.spec.ts @@ -75,6 +75,6 @@ test.describe('Share Functionality', () => { const errorMessage = page.locator('.ant-message-notice-content', { hasText: 'Failed to load shared workspace. The link data may be corrupted.' }); await expect(errorMessage).toBeVisible(); - await expect(page.frameLocator('iframe[title="PDF Preview"]').locator('text="Acme Corp"')).toBeVisible({ timeout: 15000 }); + await expect(page.locator('.agreement')).toContainText('Acme Corp', { timeout: 15000 }); }); }); \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index ed6815d7..de1b6a26 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -38,7 +38,7 @@ const App = () => { useEffect(() => { if (globalError && globalError.includes("Failed to load shared content:")) { - message.error("Failed to load shared workspace. The link data may be corrupted."); + void message.error("Failed to load shared workspace. The link data may be corrupted."); void init(); } }, [globalError, init]); From 02047a2fa82c9b39b91386ac1afb0e77ca20917b Mon Sep 17 00:00:00 2001 From: tirth707 Date: Mon, 16 Mar 2026 20:29:39 +0530 Subject: [PATCH 4/5] test: increase spinner timeout to 30s to prevent CI flakiness Signed-off-by: tirth707 --- e2e/share.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/share.spec.ts b/e2e/share.spec.ts index 51ee0de1..f74ab8b6 100644 --- a/e2e/share.spec.ts +++ b/e2e/share.spec.ts @@ -70,7 +70,8 @@ test.describe('Share Functionality', () => { test('should display error toast and fallback to default state on corrupted share link', async ({ page }) => { await page.goto('/#data=invalid_garbage_base64_string'); - await expect(page.locator('.app-spinner-container')).toBeHidden({ timeout: 10000 }); + // Updated to 30000 to match the rest of the suite + await expect(page.locator('.app-spinner-container')).toBeHidden({ timeout: 30000 }); const errorMessage = page.locator('.ant-message-notice-content', { hasText: 'Failed to load shared workspace. The link data may be corrupted.' }); await expect(errorMessage).toBeVisible(); From 3de5945008edff6aa5d1e0f1b721dca34fac5123 Mon Sep 17 00:00:00 2001 From: Tirth Patel <139233612+tirth707@users.noreply.github.com> Date: Sun, 29 Mar 2026 20:44:46 +0530 Subject: [PATCH 5/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/App.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index dfd8e858..f3bd3cd1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,9 +40,13 @@ const App = () => { useEffect(() => { if (globalError && globalError.includes("Failed to load shared content:")) { void message.error("Failed to load shared workspace. The link data may be corrupted."); - void init(); + // Clear legacy ?data= query param so init() does not re-trigger the same failing loadFromLink + if (window.location.search.includes("data=")) { + navigate(window.location.pathname + window.location.hash, { replace: true }); + } + void init(); } - }, [globalError, init]); + }, [globalError, init, navigate]); useEffect(() => { const initializeApp = async () => {