Files
paseo/packages/app/src/components/sidebar/sidebar-projection.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

109 lines
3.9 KiB
TypeScript

import type { TFunction } from "i18next";
import { buildStatusGroups } from "@/hooks/sidebar-status-view-model";
import {
splitPinnedSidebarGroups,
type PinnedSidebarGroups,
type PinnedSidebarKeys,
} from "@/hooks/use-sidebar-pins";
import type {
SidebarProjectEntry,
SidebarWorkspaceEntry,
} from "@/hooks/use-sidebar-workspaces-list";
import type { SidebarGroupMode } from "@/stores/sidebar-view-store";
import {
resolveSidebarProjectIconTargets,
type SidebarProjectIconTarget,
} from "@/utils/sidebar-project-row-model";
import {
buildSidebarShortcutSections,
type SidebarShortcutModel,
type SidebarShortcutSection,
} from "@/utils/sidebar-shortcuts";
import { statusWorkspaceGroups, type SidebarWorkspaceGroup } from "./sidebar-labels";
export interface SidebarProjection {
pinnedGroups: PinnedSidebarGroups;
workspaceGroups: SidebarWorkspaceGroup[];
/**
* The project icons this projection needs fetched, keyed by `projectViewKey` — one per project,
* whatever the mode groups by. It sits here rather than beside `useProjectIcons` in the list
* because it is the same `projects` the rows above are projected from: a mode that renders a
* row can only ever ask for an icon this list already covers. It used to be derived in the
* list, under a `groupMode === "status"` gate written when status was the only mode that put
* icons on rows.
*/
projectIconTargets: SidebarProjectIconTarget[];
shortcutModel: SidebarShortcutModel;
}
export interface SidebarProjectionInput {
projects: SidebarProjectEntry[];
pinnedKeys: PinnedSidebarKeys;
pinnedWorkspaceOrder: string[];
workspaceEntriesByKey: ReadonlyMap<string, SidebarWorkspaceEntry>;
projectNamesByViewKey: Map<string, string>;
groupMode: SidebarGroupMode;
pinnedCollapsed: boolean;
collapsedProjectKeys: ReadonlySet<string>;
collapsedWorkspaceGroupKeys: ReadonlySet<string>;
t: TFunction;
}
export function buildSidebarProjection(input: SidebarProjectionInput): SidebarProjection {
const pinnedGroups = splitPinnedSidebarGroups({
projects: input.projects,
keys: input.pinnedKeys,
pinnedWorkspaceOrder: input.pinnedWorkspaceOrder,
});
const pinnedWorkspaceKeys = new Set(input.pinnedKeys.pinnedWorkspaceKeys);
const unpinnedWorkspaces = Array.from(input.workspaceEntriesByKey.values()).filter(
(workspace) => !pinnedWorkspaceKeys.has(workspace.workspaceKey),
);
// One switch decides both what the list groups by and what the keyboard shortcuts walk, so the
// two cannot disagree and a new grouping mode is a compile error here rather than a silent
// fall-through to the project rows.
const workspaceGroups = buildWorkspaceGroups(input, unpinnedWorkspaces);
const sections: SidebarShortcutSection[] = [];
if (!input.pinnedCollapsed) {
sections.push({ workspaces: pinnedGroups.pinnedChats });
}
if (input.groupMode === "project") {
sections.push(
...pinnedGroups.unpinnedProjects.map((project) => ({
workspaces: project.workspaces,
collapsed: input.collapsedProjectKeys.has(project.viewKey),
})),
);
} else {
sections.push(
...workspaceGroups.map((group) => ({
workspaces: group.rows,
collapsed: input.collapsedWorkspaceGroupKeys.has(group.key),
})),
);
}
return {
pinnedGroups,
workspaceGroups,
projectIconTargets: resolveSidebarProjectIconTargets(input.projects),
shortcutModel: buildSidebarShortcutSections({ sections }),
};
}
/** Project mode keeps its project headers and groups nothing; status mode groups the rows. */
function buildWorkspaceGroups(
input: SidebarProjectionInput,
unpinnedWorkspaces: SidebarWorkspaceEntry[],
): SidebarWorkspaceGroup[] {
switch (input.groupMode) {
case "project":
return [];
case "status":
return statusWorkspaceGroups(
buildStatusGroups(unpinnedWorkspaces, input.projectNamesByViewKey, input.t),
);
}
}