Merge branch 'master' into feat/md-incre-2

This commit is contained in:
07akioni
2026-08-06 15:12:53 +08:00
committed by GitHub
9 changed files with 117 additions and 8 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-05-web-preview-product-badge.md
2026-08-05-web-preview-product-badge.md: c20dedf8caa497d17a577cf46261c6a24c09a1ce
2026-08-05-web-preview-product-badge.zh.md: c428dabf2a9a2ece2f90b0c3a527835a494c7cb7

View File

@@ -0,0 +1,33 @@
# Agent Note: Web preview product badge
Status: implemented
English | [中文](2026-08-05-web-preview-product-badge.zh.md)
## Problem
The Web empty state does not identify the product as a preview. Users can enter the main session surface without seeing that the product is pre-release, while a deployment setting would misrepresent a product-wide lifecycle decision as an operator choice.
## Decision
The empty hero always renders a localized `Preview` / `预览版` badge beneath the headline. It has no configuration switch: preview status is one product identity shared by every deployment, not a deployment-varying tunable.
The badge keeps the business-tertiary background so both themes retain the product-blue context, and uses the theme's primary label token for text. That pairing gives ordinary 12px text sufficient contrast in both light and dark themes; the business-primary foreground is reserved for larger or non-text accents because it does not reach the required contrast on this background.
The badge leaves the product when the first tagged release removes the repository's pre-release stance, or when the owning product decision declares the preview phase complete. That change removes the badge and its locale key together rather than adding a runtime toggle.
## Alternatives considered
**Make preview status configurable.** Rejected because two deployments of the same pre-release product must not present different lifecycle identities, and a configuration field would turn product release state into an unsupported operator choice.
**Use business-primary text on the business-tertiary background.** Rejected because the resulting light- and dark-theme contrast is below the 4.5:1 requirement for the badge's 12px text.
**Hide the badge from the accessibility tree.** Rejected because preview status is product information rather than decoration; the accessible headline therefore includes the badge text.
## Consequences
Every new session exposes the same localized preview identity in visual and accessibility output. Removing preview status is an explicit product-release edit, and the badge favors readable neutral text over an all-blue treatment while retaining the business-tinted background.
## Testing
The conversation component test covers both localized badge values, and the Web lifecycle snapshots pin the English badge in the assembled empty hero.

View File

@@ -0,0 +1,33 @@
# Agent NoteWeb 预览版产品徽标
状态:已实现
[English](2026-08-05-web-preview-product-badge.md) | 中文
## 问题
Web 空状态没有标明产品处于预览版阶段。用户可以在未看到产品尚未正式发布的情况下进入主会话界面;若改用部署设置,则会把面向整个产品的生命周期决策误表述为操作者的选择。
## 决策
空状态主视觉区始终在标题下方渲染本地化的 `Preview` / `预览版` 徽标。它没有配置开关:预览状态是所有部署共同的一项产品身份,而不是随部署变化的可调参数。
徽标沿用 business-tertiary 背景,使两套主题都保留产品蓝的视觉语境;文字则使用主题的 primary label token。这一组合让普通 12px 文字在浅色与暗色主题下都有足够的对比度。business-primary 前景色仅留给较大字号文本或非文本强调元素,因为它在该背景上达不到要求的对比度。
首个 tagged release 取消仓库的预发布立场时,或归属产品方明确决定预览阶段结束时,产品会移除该徽标。这一改动会同时移除徽标及其 locale key而不是增加运行时开关。
## 曾考虑的替代方案
**让预览状态可配置。** 不予采纳:同一个预发布产品的两套部署不得展示不同的生命周期身份,配置字段还会把产品发布状态变成一项不受支持的操作者选择。
**在 business-tertiary 背景上使用 business-primary 文字。** 不予采纳:由此产生的浅色与暗色主题对比度低于徽标 12px 文字所要求的 4.5:1。
**在无障碍树中隐藏徽标。** 不予采纳:预览状态是产品信息而非装饰,因此无障碍标题会包含徽标文字。
## 后果
每个新会话都会在视觉与无障碍输出中呈现相同的本地化预览版身份。移除预览状态是一项显式的产品发布改动;徽标保留业务蓝色调背景,同时采用可读的中性色文字,而不是全蓝色处理方案。
## 测试
会话组件测试覆盖两个本地化徽标值Web 生命周期快照则固定组装后空状态主视觉区中的英文徽标。

View File

@@ -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

View File

@@ -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

View File

@@ -45,6 +45,7 @@ export const zh = {
'access.confirm.cancel': '取消',
'access.confirm.enable': '启用 Full access',
'hero.headline': '开始构建吧',
'hero.preview': '预览版',
'hero.chooseWorkspace': '选择工作区',
'session.hierarchy': '会话层级',
'details.title': '详情',
@@ -184,6 +185,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',

View File

@@ -119,7 +119,8 @@ export function HeroShell({ t, children }: HeroShellProps) {
<div className={css.headline}>
{/* figma 34:10412: fish 34×25 leading the headline, gap 10. */}
<FishLogo size={34} className={css.fish} />
{t('hero.headline')}
<span className={css.headlineText}>{t('hero.headline')}</span>
<span className={css.previewBadge}>{t('hero.preview')}</span>
</div>
<div className={css.body}>
{/* The resident composer (ConversationRoot wrapActiveBody seat; the

View File

@@ -23,21 +23,44 @@
overflow: visible;
}
/* figma 34:10411: fish + title, gap 10, centered; 26/32 wt500. */
/* figma 34:10411: fish + title, gap 10, centered; 26/32 wt500. The preview
badge is a product addition outside that source and aligns to the title. */
.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-label-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);
}

View File

@@ -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 {
@@ -213,6 +215,14 @@ function mount(
}
}
describe('Hero chrome', () => {
it('renders the English preview badge through the hero locale seat', () => {
const view = render(<HeroShell t={makeTranslate(en, commonEn)} />)
expect(view.getByText('Let\'s start building')).toBeTruthy()
expect(view.getByText('Preview')).toBeTruthy()
})
})
describe('ConversationRoot resident composer', () => {
it('keeps composer text in the machine, mirrors to the chat store, and submits through the sink', () => {
const b = mount(conversationSnapshot())
@@ -273,6 +283,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