feat(client): drop the collapsed file row's error dot

Failures now surface only on the expanded op rows. The collapsed row's
aggregate tally added little on its own (its edit ops already flag the failing
tool call), so FileEntry.errs — which existed only for that dot — and the
files.errs strings go with it; each op keeps its own err flag for the expanded
dots.
This commit is contained in:
bowenliang123 committed 2026-10-19 10:39:53 +08:00
1 parent abce91a6a6
commit a3f572244b
7 files changed
+7 -16

No files matched your search

-1
View File
@@ -319,7 +319,6 @@ export function makeFileCard(kit: ViewKit, settings: ContextSettings): Component
<span className="lc-fa-badge lc-fa-b-search" title={t('files.kind.search')}><i />{fmt(e.searches)}</span>
) : null}
{e.added + e.removed > 0 ? <DeltaPair added={e.added} removed={e.removed} /> : null}
{e.errs > 0 ? <span className="lc-br-err-dot" title={t('files.errs', { n: e.errs })} /> : null}
{e.ops[0].time !== undefined
? <span className="lc-fa-time">{fmtTime(e.ops[0].time)}</span>
: null}
-3
View File
@@ -28,7 +28,6 @@ export interface FileEntry {
searches: number
added: number
removed: number
errs: number
ops: FileOp[]
/** Set when `path` is a pathless search's PATTERN, not a file path — display must not relativize it. */
pattern?: true
@@ -243,7 +242,6 @@ function aggregateOps(ops: readonly FileOp[], before: number | null): FileActivi
searches: 0,
added: 0,
removed: 0,
errs: 0,
ops: [],
...(op.pattern === true ? { pattern: true as const } : {}),
}
@@ -254,7 +252,6 @@ function aggregateOps(ops: readonly FileOp[], before: number | null): FileActivi
else entry.searches++
entry.added += op.added
entry.removed += op.removed
if (op.err) entry.errs++
entry.ops.push(op)
}
const entries = [...byPath.values()]
-1
View File
@@ -173,7 +173,6 @@ export const DICT_EN = {
'files.open': 'Open on your system',
'files.preview': 'Preview in the sidebar',
'files.readEst': 'Lines read, estimated from the limit argument',
'files.errs': '{n} failed',
'files.form.image': 'Image',
'files.form.dir': 'Directory',
'files.form.text': 'File',
-1
View File
@@ -171,7 +171,6 @@ export const DICT_ZH = {
'files.open': '在系统中打开',
'files.preview': '在侧边栏预览',
'files.readEst': '按调用参数 limit 估算的读取行数',
'files.errs': '{n} 次失败',
'files.form.image': '图片',
'files.form.dir': '目录',
'files.form.text': '文件',
+3 -3
View File
@@ -18,7 +18,7 @@ function fileOp(seq: number, kind: FileOp['kind'], tool: string, over: Partial<F
}
function entry(path: string, over: Partial<FileEntry>): FileEntry {
return { path, form: 'text', reads: 0, writes: 0, searches: 0, added: 0, removed: 0, errs: 0, ops: [], ...over }
return { path, form: 'text', reads: 0, writes: 0, searches: 0, added: 0, removed: 0, ops: [], ...over }
}
function richActivity(over: Partial<FileActivity> = {}): FileActivity {
@@ -29,7 +29,6 @@ function richActivity(over: Partial<FileActivity> = {}): FileActivity {
writes: 2,
added: 2,
removed: 3,
errs: 1,
ops: [
fileOp(7, 'read', 'read'),
fileOp(5, 'write', 'edit', { added: 2, removed: 1 }),
@@ -129,7 +128,8 @@ describe('FileCard — header, rows, and filters', () => {
assert.ok(aRow.querySelector('.lc-fa-path b')?.textContent === 'a.ts')
assert.equal(queryAll(aRow, '.lc-fa-badge').length, 2) // read + write
assert.ok(text(aRow).includes('+2') && text(aRow).includes('−3'))
assert.ok(aRow.querySelector('.lc-br-err-dot') !== null)
// Failures surface on the expanded op rows only: the collapsed row keeps no error dot.
assert.equal(queryAll(aRow, '.lc-br-err-dot').length, 0)
assert.ok(aRow.title === '/src/a.ts')
const dirRow = rows.find(r => r.title === '/src/')
assert.ok(dirRow !== undefined)
@@ -75,7 +75,7 @@ afterAll(() => {
function entry(index: number): FileEntry {
const op: FileOp = { seq: index, kind: 'read', tool: 'read', time: T0 + index, err: false, added: 0, removed: 0, path: '' }
return { path: `/src/file-${String(index).padStart(3, '0')}.ts`, form: 'text', reads: 1, writes: 0, searches: 0, added: 0, removed: 0, errs: 0, ops: [op] }
return { path: `/src/file-${String(index).padStart(3, '0')}.ts`, form: 'text', reads: 1, writes: 0, searches: 0, added: 0, removed: 0, ops: [op] }
}
function activityOf(count: number): FileActivity {
+3 -6
View File
@@ -108,7 +108,6 @@ describe('activityOf', () => {
assert.equal(file.searches, 0)
assert.equal(file.added, 2) // edit new_string: 'x\nq'
assert.equal(file.removed, 3) // edit old_string: 'x\ny\nz'
assert.equal(file.errs, 0)
// Ops newest first inside the entry.
assert.deepEqual(file.ops.map(o => o.seq), [7, 5, 3])
assert.equal(file.ops[0].kind, 'read')
@@ -148,16 +147,14 @@ describe('activityOf', () => {
assert.deepEqual(a.totals.image, { files: 2, ops: 2 })
})
test('counts failures from the fold stamp or the snapshot error flag', () => {
test('stamps the error flag from the fold or the snapshot', () => {
const a = run([
op(3, 'read', { file_path: '/a.ts' }, { err: true }),
op(5, 'read', { file_path: '/a.ts' }, {}, { isError: true }),
op(7, 'read', { file_path: '/b.ts' }),
])
const file = a.entries[1]
assert.equal(file.errs, 2)
assert.equal(file.ops[0].err, true)
assert.equal(a.entries[0].errs, 0)
assert.deepEqual(a.entries[1].ops.map(o => o.err), [true, true])
assert.equal(a.entries[0].ops[0].err, false)
})
test('carries the archive removal stamp onto the op for locate targeting', () => {