From 88bca1244fcb9908c7826f7cb47398f314e77de3 Mon Sep 17 00:00:00 2001 From: gcharang <21151592+gcharang@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:21:04 +0400 Subject: [PATCH] fix(web): validate production assets in image builds --- apps/web/Dockerfile | 3 ++- apps/web/src/routes/__root.tsx | 8 +------- apps/web/tests/e2e/operator-console.spec.ts | 6 +++++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 981a7b5..f6e270c 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -15,7 +15,8 @@ EXPOSE 3001 CMD ["pnpm", "--filter", "@vectorless-rag/web", "exec", "vite", "dev", "--host", "0.0.0.0", "--port", "3001"] FROM source AS build -RUN pnpm --filter @vectorless-rag/web build +RUN pnpm --filter @vectorless-rag/web build \ + && pnpm --filter @vectorless-rag/web production-smoke FROM node:22.23-alpine AS runtime ENV NODE_ENV=production HOST=0.0.0.0 PORT=3001 diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index f963be1..78339af 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react' import { AppearanceProvider } from '../components/AppearanceProvider' import { RouteError } from '../components/RouteError' -import appCss from '../styles.css?url' +import '../styles.css' const THEME_INIT_SCRIPT = `(function(){try{var mode=localStorage.getItem('vectorless:appearance');if(mode==='light'||mode==='dark'){document.documentElement.setAttribute('data-theme',mode);}else{document.documentElement.removeAttribute('data-theme');}}catch(_){document.documentElement.removeAttribute('data-theme');}})();` @@ -22,12 +22,6 @@ export const Route = createRootRoute({ title: 'Vectorless RAG Operator Console', }, ], - links: [ - { - rel: 'stylesheet', - href: appCss, - }, - ], }), headers: () => ({ 'Cache-Control': 'private, no-store' }), errorComponent: ({ error }) => , diff --git a/apps/web/tests/e2e/operator-console.spec.ts b/apps/web/tests/e2e/operator-console.spec.ts index 6bf5b44..8aad358 100644 --- a/apps/web/tests/e2e/operator-console.spec.ts +++ b/apps/web/tests/e2e/operator-console.spec.ts @@ -9,7 +9,7 @@ const readingWidth = 60 * 16 const chatWidth = 60 * 16 async function waitForHydration(page: import('@playwright/test').Page) { - await expect(page.locator('html')).toHaveAttribute('data-hydrated', 'true') + await expect(page.locator('html')).toHaveAttribute('data-hydrated', 'true', { timeout: 15_000 }) } interface AppearanceSnapshot { @@ -111,6 +111,7 @@ async function captureWaitingIndicator(page: import('@playwright/test').Page) { async function connectToChat(page: import('@playwright/test').Page, apiKey = key) { await page.goto('/connect') + await waitForHydration(page) await expect(page.getByRole('button', { name: 'Connect' })).toBeEnabled() await page.getByLabel('API key').fill(apiKey) await page.getByRole('button', { name: 'Connect' }).click() @@ -411,6 +412,7 @@ test('marks personalized responses no-store without changing stream caching', as const documentResponse = await page.goto('/connect') expect(documentResponse?.headers()['cache-control']).toBe('private, no-store') + await waitForHydration(page) await expect(page.getByRole('button', { name: 'Connect' })).toBeEnabled() await page.getByLabel('API key').fill(key) const serverFunctionResponse = page.waitForResponse((response) => ( @@ -428,6 +430,7 @@ test('marks personalized responses no-store without changing stream caching', as test('connects through the UI and exercises the operator workflows', async ({ page }) => { await page.goto('/connect') + await waitForHydration(page) const connectButton = page.getByRole('button', { name: 'Connect' }) await expect(connectButton).toBeEnabled() await page.getByLabel('API key').fill(key) @@ -807,6 +810,7 @@ test('redirects protected routes to connect without a session', async ({ page }) test('returns an existing documents-only session to its scoped home', async ({ page }) => { await page.goto('/connect') + await waitForHydration(page) await expect(page.getByRole('button', { name: 'Connect' })).toBeEnabled() await page.getByLabel('API key').fill('mock-documents-only') await page.getByRole('button', { name: 'Connect' }).click()