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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The format is based on Keep a Changelog and this project follows Semantic Versio

### Changed

- **Design-system page conformance** — updated deterministic browser fixtures for current onboarding, provider, navigation, Settings, Catalog, and Run behavior; added responsive route, mobile navigation, focus restoration, target-size, overflow, and template-chip contrast regression coverage.
- **Design-system primitive ownership** — moved the native control, shared button, field, card, list, tab, empty-state, status, and utility patterns used by the frontend into `components.css`, removed unconsumed handoff primitives and duplicate app-level definitions, and tokenized application font sizes and border radii.
- **Design-system foundation** — promoted `docs/design-system/tokens/` as the durable byte-identical token source, standardized the desktop sidebar at 220px, added input and accessible semantic-status tokens, resolved invalid aliases, and restored readable template operation chips.
- **Agent workflow commands** — `AGENTS.md` now records the common validation, development, E2E, and benchmark data persistence workflows agents should use when changing the repo.
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4459,6 +4459,22 @@ pre.code-block {
grid-template-columns: 1fr;
}

.run-unified-layout,
.evaluate-queue-layout {
grid-template-columns: 1fr;
}

.run-config-rail,
.evaluate-queue-rail {
border-right: 0;
border-bottom: 1px solid var(--paper-7);
}

.evaluate-rubric {
border-left: 0;
border-top: 1px solid var(--paper-7);
}

.filters-row {
grid-template-columns: 1fr;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/tests/e2e/evaluate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test.describe('Evaluate queue', () => {
await expect(page.getByRole('heading', { name: 'Evaluate' })).toBeVisible();
await expect(page.locator('.merged-page-header').getByRole('heading', { name: 'Evaluate' })).toBeVisible();
await expect(page.locator('.evaluate-header')).toHaveCount(0);
await expect(page.getByText('Params')).toBeVisible();
await expect(page.getByText('Params')).toHaveCount(0);
await expect(page.getByRole('heading', { name: 'All caught up' })).toBeVisible();
});

Expand Down
12 changes: 11 additions & 1 deletion frontend/tests/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import crypto from 'node:crypto';

import { APIRequestContext, APIResponse, expect } from '@playwright/test';
import { APIRequestContext, APIResponse, expect, type Page } from '@playwright/test';
import { loadEnv } from 'vite';

const dirname = path.dirname(fileURLToPath(import.meta.url));
Expand All @@ -13,6 +13,16 @@ const API_BASE_URL = env.E2E_API_BASE_URL ?? 'http://localhost:8080';
const API_TOKEN = env.INFERHARNESS_API_TOKEN ?? env.VITE_INFERHARNESS_API_TOKEN;
const authHeaders = API_TOKEN ? { 'x-api-token': API_TOKEN } : undefined;

export async function dismissOnboarding(page: Page) {
await page.addInitScript(() => {
window.localStorage.setItem('inferharness.onboarding.v1', JSON.stringify({
dismissedAt: '2026-01-01T00:00:00.000Z',
replaying: false,
ribbonsDismissed: []
}));
});
}

export interface InferenceServerRecord {
inference_server: {
server_id: string;
Expand Down
8 changes: 7 additions & 1 deletion frontend/tests/e2e/inference-servers-archive.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { expect, test } from '@playwright/test';

import { archiveInferenceServer, createInferenceServer } from './helpers.js';
import { archiveInferenceServer, createInferenceServer, dismissOnboarding } from './helpers.js';

test('archives an inference server', async ({ page, request }) => {
await dismissOnboarding(page);
const created = await createInferenceServer(request);

await page.goto('/');
Expand All @@ -21,5 +22,10 @@ test('archives an inference server', async ({ page, request }) => {
]);
expect(archiveResponse.ok()).toBeTruthy();

await page.reload();
await page.getByRole('button', { name: 'Archived', exact: true }).click();
const archivedCard = page.locator('.catalog-server-card').filter({ hasText: created.inference_server.display_name });
await expect(archivedCard).toBeVisible();
await archivedCard.click();
await expect(page.getByRole('button', { name: 'Unarchive', exact: true })).toBeVisible();
});
12 changes: 9 additions & 3 deletions frontend/tests/e2e/inference-servers-create.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { expect, test } from '@playwright/test';
import crypto from 'node:crypto';

import { archiveInferenceServer, findInferenceServerByName } from './helpers.js';
import { archiveInferenceServer, dismissOnboarding, findInferenceServerByName } from './helpers.js';

test.beforeEach(async ({ page }) => {
await dismissOnboarding(page);
});

test('populates safe cloud provider defaults without filling the bearer token', async ({ page }) => {
await page.goto('/catalog?tab=servers');

await page.getByRole('button', { name: '+ Add server' }).click();
await page.getByRole('button', { name: '+ Add server', exact: true }).click();

const createDrawer = page
.getByRole('dialog')
Expand All @@ -20,6 +24,8 @@ test('populates safe cloud provider defaults without filling the bearer token',
'Local / custom inference server',
'OpenAI',
'Mistral',
'Anthropic',
'Google Gemini',
'Groq',
'Together AI',
'Fireworks AI',
Expand Down Expand Up @@ -68,7 +74,7 @@ test('creates a new inference server from the dashboard', async ({ page, request

await page.goto('/catalog?tab=servers');

await page.getByRole('button', { name: '+ Add server' }).click();
await page.getByRole('button', { name: '+ Add server', exact: true }).click();

const createDrawer = page
.getByRole('dialog')
Expand Down
3 changes: 2 additions & 1 deletion frontend/tests/e2e/inference-servers-edit.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { expect, test } from '@playwright/test';

import { archiveInferenceServer, createInferenceServer, findInferenceServerByName } from './helpers.js';
import { archiveInferenceServer, createInferenceServer, dismissOnboarding, findInferenceServerByName } from './helpers.js';

test('edits an inference server', async ({ page, request }) => {
await dismissOnboarding(page);
// Create server via API with hardware pre-populated to verify edit drawer pre-fills
const created = await createInferenceServer(request, {
hardware: {
Expand Down
3 changes: 2 additions & 1 deletion frontend/tests/e2e/inference-servers-inspect.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { expect, test } from '@playwright/test';

import { archiveInferenceServer, createInferenceServer } from './helpers.js';
import { archiveInferenceServer, createInferenceServer, dismissOnboarding } from './helpers.js';

test('inspects an inference server', async ({ page, request }) => {
await dismissOnboarding(page);
const created = await createInferenceServer(request);

await page.goto('/');
Expand Down
8 changes: 6 additions & 2 deletions frontend/tests/e2e/models.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ test('model filter rail narrows visible model cards', async ({ page, request })
await page.goto(`/catalog?tab=models&servers=${encodeURIComponent(serverId)}`);
await page.waitForLoadState('networkidle');

await page.getByLabel('MLX').check();
const mlxFilter = page.getByLabel('MLX');
await mlxFilter.click();
await expect(mlxFilter).toBeChecked();
await expect(page.locator('.catalog-model-card').filter({ hasText: 'Qwen3 Coder' })).toBeVisible();
await expect(page.locator('.catalog-model-card').filter({ hasText: 'Devstral' })).toHaveCount(0);

Expand All @@ -121,7 +123,9 @@ test('model filter rail uses persisted metadata instead of inferring from model
await page.goto(`/catalog?tab=models&servers=${encodeURIComponent(serverId)}`);
await page.waitForLoadState('networkidle');

await page.getByLabel('MLX').check();
const mlxFilter = page.getByLabel('MLX');
await mlxFilter.click();
await expect(mlxFilter).toBeChecked();
await expect(page.locator('.catalog-model-card').filter({ hasText: 'Persisted MLX' })).toBeVisible();
await expect(page.locator('.catalog-model-card').filter({ hasText: 'Raw MLX Suffix' })).toHaveCount(0);

Expand Down
Loading
Loading