Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions apps/web/e2e/browser-lock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ test("lock broadcast in one tab locks another open tab", async ({
await context.close();
});

test("shell loading state is a quiet skeleton, not the large six-dot grip", async ({
test("shell loading state is text-first, not the large six-dot grip", async ({
page,
}) => {
await mockApi(page, { userDelayMs: 1_000 });
Expand All @@ -336,7 +336,7 @@ test("shell loading state is a quiet skeleton, not the large six-dot grip", asyn
await page.goto(`${APP}/app/project`, { waitUntil: "domcontentloaded" });

await expect(
page.getByRole("status", { name: "Loading projects" }),
page.getByRole("status").filter({ hasText: "Loading" }),
).toBeVisible();
await expect(page.locator('svg[aria-label="Loading"]')).toHaveCount(0);
});
Expand Down Expand Up @@ -417,22 +417,26 @@ test("unsaved edits survive tab switches and project switches go through the gua
await page.getByRole("textbox", { name: "Passphrase" }).fill(passphrase);
await page.getByRole("button", { name: "Unlock" }).click();

// Raw .env view: the editor lives in Monaco there (the table is the default view).
await page.getByRole("radio", { name: ".env" }).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('UNSAVED="1"');
await expect(page.getByRole("button", { name: /^Save/ })).toBeEnabled();

const tabs = page.getByRole("navigation", { name: "Project" });
await tabs.getByRole("link", { name: "History" }).click();
await expect(page).toHaveURL(/project-eval\/history$/);
await tabs.getByRole("link", { name: "Secrets" }).click();
await expect(
page.getByRole("button", { name: "Save", exact: true }),
).toBeEnabled();

await page
.getByRole("button", { name: "History", exact: true })
.first()
.click();
await page
.getByRole("button", { name: "Editor", exact: true })
.first()
.click();
await expect(editor).toContainText("UNSAVED");

await page.getByRole("link", { name: /Second Project/ }).click();
await page.locator("aside nav a", { hasText: "Second Project" }).click();
const guard = page.getByRole("dialog", { name: "Save your changes?" });
await expect(guard).toBeVisible();
await expect(page).toHaveURL(/project-eval$/);
Expand Down
56 changes: 56 additions & 0 deletions apps/web/e2e/visual-parity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { PNG } from "pngjs";

const REACT = process.env.REACT_URL ?? "http://127.0.0.1:5173";
const SVELTE = process.env.SVELTE_URL ?? "http://127.0.0.1:9090";
const PROJECT_ID = process.env.PARITY_PROJECT_ID ?? "69e4efc7ed94cab2c9b88c9b";
const REACT_STORAGE_STATE = process.env.PARITY_REACT_STORAGE_STATE;
const SVELTE_STORAGE_STATE = process.env.PARITY_SVELTE_STORAGE_STATE;

test.beforeAll(async ({ request }, testInfo) => {
const check = async (url: string) => {
Expand Down Expand Up @@ -94,3 +97,56 @@ for (const route of Object.keys(THRESHOLDS)) {
).toBeLessThanOrEqual(threshold);
});
}

test("authenticated history tab visual parity", async ({
browser,
}, testInfo) => {
if (!REACT_STORAGE_STATE || !SVELTE_STORAGE_STATE) {
testInfo.skip(
true,
"History parity needs authenticated storage states. Provide PARITY_REACT_STORAGE_STATE and PARITY_SVELTE_STORAGE_STATE.",
);
}

const route = `/app/project/${PROJECT_ID}`;
const reactContext = await browser.newContext({
storageState: REACT_STORAGE_STATE,
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1,
colorScheme: "dark",
});
const svelteContext = await browser.newContext({
storageState: SVELTE_STORAGE_STATE,
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 1,
colorScheme: "dark",
});

const reactPage = await reactContext.newPage();
await reactPage.goto(`${REACT}${route}`, { waitUntil: "load" });
await reactPage.getByRole("button", { name: "History" }).click();
await expect(
reactPage.getByPlaceholder("Search edits — author, email, diff content…"),
).toBeVisible();
await reactPage.waitForTimeout(1200);

const sveltePage = await svelteContext.newPage();
await sveltePage.goto(`${SVELTE}${route}`, { waitUntil: "load" });
await sveltePage.getByRole("button", { name: "History" }).click();
await expect(
sveltePage.getByPlaceholder("Search edits — author, email, diff content…"),
).toBeVisible();
await sveltePage.waitForTimeout(1200);

const reactShot = await reactPage.locator("main").screenshot();
const svelteShot = await sveltePage.locator("main").screenshot();

await reactContext.close();
await svelteContext.close();

const ratio = mismatchRatio(reactShot, svelteShot);
expect(
ratio,
`History tab pixel mismatch ${(ratio * 100).toFixed(2)}% for ${route}.`,
).toBeLessThanOrEqual(0.03);
});
3 changes: 1 addition & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
},
"dependencies": {
"@fontsource-variable/funnel-display": "^5.2.8",
"@fontsource-variable/geist": "^5.3.0",
"@fontsource-variable/geist-mono": "^5.3.0",
"@monaco-editor/loader": "^1.5.0",
"@packages/backend-sdk": "workspace:*",
"@tabler/icons-svelte": "^3.34.1",
"clsx": "^2.1.1",
Expand Down
61 changes: 0 additions & 61 deletions apps/web/src/app.css
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
@import "@fontsource-variable/funnel-display";
@import "@fontsource-variable/geist";
@import "@fontsource-variable/geist-mono";
@import "tailwindcss";
@plugin "daisyui" {
logs: false;
}
@import "tw-animate-css";
@import "./lib/appearance/greys.css";
@import "./lib/appearance/theme.css";

@custom-variant dark (&:is(.dark *));

Expand Down Expand Up @@ -54,54 +50,6 @@
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-editor: #1e1e1e;
/* App design system (lib/appearance/theme.css). */
--font-ui: "Geist Variable", system-ui, sans-serif;
--font-code: "Geist Mono Variable", ui-monospace, monospace;
--color-root: var(--root);
--color-root-hover: var(--root-hover);
--color-root-sel: var(--root-sel);
--color-s50: var(--s50);
--color-s50-hover: var(--s50-hover);
--color-s50-sel: var(--s50-sel);
--color-s50-border: var(--s50-border);
--color-s100: var(--s100);
--color-s100-hover: var(--s100-hover);
--color-s100-sel: var(--s100-sel);
--color-s100-border: var(--s100-border);
--color-s150: var(--s150);
--color-s150-hover: var(--s150-hover);
--color-s150-border: var(--s150-border);
--color-elev: var(--elev);
--color-elev-hover: var(--elev-hover);
--color-elev-border: var(--elev-border);
--color-field: var(--field);
--color-field-border: var(--field-border);
--color-fg: var(--fg);
--color-fg2: var(--fg2);
--color-fg3: var(--fg3);
--color-ph: var(--ph);
--color-faint: var(--faint);
--color-btn: var(--btn);
--color-btn-hover: var(--btn-hover);
--color-btn-border: var(--btn-border);
--color-btn-primary: var(--btn-primary);
--color-btn-primary-hover: var(--btn-primary-hover);
--color-btn-primary-fg: var(--btn-primary-fg);
--color-success: var(--success);
--color-warning: var(--warning);
--color-danger: var(--danger);
--color-danger-bg: var(--danger-bg);
--color-danger-bg-hover: var(--danger-bg-hover);
--radius-panel: 24px;
--radius-well: 18px;
--radius-card: 12px;
--radius-row: 6px;
--shadow-raise: var(--raise-shadow);
--shadow-panel: inset 0 0 0 0.5px var(--s50-border);
--shadow-card: inset 0 0 0 0.5px var(--s100-border), var(--raise-shadow);
--shadow-btn: inset 0 0 0 0.5px var(--btn-border), var(--raise-shadow);
--shadow-field: inset 0 0 0 0.5px var(--field-border);
--shadow-elev: 0 0 0 0.5px var(--elev-border), var(--elev-shadow);
--animate-marquee: marquee var(--duration) infinite linear;
--animate-marquee-vertical: marquee-vertical var(--duration) linear infinite;
@keyframes marquee {
Expand Down Expand Up @@ -254,15 +202,6 @@ body {
min-height: 100%;
}

/* Monospace for keys and values. Ligatures off so "==" or "->" never render as one glyph. */
@utility mono {
font-family: var(--font-code);
font-variant-ligatures: none;
font-feature-settings:
"liga" 0,
"calt" 0;
}

body {
margin: 0;
}
Expand Down
34 changes: 0 additions & 34 deletions apps/web/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,6 @@
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="darkreader-lock" />
<script>
// Apply the saved app theme and grey preset before first paint (lib/appearance/appearance.svelte.ts).
(function () {
var a = {};
try {
a =
JSON.parse(localStorage.getItem("cryptly.appearance") || "{}") ||
{};
} catch (e) {}
var t =
a.theme === "light" || a.theme === "dark"
? a.theme
: matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
document.documentElement.dataset.theme = t;
document.documentElement.dataset.greys =
typeof a.greys === "string" ? a.greys : "Stone";
// App shell pages: until the app mounts (body wrapper still empty), paint the theme's
// root grey instead of the landing page's black, so light mode doesn't flash dark.
if (/^\/app\/(project|settings)(\/|$)/.test(location.pathname)) {
var s = document.createElement("style");
var empty = "html:not(:has(body > div > :not(script)))";
s.textContent =
empty +
"," +
empty +
" body{background:var(--root," +
(t === "light" ? "#ece9e7" : "#161413") +
")!important}";
document.head.appendChild(s);
}
})();
</script>
<meta name="theme-color" content="#000000" />

<!-- Open Graph / Facebook -->
Expand Down
Loading
Loading