From 047ea509873aa2b891deb701924c19d7b35ec107 Mon Sep 17 00:00:00 2001
From: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com>
Date: Tue, 4 Aug 2026 01:46:52 -0700
Subject: [PATCH] feat(web): add preview badge to empty hero
---
.../lifecycle-chrome/hero.expected.md | 2 +-
.../lifecycle-chrome/plan-active.expected.md | 2 +-
.../ui-conversation/src/client/locales.ts | 2 ++
.../src/client/skeleton/EmptyHero.tsx | 3 +-
.../src/client/skeleton/HeroShell.module.css | 31 ++++++++++++++++---
.../ui-conversation/tests/skeleton.spec.tsx | 11 ++++++-
6 files changed, 43 insertions(+), 8 deletions(-)
diff --git a/apps/web/tests/snapshots/lifecycle-chrome/hero.expected.md b/apps/web/tests/snapshots/lifecycle-chrome/hero.expected.md
index 8611ac5c0d..bdb07876a3 100644
--- a/apps/web/tests/snapshots/lifecycle-chrome/hero.expected.md
+++ b/apps/web/tests/snapshots/lifecycle-chrome/hero.expected.md
@@ -20,7 +20,7 @@
- button "Settings":
- img
- text: Settings
-- text: Let's start building
+- text: Let's start building Preview
- button "Choose workspace":
- img
- text: workspace
diff --git a/apps/web/tests/snapshots/lifecycle-chrome/plan-active.expected.md b/apps/web/tests/snapshots/lifecycle-chrome/plan-active.expected.md
index a9fb7901d7..8c5cf915dc 100644
--- a/apps/web/tests/snapshots/lifecycle-chrome/plan-active.expected.md
+++ b/apps/web/tests/snapshots/lifecycle-chrome/plan-active.expected.md
@@ -20,7 +20,7 @@
- button "Settings":
- img
- text: Settings
-- text: Let's start building
+- text: Let's start building Preview
- button "Choose workspace":
- img
- text: workspace
diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts
index 7a38aaa0f8..2f05223e5d 100644
--- a/packages/client/ui-conversation/src/client/locales.ts
+++ b/packages/client/ui-conversation/src/client/locales.ts
@@ -33,6 +33,7 @@ export const zh = {
'access.confirm.cancel': '取消',
'access.confirm.enable': '启用 Full access',
'hero.headline': '开始构建吧',
+ 'hero.preview': '预览版',
'hero.chooseWorkspace': '选择工作区',
'session.hierarchy': '会话层级',
'details.title': '详情',
@@ -144,6 +145,7 @@ export const en = {
'access.confirm.cancel': 'Cancel',
'access.confirm.enable': 'Enable Full access',
'hero.headline': 'Let\'s start building',
+ 'hero.preview': 'Preview',
'hero.chooseWorkspace': 'Choose workspace',
'session.hierarchy': 'Session hierarchy',
'details.title': 'Details',
diff --git a/packages/client/ui-conversation/src/client/skeleton/EmptyHero.tsx b/packages/client/ui-conversation/src/client/skeleton/EmptyHero.tsx
index 0c1b31bbb7..4b491e1f7f 100644
--- a/packages/client/ui-conversation/src/client/skeleton/EmptyHero.tsx
+++ b/packages/client/ui-conversation/src/client/skeleton/EmptyHero.tsx
@@ -119,7 +119,8 @@ export function HeroShell({ t, children }: HeroShellProps) {
{/* figma 34:10412: fish 34×25 leading the headline, gap 10. */}
- {t('hero.headline')}
+ {t('hero.headline')}
+ {t('hero.preview')}
{/* The resident composer (ConversationRoot wrapActiveBody seat; the
diff --git a/packages/client/ui-conversation/src/client/skeleton/HeroShell.module.css b/packages/client/ui-conversation/src/client/skeleton/HeroShell.module.css
index 523f640c0b..a95007e381 100644
--- a/packages/client/ui-conversation/src/client/skeleton/HeroShell.module.css
+++ b/packages/client/ui-conversation/src/client/skeleton/HeroShell.module.css
@@ -23,21 +23,44 @@
overflow: visible;
}
-/* figma 34:10411: fish + title, gap 10, centered; 26/32 wt500. */
+/* Fish + title stay centered as one unit; the preview badge aligns with the
+ title's leading edge on the second row. */
.headline {
- display: flex;
+ display: grid;
+ grid-template-columns: 34px auto;
+ column-gap: 10px;
+ row-gap: 4px;
align-items: center;
justify-content: center;
- gap: 10px;
font-size: 26px;
line-height: 32px;
font-weight: 500;
color: var(--dsw-alias-label-primary);
}
+.headlineText {
+ grid-row: 1;
+ grid-column: 2;
+}
+
+.previewBadge {
+ grid-row: 2;
+ grid-column: 2;
+ justify-self: start;
+ padding: 0 4px;
+ border-radius: 4px;
+ background: var(--dsw-alias-state-business-tertiary);
+ color: var(--dsw-alias-state-business-primary);
+ font-size: 12px;
+ line-height: 18px;
+ font-weight: 500;
+ white-space: nowrap;
+}
+
/* figma fish fill rides business blue. */
.fish {
- flex: none;
+ grid-row: 1;
+ grid-column: 1;
color: var(--dsw-alias-state-business-primary);
}
diff --git a/packages/client/ui-conversation/tests/skeleton.spec.tsx b/packages/client/ui-conversation/tests/skeleton.spec.tsx
index 312ec88642..506984bca4 100644
--- a/packages/client/ui-conversation/tests/skeleton.spec.tsx
+++ b/packages/client/ui-conversation/tests/skeleton.spec.tsx
@@ -12,12 +12,14 @@ import type {
import type { ConversationRootProps } from '../src/client/skeleton/ConversationRoot.tsx'
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
+import { en as commonEn } from '@deepseek-ai/dsh-client-locale/src/locales/en.ts'
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
import { createChatStore } from '../src/client/stores.ts'
import { SessionInputShell } from '../src/client/input/facade.ts'
-import { zh } from '../src/client/locales.ts'
+import { en, zh } from '../src/client/locales.ts'
import { ConversationRoot } from '../src/client/skeleton/ConversationRoot.tsx'
import { ConversationSession } from '../src/client/skeleton/ConversationSession.tsx'
+import { HeroShell } from '../src/client/skeleton/EmptyHero.tsx'
import { InputBar } from '../src/client/skeleton/InputBar.tsx'
import type { InputBarProps } from '../src/client/skeleton/InputBar.tsx'
import type {
@@ -214,6 +216,12 @@ function mount(
}
describe('ConversationRoot resident composer', () => {
+ it('renders the English preview badge through the hero locale seat', () => {
+ const view = render()
+ expect(view.getByText('Let\'s start building')).toBeTruthy()
+ expect(view.getByText('Preview')).toBeTruthy()
+ })
+
it('keeps composer text in the machine, mirrors to the chat store, and submits through the sink', () => {
const b = mount(conversationSnapshot())
const box = b.view.getByRole('textbox')
@@ -273,6 +281,7 @@ describe('ConversationRoot resident composer', () => {
expect(host).not.toBeNull()
expect(header?.getAttribute('aria-hidden')).toBe('true')
expect(b.view.getByText('开始构建吧')).toBeTruthy()
+ expect(b.view.getByText('预览版')).toBeTruthy()
expect(b.view.queryByTestId('view-chat')).toBeNull()
// The same machine-backed textarea is live in the hero, and the
// persistence mirror stays bound (ConversationSession mounts chrome-hidden