diff --git a/core/http/react-ui/e2e/home-redesign.spec.js b/core/http/react-ui/e2e/home-redesign.spec.js new file mode 100644 index 000000000..5ee5950a8 --- /dev/null +++ b/core/http/react-ui/e2e/home-redesign.spec.js @@ -0,0 +1,29 @@ +import { test, expect } from './coverage-fixtures.js' + +test.describe('Home editorial redesign', () => { + test('renders the serif greeting header', async ({ page }) => { + await page.goto('/app') + const greeting = page.locator('.home-greeting') + await expect(greeting).toBeVisible({ timeout: 15_000 }) + const family = await greeting.evaluate(el => getComputedStyle(el).fontFamily) + expect(family.toLowerCase()).toContain('fraunces') + }) + + test('quick links expose a single primary action', async ({ page }) => { + await page.goto('/app') + await expect(page.locator('.home-greeting, .empty-state-title').first()).toBeVisible({ timeout: 15_000 }) + const primaries = page.locator('.home-quick-links .btn-primary') + // At most one primary CTA in the quick-links row. + expect(await primaries.count()).toBeLessThanOrEqual(1) + }) + + test('loaded-models block uses an editorial section heading', async ({ page }) => { + await page.goto('/app') + await expect(page.locator('.home-greeting').first()).toBeVisible({ timeout: 15_000 }) + // The refined loaded-models block introduces a SectionHeading; the legacy + // inline ".home-loaded-text" label is gone. + const heading = page.locator('.home-loaded .section-heading') + await expect(heading).toBeVisible({ timeout: 15_000 }) + await expect(heading).toHaveText(/active models/i) + }) +}) diff --git a/core/http/react-ui/public/locales/de/home.json b/core/http/react-ui/public/locales/de/home.json index 0c8f29119..8b881828f 100644 --- a/core/http/react-ui/public/locales/de/home.json +++ b/core/http/react-ui/public/locales/de/home.json @@ -43,6 +43,7 @@ "documentation": "Dokumentation" }, "loadedModels": { + "heading": "Aktive Modelle", "count_one": "{{count}} Modell geladen", "count_other": "{{count}} Modelle geladen", "stop": "Modell stoppen", diff --git a/core/http/react-ui/public/locales/en/home.json b/core/http/react-ui/public/locales/en/home.json index 331220a9f..0bdc26577 100644 --- a/core/http/react-ui/public/locales/en/home.json +++ b/core/http/react-ui/public/locales/en/home.json @@ -43,6 +43,7 @@ "documentation": "Documentation" }, "loadedModels": { + "heading": "Active models", "count_one": "{{count}} model loaded", "count_other": "{{count}} models loaded", "stop": "Stop model", diff --git a/core/http/react-ui/public/locales/es/home.json b/core/http/react-ui/public/locales/es/home.json index 0c700c364..397d78e07 100644 --- a/core/http/react-ui/public/locales/es/home.json +++ b/core/http/react-ui/public/locales/es/home.json @@ -43,6 +43,7 @@ "documentation": "Documentación" }, "loadedModels": { + "heading": "Modelos activos", "count_one": "{{count}} modelo cargado", "count_other": "{{count}} modelos cargados", "stop": "Detener modelo", diff --git a/core/http/react-ui/public/locales/id/home.json b/core/http/react-ui/public/locales/id/home.json index ea8f22c10..d7873e214 100644 --- a/core/http/react-ui/public/locales/id/home.json +++ b/core/http/react-ui/public/locales/id/home.json @@ -43,6 +43,7 @@ "documentation": "Dokumentasi" }, "loadedModels": { + "heading": "Model aktif", "count_one": "{{count}} model dimuat", "count_other": "{{count}} model dimuat", "stop": "Hentikan model", diff --git a/core/http/react-ui/public/locales/it/home.json b/core/http/react-ui/public/locales/it/home.json index a67b66517..8cc9d3fde 100644 --- a/core/http/react-ui/public/locales/it/home.json +++ b/core/http/react-ui/public/locales/it/home.json @@ -43,6 +43,7 @@ "documentation": "Documentazione" }, "loadedModels": { + "heading": "Modelli attivi", "count_one": "{{count}} modello caricato", "count_other": "{{count}} modelli caricati", "stop": "Ferma modello", diff --git a/core/http/react-ui/public/locales/ko/home.json b/core/http/react-ui/public/locales/ko/home.json index c93c12851..11fccbcb4 100644 --- a/core/http/react-ui/public/locales/ko/home.json +++ b/core/http/react-ui/public/locales/ko/home.json @@ -43,6 +43,7 @@ "documentation": "문서" }, "loadedModels": { + "heading": "활성 모델", "count_one": "모델 {{count}}개 로드됨", "count_other": "모델 {{count}}개 로드됨", "stop": "모델 중지", diff --git a/core/http/react-ui/public/locales/zh-CN/home.json b/core/http/react-ui/public/locales/zh-CN/home.json index 4f7517cf4..667094057 100644 --- a/core/http/react-ui/public/locales/zh-CN/home.json +++ b/core/http/react-ui/public/locales/zh-CN/home.json @@ -43,6 +43,7 @@ "documentation": "文档" }, "loadedModels": { + "heading": "活动模型", "count_one": "已加载 {{count}} 个模型", "count_other": "已加载 {{count}} 个模型", "stop": "停止模型", diff --git a/core/http/react-ui/src/App.css b/core/http/react-ui/src/App.css index b02e88849..5c7bad21e 100644 --- a/core/http/react-ui/src/App.css +++ b/core/http/react-ui/src/App.css @@ -5959,33 +5959,21 @@ select.input { transform: translateY(-1px); box-shadow: var(--shadow-sm); } +/* Quiet variant: a deliberately understated link (e.g. Documentation) so the + quick-links row keeps a single clear primary action. */ +.home-link-btn--quiet { opacity: 0.8; } /* Home loaded models */ -.home-loaded-models { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: var(--spacing-sm); - padding: var(--spacing-sm) var(--spacing-md); - background: var(--color-surface-raised); - border: 1px solid var(--color-border-subtle); - border-radius: var(--radius-lg); - font-size: var(--text-xs); - color: var(--color-text-secondary); +.home-loaded { width: 100%; - box-shadow: var(--shadow-subtle); -} -.home-loaded-dot { - width: 8px; - height: 8px; - border-radius: var(--radius-full); - background: var(--color-success); -} -.home-loaded-text { - font-weight: var(--font-weight-medium); - color: var(--color-text-primary); + display: flex; + flex-direction: column; + gap: var(--spacing-sm); } .home-loaded-list { + list-style: none; + margin: 0; + padding: 0; display: flex; flex-wrap: wrap; gap: var(--spacing-xs); @@ -5994,12 +5982,11 @@ select.input { display: inline-flex; align-items: center; gap: 6px; - padding: 3px 10px; + padding: 3px 6px 3px 10px; background: var(--color-surface-sunken); border: 1px solid var(--color-border-divider); border-radius: var(--radius-full); font-size: var(--text-xs); - font-family: var(--font-mono); } .home-loaded-item button { background: none; @@ -6007,18 +5994,16 @@ select.input { color: var(--color-error); cursor: pointer; padding: 0; + line-height: 1; font-size: 0.625rem; } +.home-loaded-empty { + color: var(--color-text-secondary); + font-size: var(--text-sm); + margin: 0; +} .home-stop-all { - margin-left: auto; - background: none; - border: 1px solid var(--color-error); - color: var(--color-error); - padding: 2px 8px; - border-radius: var(--radius-full); - font-size: 0.75rem; - cursor: pointer; - font-family: inherit; + align-self: flex-start; } /* Home wizard (no models) */ diff --git a/core/http/react-ui/src/pages/Home.jsx b/core/http/react-ui/src/pages/Home.jsx index f3a4957d7..2c36b03a6 100644 --- a/core/http/react-ui/src/pages/Home.jsx +++ b/core/http/react-ui/src/pages/Home.jsx @@ -14,6 +14,9 @@ import { fileToBase64, backendControlApi, systemApi, modelsApi, mcpApi, nodesApi import { API_CONFIG } from '../utils/config' import { greetingKey } from '../utils/greeting' import StatusPill from '../components/StatusPill' +import Skeleton from '../components/Skeleton' +import SectionHeading from '../components/SectionHeading' +import EmptyState from '../components/EmptyState' import { staggerStyle } from '../hooks/useStagger' export default function Home() { @@ -437,53 +440,64 @@ export default function Home() { {t('quickLinks.manageByChat')} )} -