fix(ui): show project names exactly as the folder is named

Auto-derived project labels were title-cased, turning .ssh into .Ssh and
opencode-claude into Opencode Claude. Show the folder name verbatim in the
sidebar, window title, settings selector and notification templates, and
migrate persisted legacy labels back to the folder name (manual renames
are preserved).
This commit is contained in:
Bohdan Triapitsyn
2026-08-15 10:16:31 +03:00
parent 8c91c82b62
commit 9032dfa5c0
5 changed files with 20 additions and 17 deletions
@@ -12,9 +12,7 @@ import { isVSCodeRuntime } from '@/lib/desktop';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
const formatProjectLabel = (label: string): string => {
return label.replace(/[-_]/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
};
const formatProjectLabel = (label: string): string => label.trim();
export const SettingsProjectSelector: React.FC<{ className?: string }> = ({ className }) => {
const { t } = useI18n();
@@ -156,11 +156,8 @@ export const resolveArchivedFolderName = (session: Session, projectRoot: string
return segments[segments.length - 1] ?? 'unassigned';
};
export const formatProjectLabel = (label: string): string => {
return label
.replace(/[-_]/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase());
};
// Folder names are shown exactly as they are on disk — no title-casing.
export const formatProjectLabel = (label: string): string => label.trim();
export const renderHighlightedText = (text: string, query: string): React.ReactNode => {
if (!query) {
+1 -3
View File
@@ -7,9 +7,7 @@ import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
const APP_TITLE = 'OpenChamber';
const formatProjectLabel = (label: string): string => {
return label.replace(/[-_]/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
};
const formatProjectLabel = (label: string): string => label.trim();
const getProjectNameFromPath = (path: string): string => {
const normalized = path.replace(/\\/g, '/').replace(/\/+$/, '');
+14 -3
View File
@@ -166,14 +166,22 @@ const normalizeProjectPath = (value: string): string => {
return normalized.length > 1 ? normalized.replace(/\/+$/, '') : normalized;
};
// Folder names are shown verbatim: title-casing them turned `.ssh` into `.Ssh`
// and made every project look like a name the user never chose.
const deriveProjectLabel = (path: string): string => {
const normalized = normalizeProjectPath(path);
if (!normalized || normalized === '/') {
return 'Root';
}
const segments = normalized.split('/').filter(Boolean);
const raw = segments[segments.length - 1] || normalized;
return raw.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
return segments[segments.length - 1] || normalized;
};
// Labels auto-derived by older versions were title-cased and persisted. Drop
// them back to the folder name; labels the user typed themselves are kept.
const legacyAutoProjectLabel = (path: string): string => {
const derived = deriveProjectLabel(path);
return derived.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
};
const sanitizeProjectIconImage = (value: unknown): ProjectEntry['iconImage'] | undefined => {
@@ -261,7 +269,10 @@ const sanitizeProjects = (value: unknown): ProjectEntry[] => {
};
if (typeof candidate.label === 'string' && candidate.label.trim().length > 0) {
project.label = candidate.label.trim();
const storedLabel = candidate.label.trim();
project.label = storedLabel === legacyAutoProjectLabel(normalizedPath)
? deriveProjectLabel(normalizedPath)
: storedLabel;
}
if (typeof candidate.icon === 'string' && candidate.icon.trim().length > 0) {
project.icon = candidate.icon.trim();
@@ -27,9 +27,8 @@ export const createNotificationTemplateRuntime = (deps) => {
const formatProjectLabel = (label) => {
if (!label || typeof label !== 'string') return '';
return label
.replace(/[-_]/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase());
// Folder names are shown exactly as they are on disk — no title-casing.
return label.trim();
};
const resolveNotificationTemplate = (template, variables) => {