Merge pull request #4457 from deepseek-harness/worktree/plan-tab-text-icon

fix(web): align plan sidebar with file previews
This commit is contained in:
Yichen Jiang authored and GitHub committed 2026-09-18 12:41:32 +08:00
commit 5daaaea3c0
8 files changed
+15 -34

No files matched your search

@@ -1,5 +1,4 @@
- region "Temporary review":
- button "Copy"
- heading "Temporary review" [level=1]
- paragraph: Review without a tool invocation.
- heading "Implementation" [level=2]
+2 -2
View File
@@ -2,5 +2,5 @@
# 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 packages/client/ui-plan/README.md
README.md: f7e7aede2c76717228d101153e4d3d2f8d3f88c4
README.zh.md: 9ab37a25dd7cb406b42bf5c531445357d6725e2e
README.md: f5cec26aff9ec7fc478915c20bca86c06474f9f7
README.zh.md: 4bd416b757b7ff6bae2947b84db9f8d50101aa00
+1 -1
View File
@@ -33,7 +33,7 @@ While the effective target is plan mode, the seat renders the warn-colored "Plan
### Reading submitted plans
When a Turn ends, each submitted plan appears in its final artifact area, using the file-delivery card treatment with a Markdown icon, title, and Open action. A pending plan opens automatically once per submission in the current browser session. Closing it stays effective across review remounts; a new submission opens its own plan. Historical cards open only when clicked. Use the card or review strip’s View full plan link to read and copy the complete Markdown. Plan tabs show a document-and-pen icon. Different submissions retain separate tabs; the review buttons alone decide whether implementation may begin.
When a Turn ends, each submitted plan appears in its final artifact area, using the file-delivery card treatment with a Markdown icon, title, and Open action. A pending plan opens automatically once per submission in the current browser session. Closing it stays effective across review remounts; a new submission opens its own plan. Historical cards open only when clicked. Use the card or review strip’s View full plan link to read the complete Markdown. Plan tabs show a plain text file icon. Different submissions retain separate tabs; the review buttons alone decide whether implementation may begin.
A review without a logged invocation also opens automatically. Its complete text lives only in the tab’s navigation memory, and the pending review card can reopen it. Reloading the page loses that text; an expired preview directs the user back to a pending review.
+1 -1
View File
@@ -33,7 +33,7 @@ kind: "package-reference"
### 查看已提交的计划
回合结束后,每次提交的计划都会显示在该回合末尾的产物区域,并采用文件产物卡片的样式,包含 Markdown 图标、标题和“打开”操作。在当前浏览器会话中,每份待审计划自动打开一次;关闭后,重新挂载审批组件不会再次展开,新提交会打开其对应计划。历史卡片仅在点击后打开。通过卡片或审批条带上的“查看全文”链接可阅读和复制完整 Markdown。计划标签页显示文档加笔的图标。不同提交保留独立标签页;是否开始实施仍由审批按钮决定。
回合结束后,每次提交的计划都会显示在该回合末尾的产物区域,并采用文件产物卡片的样式,包含 Markdown 图标、标题和“打开”操作。在当前浏览器会话中,每份待审计划自动打开一次;关闭后,重新挂载审批组件不会再次展开,新提交会打开其对应计划。历史卡片仅在点击后打开。通过卡片或审批条带上的“查看全文”链接可阅读完整 Markdown。计划标签页显示纯文本文件图标。不同提交保留独立标签页;是否开始实施仍由审批按钮决定。
没有已记录调用标识的审批也会自动打开。完整正文只保存在标签页的导航内存中,待处理审批卡片可以重新打开它。刷新页面会丢失该正文;失效的预览会提示用户返回待处理审批。
@@ -22,7 +22,7 @@
}
:global(body[data-ds-dark-theme]) .card { --plan-card-fill: var(--dsw-static-neutral-850); --plan-card-hover: var(--dsw-static-neutral-800); }
.card:hover { background: var(--plan-card-hover); }
.card:focus-visible, .iconButton:focus-visible { outline: 2px solid var(--dsw-alias-label-primary); outline-offset: 3px; }
.card:focus-visible { outline: 2px solid var(--dsw-alias-label-primary); outline-offset: 3px; }
.cardIcon { box-sizing: border-box; flex: none; display: grid; place-items: center; width: 40px; height: 40px; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--plan-card-fill); }
.cardDetails { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }
.cardTitle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 500; line-height: 20px; }
@@ -31,11 +31,8 @@
.reviewLink { display: inline-flex; align-items: center; gap: 4px; padding: 0; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; cursor: pointer; }
.reviewLink:hover { color: var(--dsw-alias-label-primary); }
.reviewLink:focus-visible { outline: 2px solid var(--dsw-alias-label-primary); outline-offset: 3px; }
.iconButton { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: 0; border-radius: 6px; background: transparent; color: inherit; cursor: pointer; }
.iconButton:hover { background: var(--dsw-alias-interactive-bg-hover); }
.preview { position: relative; height: 100%; overflow: auto; padding: 20px 24px 40px; box-sizing: border-box; }
.toolbar { display: flex; justify-content: flex-end; margin-bottom: 8px; }
.document { color: var(--dsw-alias-label-primary); font-size: 14px; line-height: 1.75; overflow-wrap: anywhere; }
.message { padding: 24px; color: var(--dsw-alias-label-secondary); font-size: 14px; }
.titleIcon { display: inline-flex; flex: none; }
.titleIcon { flex: none; }
@media (max-width: 767px) { .preview { padding: 16px 18px 32px; } }
@@ -1,6 +1,6 @@
/** Read-only Markdown viewer for logged plans and temporary review documents. */
import { useMemo } from 'react'
import { IconCopyOutlineRegular, IconPlanOutlineRegular, MarkdownText, writeClipboard } from '@deepseek-ai/dsh-client-ui-primitives'
import { FileTypeIcon, MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
import type {} from './plan-resource.ts'
@@ -11,7 +11,7 @@ import css from './PlanPreview.module.css'
type PlanPreviewProps = PropsRuntime<'sidebar.right.pane.tab'> & PropsLocale<'plan'>
/**
* Render the submitted plan with its complete Markdown and a copy action.
* Render the submitted plan with its complete Markdown.
* @param props - Framework-bound tab identity, resource, and copy.
* @returns the plan document or a localized loading/failure state.
*/
@@ -34,17 +34,15 @@ export function PlanPreview({ useTabInfo, useResource, t }: PlanPreviewProps) {
)
return (
<section className={css.preview} data-plan-preview={'callId' in plan ? plan.callId : tab.tab.navigation.address} aria-label={plan.title}>
<div className={css.toolbar}><button type="button" className={css.iconButton} aria-label={t('copy')}
onClick={() => { void writeClipboard(plan.markdown) }}><IconCopyOutlineRegular /></button></div>
<div className={css.document}><MarkdownText text={plan.markdown} labels={labels} /></div>
</section>
)
}
/**
* Display a plan icon and the heading in its tab after resource recovery.
* Display a plain file icon and the heading in its tab after resource recovery.
* @param props - Framework-bound tab identity and resource reader.
* @returns a decorative plan icon followed by the recovered title or initial localized label.
* @returns a decorative file icon followed by the recovered title or initial localized label.
*/
export function PlanTitle({ useTabInfo, useResource }: PropsRuntime<'sidebar.right.pane.tab.title'>) {
const tab = useTabInfo()
@@ -52,5 +50,5 @@ export function PlanTitle({ useTabInfo, useResource }: PropsRuntime<'sidebar.rig
const params = tab.tab.navigation.params
const plan = isReviewPreviewAddress(tab.tab.navigation.address)
? (params !== undefined && 'planReview' in params ? params.planReview : undefined) : resource.value
return <><span className={css.titleIcon} aria-hidden="true"><IconPlanOutlineRegular size={16} /></span>{plan?.title ?? tab.tab.title}</>
return <><FileTypeIcon kind="other" size={16} className={css.titleIcon} />{plan?.title ?? tab.tab.title}</>
}
@@ -223,22 +223,10 @@ describe('plan entry points and document', () => {
render(<PlanTitle {...props as unknown as Parameters<typeof PlanTitle>[0]} />)
expect(screen.getByText(plan.title)).toBeTruthy()
})
it('copies the complete Markdown and keeps a tab label while history loads', async () => {
const writeText = vi.fn().mockResolvedValue(undefined)
const clipboard = Object.getOwnPropertyDescriptor(navigator, 'clipboard')
Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText } })
try {
const props = { t, useTabInfo: () => ({ tab: { title: 'Plan', navigation: { address: planAddress(target) } } }), useResource: () => ({ status: 'live', value: plan }) }
const view = render(<PlanPreview {...props as unknown as Parameters<typeof PlanPreview>[0]} />)
fireEvent.click(screen.getByRole('button', { name: 'Copy' }))
await Promise.resolve()
expect(writeText).toHaveBeenCalledWith(markdown)
view.rerender(<PlanTitle {...{ ...props, useResource: () => ({ status: 'loading' }) } as unknown as Parameters<typeof PlanTitle>[0]} />)
expect(screen.getByText('Plan')).toBeTruthy()
} finally {
if (clipboard === undefined) Reflect.deleteProperty(navigator, 'clipboard')
else Object.defineProperty(navigator, 'clipboard', clipboard)
}
it('keeps a tab label while history loads', () => {
const props = { useTabInfo: () => ({ tab: { title: 'Plan', navigation: { address: planAddress(target) } } }), useResource: () => ({ status: 'loading' }) }
render(<PlanTitle {...props as unknown as Parameters<typeof PlanTitle>[0]} />)
expect(screen.getByText('Plan')).toBeTruthy()
})
it.each([en, zh])('localizes plan failures and unavailable providers', (dictionary) => {
const props = { t: makeTranslate(dictionary, commonEn),
@@ -1,5 +1,4 @@
- 'region "Add `--greeting` flag to CLI"':
- button "Copy"
- heading "Add --greeting flag to CLI" [level=1]:
- text: Add
- code: "--greeting"