fix(web): keep line counts visible after a changed-files row opens

A completed native open no longer replaces the row's counts with a lasting
acknowledgement; only pending and failed opens do, for rows and the folder
header alike.
This commit is contained in:
creatixchu committed 2026-09-14 11:53:19 +08:00
1 parent 8ea92ce9b5
commit 7f5f4e778a
6 files changed
+17 -14

No files matched your search

@@ -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-deliverables/README.md
README.md: 0effc0f608365c56dc79de77abcf7d0458f2f893
README.zh.md: b338fc9cb590f002ecd99f1af045ef96f195ff35
README.md: c0442288a3041847583053ebcff875ec7c0ec904
README.zh.md: 70ad1dfffa48d6850e0e17bf10d83929d1e59c4f
+1 -1
View File
@@ -36,7 +36,7 @@ The `present` tool row shows running, delivered, failed, or interrupted status;
### The changed-files card
The card renders the turn's latest `workspace/changes` event: its header names the complete changed-file count with the summed added and deleted lines, and each row shows one file's display path with its own counts, or “binary” for a binary file. Rows appear in the recorded display order, so repository files above the working directory and files outside it sort first. Three rows show before a fold; a control below reveals every recorded file and, once expanded, collapses the list again from the bottom. With a Host desktop available, the header opens the deepest workspace folder containing the listed files in the file manager and each row opens its file in the default application, both through authenticated Host actions that share the delivery cards' pending, acknowledged, and retryable-error states in place of the counts. Without a desktop the header is a plain label and rows preview their files in the right Sidebar. The first file section starts 20px below the closing prose, a following explicit-delivery section starts 16px below the card, and the action footer starts 20px below the last file section. Final file deliveries still require `present`.
The card renders the turn's latest `workspace/changes` event: its header names the complete changed-file count with the summed added and deleted lines, and each row shows one file's display path with its own counts, or “binary” for a binary file. Rows appear in the recorded display order, so repository files above the working directory and files outside it sort first. Three rows show before a fold; a control below reveals every recorded file and, once expanded, collapses the list again from the bottom. With a Host desktop available, the header opens the deepest workspace folder containing the listed files in the file manager and each row opens its file in the default application, both through authenticated Host actions that show the delivery cards' pending and retryable-error states in place of the counts; a completed open shows the counts again. Without a desktop the header is a plain label and rows preview their files in the right Sidebar. The first file section starts 20px below the closing prose, a following explicit-delivery section starts 16px below the card, and the action footer starts 20px below the last file section. Final file deliveries still require `present`.
### Inline-code links
+1 -1
View File
@@ -36,7 +36,7 @@ Web 的 `standard`、`ptc` 与 `cordis` preset 提供 `present` 用于声明交
### 改动文件卡片
卡片渲染本轮最新的 `workspace/changes` 事件:标题给出改动文件总数与增删行数合计,每一行显示一个文件的展示路径及其增删行数,二进制文件显示“二进制”。行按记录的展示顺序排列,因此仓库内位于工作目录之上的文件与工作区外的文件排在最前。折叠前显示三行;下方的控件展开全部记录文件,展开后同一位置的控件从底部收起列表。Host 有桌面时,标题在文件管理器中打开包含所列文件的最深工作区文件夹,每一行用默认应用打开该文件,两者都通过经过认证的 Host 操作完成,并在计数位置复用交付卡片的等待、已确认与可重试错误状态。没有桌面时标题是普通标签,行改为在右侧 Sidebar 中预览文件。首个文件区块位于收尾正文下方 20px,后续显式交付区块位于卡片下方 16px,操作页脚位于最后一个文件区块下方 20px。最终文件交付仍需调用 `present`。
卡片渲染本轮最新的 `workspace/changes` 事件:标题给出改动文件总数与增删行数合计,每一行显示一个文件的展示路径及其增删行数,二进制文件显示“二进制”。行按记录的展示顺序排列,因此仓库内位于工作目录之上的文件与工作区外的文件排在最前。折叠前显示三行;下方的控件展开全部记录文件,展开后同一位置的控件从底部收起列表。Host 有桌面时,标题在文件管理器中打开包含所列文件的最深工作区文件夹,每一行用默认应用打开该文件,两者都通过经过认证的 Host 操作完成,并在计数位置显示交付卡片的等待与可重试错误状态;打开成功后重新显示计数。没有桌面时标题是普通标签,行改为在右侧 Sidebar 中预览文件。首个文件区块位于收尾正文下方 20px,后续显式交付区块位于卡片下方 16px,操作页脚位于最后一个文件区块下方 20px。最终文件交付仍需调用 `present`。
### 行内代码链接
@@ -18,14 +18,17 @@ const COLLAPSED_ROWS = 3
const GROUPED = new Intl.NumberFormat('en-US')
/** Row copy for an open gesture; the changed-files card never reveals, so only open phases occur. */
function rowStatus(phase: PresentedOpenPhase | undefined): { key: 'presented.opening' | 'presented.opened' | 'presented.error' | 'presented.nativeUnavailable'; error: boolean } | undefined {
/**
* Row copy for an open gesture while it is pending or failed; a completed open
* shows the line counts again, which are the row's primary information. The
* changed-files card never reveals, so only open phases occur.
*/
function rowStatus(phase: PresentedOpenPhase | undefined): { key: 'presented.opening' | 'presented.error' | 'presented.nativeUnavailable'; error: boolean } | undefined {
switch (phase) {
case undefined: return undefined
case 'opening': return { key: 'presented.opening', error: false }
case 'error': return { key: 'presented.error', error: true }
case 'nativeUnavailable': return { key: 'presented.nativeUnavailable', error: true }
default: return { key: 'presented.opened', error: false }
default: return undefined
}
}
@@ -62,8 +65,7 @@ export function ChangedFiles({ changes, cwd, sessionId, host, phases, onOpen, op
const sum = totals(changes.files)
const folderPhase = phases[changedFileUrl(sessionId, changes.seq, null)]
const folderStatus = folderPhase === 'opening' ? t('changes.folderOpening')
: folderPhase === 'opened' ? t('changes.folderOpened')
: folderPhase === undefined ? undefined : t('changes.folderError')
: folderPhase === 'error' || folderPhase === 'nativeUnavailable' ? t('changes.folderError') : undefined
const summary = <>
<span className={css.tile}><IconCodeBracketsOutline16 size={18} /></span>
<span className={css.titles}>
@@ -49,7 +49,6 @@ export const zh = {
'changes.collapse': '收起',
'changes.collapseAria': '收起改动文件列表',
'changes.folderOpening': '正在打开文件夹…',
'changes.folderOpened': '已请求打开文件夹',
'changes.folderError': '无法打开文件夹,点击重试',
}
@@ -99,7 +98,6 @@ export const en: Record<DeliverablesKey, string> = {
'changes.collapse': 'Collapse',
'changes.collapseAria': 'Collapse changed files',
'changes.folderOpening': 'Opening folder…',
'changes.folderOpened': 'Requested opening folder',
'changes.folderError': 'Could not open folder. Click to retry.',
}
@@ -534,7 +534,8 @@ describe('ChangedFiles card', () => {
expect((view.getByRole('button', { name: 'Open config/design-token in default app' }) as HTMLButtonElement).disabled).toBe(true)
expect(view.getByText(en['presented.error']).closest('[data-error]')).toBeTruthy()
expect(view.getByText(en['presented.nativeUnavailable'])).toBeTruthy()
expect(view.getByText(en['changes.folderOpened'])).toBeTruthy()
// A completed folder open leaves the summed counts in place.
expect(view.getByText('+1,232')).toBeTruthy()
// A row without a verified Host path previews in the Sidebar instead of retrying the native open.
fireEvent.click(view.getByRole('button', { name: 'Open config/launch-plan.yaml in sidebar' }))
expect(openFile).toHaveBeenCalledWith('config/launch-plan.yaml')
@@ -544,7 +545,9 @@ describe('ChangedFiles card', () => {
const { view: pending } = renderCard(controller)
expect(pending.getByText(en['changes.folderOpening'])).toBeTruthy()
expect((pending.getByRole('button', { name: 'Open the folder containing the changed files' }) as HTMLButtonElement).disabled).toBe(true)
expect(pending.getByText(en['presented.opened'])).toBeTruthy()
// A completed row open shows its counts again rather than a lasting acknowledgement.
expect(pending.queryByText(en['presented.opened'])).toBeNull()
expect(pending.getByText('+42')).toBeTruthy()
pending.unmount()
controller.state.set({ '/api/changes.open?sessionId=child-session&seq=5': 'error' })
const { view: failed } = renderCard(controller)