Files
paseo/packages/app/src/hooks/sidebar-status-view-model.ts
T
paseo-bot[bot] 81a5e5bc61 Show the PR checks summary, sidebar status, and "Worked for" line in the app language (#6196)
* fix(app): translate the PR checks summary, sidebar status labels, and "Worked for" line

These were written in the components, so they stayed English in every
other language. They now come from the translation files, with copy for
all nine supported languages.

Closes #6190

* fix(app): rebuild translated labels when the app language changes

The checks summary, sidebar status groups, and turn footer now take the
translation function, so their memoized labels rebuild on a language
switch. Russian one-check group labels use singular wording.

* test(app): compare compact footer targets at sub-pixel precision

The browser reports the 44pt footer buttons as 43.99999 wide on some
runs, which fails the finger-size check on main as well.

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
2026-10-06 09:19:52 +02:00

96 lines
2.8 KiB
TypeScript

import type { TFunction } from "i18next";
import { STATUS_BUCKET_ORDER } from "@/utils/sidebar-agent-state";
import type { SidebarWorkspaceEntry } from "@/hooks/sidebar-workspaces-view-model";
export type StatusBucket = SidebarWorkspaceEntry["statusBucket"];
export { STATUS_BUCKET_ORDER };
const STATUS_BUCKET_LABEL_KEYS = {
needs_input: "sidebar.statusBucket.needsInput",
failed: "sidebar.statusBucket.failed",
attention: "sidebar.statusBucket.readyToReview",
running: "sidebar.statusBucket.working",
done: "sidebar.statusBucket.done",
} as const satisfies Record<StatusBucket, string>;
export function getStatusBucketLabel(bucket: StatusBucket, t: TFunction): string {
return t(STATUS_BUCKET_LABEL_KEYS[bucket]);
}
export interface StatusGroup {
bucket: StatusBucket;
label: string;
rows: SidebarWorkspaceEntry[];
}
export function buildStatusGroups(
workspaces: SidebarWorkspaceEntry[],
projectNamesByViewKey: Map<string, string>,
t: TFunction,
): StatusGroup[] {
const bucketRows = new Map<StatusBucket, SidebarWorkspaceEntry[]>();
for (const ws of workspaces) {
const bucket: StatusBucket = ws.statusBucket;
let rows = bucketRows.get(bucket);
if (!rows) {
rows = [];
bucketRows.set(bucket, rows);
}
rows.push(ws);
}
const groups: StatusGroup[] = [];
for (const bucket of STATUS_BUCKET_ORDER) {
const rows = bucketRows.get(bucket);
if (!rows || rows.length === 0) continue;
rows.sort((a, b) => compareStatusRows(a, b, projectNamesByViewKey));
groups.push({ bucket, label: getStatusBucketLabel(bucket, t), rows });
}
return groups;
}
function compareStatusRows(
a: SidebarWorkspaceEntry,
b: SidebarWorkspaceEntry,
projectNamesByViewKey: Map<string, string>,
): number {
const aTime = a.statusEnteredAt?.getTime() ?? null;
const bTime = b.statusEnteredAt?.getTime() ?? null;
if (aTime !== null && bTime !== null) {
if (aTime !== bTime) return bTime - aTime;
} else if (aTime !== null) {
return -1;
} else if (bTime !== null) {
return 1;
}
const aProject = projectNamesByViewKey.get(a.projectViewKey) ?? "";
const bProject = projectNamesByViewKey.get(b.projectViewKey) ?? "";
const projectCmp = aProject.localeCompare(bProject);
if (projectCmp !== 0) return projectCmp;
const nameCmp = a.name.localeCompare(b.name);
if (nameCmp !== 0) return nameCmp;
return a.workspaceKey.localeCompare(b.workspaceKey);
}
export function buildStatusShortcutIndex(groups: StatusGroup[]): Map<string, number> {
const index = new Map<string, number>();
let shortcutNumber = 1;
for (const group of groups) {
for (const row of group.rows) {
if (shortcutNumber > 9) return index;
index.set(row.workspaceKey, shortcutNumber);
shortcutNumber += 1;
}
}
return index;
}