From 67cc82e69ab9a624486e1d1cde0895b1a6348627 Mon Sep 17 00:00:00 2001 From: "malini-agent[bot]" <338999285+malini-agent[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:13:04 +0200 Subject: [PATCH] fix(web): stop false "updated elsewhere" on save and restore loaders --- apps/web/e2e/browser-lock.spec.ts | 65 ++++++++- .../web/src/lib/projects/ui/DiffEditor.svelte | 4 +- .../src/lib/projects/ui/ProjectPage.svelte | 37 ++++- .../src/lib/projects/ui/ProjectsPage.svelte | 6 +- .../secrets/monaco/SecretsFileEditor.svelte | 9 +- apps/web/src/lib/secrets/monaco/init.ts | 19 +++ .../lib/secrets/ui/SecretsEditorPage.svelte | 134 ++++++++++++------ apps/web/src/lib/shared/ui/ShellLoader.svelte | 30 ---- 8 files changed, 208 insertions(+), 96 deletions(-) create mode 100644 apps/web/src/lib/secrets/monaco/init.ts delete mode 100644 apps/web/src/lib/shared/ui/ShellLoader.svelte diff --git a/apps/web/e2e/browser-lock.spec.ts b/apps/web/e2e/browser-lock.spec.ts index 3106fe9..e6bc0d5 100644 --- a/apps/web/e2e/browser-lock.spec.ts +++ b/apps/web/e2e/browser-lock.spec.ts @@ -327,18 +327,13 @@ test("lock broadcast in one tab locks another open tab", async ({ await context.close(); }); -test("shell loading state is text-first, not the large six-dot grip", async ({ - page, -}) => { +test("projects loading state is the legacy amber grip", async ({ page }) => { await mockApi(page, { userDelayMs: 1_000 }); await seedBrowserState(page, Date.now()); await page.goto(`${APP}/app/project`, { waitUntil: "domcontentloaded" }); - await expect( - page.getByRole("status").filter({ hasText: "Loading" }), - ).toBeVisible(); - await expect(page.locator('svg[aria-label="Loading"]')).toHaveCount(0); + await expect(page.locator('svg[aria-label="Loading"]')).toBeVisible(); }); test("unlock after lock cleanup does not leave project shell stuck loading", async ({ @@ -446,3 +441,59 @@ test("unsaved edits survive tab switches and project switches go through the gua await expect(page).toHaveURL(/project-two$/); await expect(guard).toHaveCount(0); }); + +test("saving does not flag the project as updated elsewhere", async ({ + page, +}) => { + const passphrase = "correct horse battery staple"; + const fixtures = await createUnlockedFixtures(page, passphrase); + await mockApi(page, { user: fixtures.user, projects: [fixtures.project] }); + // The backend echoes every save to all subscribers, the saver included, + // and the echo can arrive before the PATCH response. + let echo: string | null = null; + let echoed = () => {}; + const echoDelivered = new Promise((resolve) => (echoed = resolve)); + await page.route("**/projects/*/events", async (route) => { + if (!echo) return route.fulfill({ status: 204, body: "" }); + await route.fulfill({ + headers: { "content-type": "text/event-stream" }, + body: `data: ${JSON.stringify({ newEncryptedSecrets: echo })}\n\n`, + }); + echoed(); + }); + await page.route("**/projects/project-eval", async (route) => { + if (route.request().method() !== "PATCH") return route.fallback(); + echo = route.request().postDataJSON().encryptedSecrets; + await echoDelivered; + await new Promise((resolve) => setTimeout(resolve, 300)); + await route.fulfill({ json: {} }); + }); + await seedAuthOnly(page); + + await page.goto(`${APP}/app/project/project-eval`, { + waitUntil: "domcontentloaded", + }); + await page.getByRole("textbox", { name: "Passphrase" }).fill(passphrase); + await page.getByRole("button", { name: "Unlock" }).click(); + + const editor = page.locator(".monaco-editor .view-lines"); + await editor.click(); + await page.keyboard.press("ControlOrMeta+End"); + await page.keyboard.press("Enter"); + await page.keyboard.type('SAVED="1"'); + await page.getByRole("button", { name: "Save", exact: true }).click(); + + await expect( + page.getByRole("button", { name: "Saved", exact: true }), + ).toBeVisible(); + await expect( + page.getByText("This project was updated elsewhere"), + ).toHaveCount(0); + + await editor.click(); + await page.keyboard.press("ControlOrMeta+End"); + await page.keyboard.type("2"); + await expect( + page.getByRole("button", { name: "Save", exact: true }), + ).toBeEnabled(); +}); diff --git a/apps/web/src/lib/projects/ui/DiffEditor.svelte b/apps/web/src/lib/projects/ui/DiffEditor.svelte index 089454f..0d779ff 100644 --- a/apps/web/src/lib/projects/ui/DiffEditor.svelte +++ b/apps/web/src/lib/projects/ui/DiffEditor.svelte @@ -1,6 +1,6 @@ - -
- - - {label} - -