diff --git a/docs/frontend/application.md b/docs/frontend/application.md index 688dea5..e10fccd 100644 --- a/docs/frontend/application.md +++ b/docs/frontend/application.md @@ -4,7 +4,7 @@ ## 状态与路由 -应用启动先请求 `system/status`,已初始化时再请求 `auth/me`。未初始化渲染安装页;未登录渲染登录页;登录后由 `BrowserRouter` 提供 `/` 总览和 `/settings` 设置,未知路径回到 `/`。状态响应中的构建版本以 `v` 徽标显示在安装页、登录页和登录后侧栏的品牌区域。这些分支只负责交互,服务端 session 才是安全边界。 +应用启动先请求 `system/status`,已初始化时再请求 `auth/me`。未初始化渲染安装页;未登录渲染登录页;登录后由 `BrowserRouter` 提供 `/` 总览和 `/settings` 设置,未知路径回到 `/`。状态响应中的构建版本以 `v` 徽标显示在安装页、登录页和登录后侧栏的品牌区域;品牌图标、名称和版本徽标保持单行排列。这些分支只负责交互,服务端 session 才是安全边界。 主题以服务端通用设置为持久来源,`localStorage` 仅用于首屏缓存;system 模式会跟随系统主题变化。当前账号 ID 保存到 `localStorage`。总览先加载账号列表,为当前账号加载 Dashboard,并在每轮请求完成 30 秒后刷新;切换账号会取消旧请求并校验响应账号,避免迟到响应覆盖当前账号。手动刷新调用账号级 sync 后重新读取 Dashboard。 diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 2856a6f..0694f4c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -88,7 +88,7 @@ const Splash = () => ( function Brand({ version }: { version?: string }) { return (
- Codex Helper + Codex Helper {version && v{version}}
); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index c5931a5..8bf2102 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -71,6 +71,7 @@ select { gap: 10px; font-weight: 700; letter-spacing: -0.03em; + white-space: nowrap; } .logo svg { color: var(--accent); diff --git a/frontend/tests/settings-layout.spec.ts b/frontend/tests/settings-layout.spec.ts index 61f9e15..ddccd36 100644 --- a/frontend/tests/settings-layout.spec.ts +++ b/frontend/tests/settings-layout.spec.ts @@ -1,7 +1,11 @@ import { expect, test, type Page } from "@playwright/test"; const responses: Record = { - "system/status": { initialized: true, appServer: true, version: "test" }, + "system/status": { + initialized: true, + appServer: true, + version: "0.3.0-beta.1", + }, "auth/me": { username: "admin" }, "settings/general": { timezone: "America/New_York", @@ -60,9 +64,28 @@ async function openSettings(page: Page) { test("shows the build version in the authenticated brand", async ({ page }) => { await openSettings(page); - const badge = page.locator("aside .version-badge"); + const brand = page.locator("aside .logo"); + const badge = brand.locator(".version-badge"); await expect(badge).toBeVisible(); - await expect(badge).toHaveText("vtest"); + await expect(badge).toHaveText("v0.3.0-beta.1"); + await expect(brand).toHaveCSS("white-space", "nowrap"); + const layout = await brand.evaluate((element) => { + const badgeElement = element.querySelector(".version-badge"); + const nameElement = element.querySelector(".brand-name"); + const iconElement = element.querySelector("svg"); + if (!badgeElement || !nameElement || !iconElement) + throw new Error("brand is incomplete"); + const badgeRect = badgeElement.getBoundingClientRect(); + const nameRect = nameElement.getBoundingClientRect(); + const iconRect = iconElement.getBoundingClientRect(); + return { + badgeCenter: badgeRect.top + badgeRect.height / 2, + nameCenter: nameRect.top + nameRect.height / 2, + iconCenter: iconRect.top + iconRect.height / 2, + }; + }); + expect(Math.abs(layout.badgeCenter - layout.nameCenter)).toBeLessThan(1); + expect(Math.abs(layout.iconCenter - layout.nameCenter)).toBeLessThan(1); }); test("shows the build version before setup", async ({ page }) => {