From 0cd9762bad1dbe32dd4a2c8c261bc26efefb6f9a Mon Sep 17 00:00:00 2001 From: bowenliang123 Date: Sat, 17 Oct 2026 12:47:42 +0800 Subject: [PATCH] feat(client): add a by-name sort to File Activity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sort toggle ordered rows by counts, recency, and path; the row's own last segment — the bold name a reader scans for — had no order of its own, so same-named files in different directories never grouped. Name leads on the displayed last segment (either separator style, via the shared leafNameOf) and the full path breaks ties between rows that share a name. The Preferences picker, the host Config union, the shared DefaultFileSort vocabulary, and both dictionaries carry the fourth option; glyphOf now reuses the same leaf helper instead of its own split. --- src/client/components/fileCard.tsx | 14 ++++++-- src/client/components/settingsCard.tsx | 1 + src/client/fileActivity.ts | 9 ++++-- src/client/i18nEn.ts | 3 +- src/client/i18nZh.ts | 3 +- src/client/settings.ts | 2 +- src/host/config.ts | 2 +- src/shared/types.ts | 2 +- tests/client/components/fileCard.spec.ts | 34 ++++++++++++++++++-- tests/client/components/settingsCard.spec.ts | 5 +-- tests/client/fileActivity.spec.ts | 4 +++ tests/client/settings.spec.ts | 4 +-- 12 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/client/components/fileCard.tsx b/src/client/components/fileCard.tsx index 8b45846..d9d6223 100644 --- a/src/client/components/fileCard.tsx +++ b/src/client/components/fileCard.tsx @@ -1,5 +1,5 @@ import { memo, useEffect, useState, type ChangeEvent, type ComponentType, type MouseEvent, type ReactElement } from 'react' -import { absPathOf, displayPathOf, glyphOf } from '../fileActivity' +import { absPathOf, baseNameOf, displayPathOf, glyphOf } from '../fileActivity' import type { FileActivity, FileEntry, FileOp, FileOpKind } from '../fileActivity' import type { ContextSettings, DefaultFileSort } from '../settings' import type { DetailState } from '../timelineSource' @@ -74,13 +74,21 @@ export function makeFileCard(kit: ViewKit, settings: ContextSettings): Component // Count sort magnitude: the selected kind's own ops under a kind chip, else the file's overall op count. const countOf = (e: FileEntry): number => filter === 'read' ? e.reads : filter === 'write' ? e.writes : filter === 'search' ? e.searches : e.ops.length + // Name sort: the displayed last segment leads, the full path breaks ties between same-named files. + const byName = (a: FileEntry, b: FileEntry): number => { + const an = baseNameOf(displayOf(a)) + const bn = baseNameOf(displayOf(b)) + return an !== bn ? (an < bn ? -1 : 1) : (a.path < b.path ? -1 : 1) + } const shown = activity.entries .filter(e => matches(e, filter) && (q === '' || displayOf(e).toLowerCase().includes(q))) .sort((a, b) => sort === 'count' ? (countOf(b) - countOf(a)) || (b.ops[0].seq - a.ops[0].seq) : sort === 'latest' ? b.ops[0].seq - a.ops[0].seq - : (a.path < b.path ? -1 : 1)) + : sort === 'name' + ? byName(a, b) + : (a.path < b.path ? -1 : 1)) const chips: { key: FileFilter; files: number; ops: number }[] = [ { @@ -165,7 +173,7 @@ export function makeFileCard(kit: ViewKit, settings: ContextSettings): Component ) : null} - {(['count', 'latest', 'path'] as const).map(k => ( + {(['count', 'latest', 'path', 'name'] as const).map(k => (