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:
@@ -12,9 +12,7 @@ import { isVSCodeRuntime } from '@/lib/desktop';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useI18n } from '@/lib/i18n';
|
import { useI18n } from '@/lib/i18n';
|
||||||
|
|
||||||
const formatProjectLabel = (label: string): string => {
|
const formatProjectLabel = (label: string): string => label.trim();
|
||||||
return label.replace(/[-_]/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SettingsProjectSelector: React.FC<{ className?: string }> = ({ className }) => {
|
export const SettingsProjectSelector: React.FC<{ className?: string }> = ({ className }) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|||||||
@@ -156,11 +156,8 @@ export const resolveArchivedFolderName = (session: Session, projectRoot: string
|
|||||||
return segments[segments.length - 1] ?? 'unassigned';
|
return segments[segments.length - 1] ?? 'unassigned';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatProjectLabel = (label: string): string => {
|
// Folder names are shown exactly as they are on disk — no title-casing.
|
||||||
return label
|
export const formatProjectLabel = (label: string): string => label.trim();
|
||||||
.replace(/[-_]/g, ' ')
|
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renderHighlightedText = (text: string, query: string): React.ReactNode => {
|
export const renderHighlightedText = (text: string, query: string): React.ReactNode => {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import { getRuntimeApiBaseUrl } from '@/lib/runtime-switch';
|
|||||||
|
|
||||||
const APP_TITLE = 'OpenChamber';
|
const APP_TITLE = 'OpenChamber';
|
||||||
|
|
||||||
const formatProjectLabel = (label: string): string => {
|
const formatProjectLabel = (label: string): string => label.trim();
|
||||||
return label.replace(/[-_]/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProjectNameFromPath = (path: string): string => {
|
const getProjectNameFromPath = (path: string): string => {
|
||||||
const normalized = path.replace(/\\/g, '/').replace(/\/+$/, '');
|
const normalized = path.replace(/\\/g, '/').replace(/\/+$/, '');
|
||||||
|
|||||||
@@ -166,14 +166,22 @@ const normalizeProjectPath = (value: string): string => {
|
|||||||
return normalized.length > 1 ? normalized.replace(/\/+$/, '') : normalized;
|
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 deriveProjectLabel = (path: string): string => {
|
||||||
const normalized = normalizeProjectPath(path);
|
const normalized = normalizeProjectPath(path);
|
||||||
if (!normalized || normalized === '/') {
|
if (!normalized || normalized === '/') {
|
||||||
return 'Root';
|
return 'Root';
|
||||||
}
|
}
|
||||||
const segments = normalized.split('/').filter(Boolean);
|
const segments = normalized.split('/').filter(Boolean);
|
||||||
const raw = segments[segments.length - 1] || normalized;
|
return segments[segments.length - 1] || normalized;
|
||||||
return raw.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
};
|
||||||
|
|
||||||
|
// 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 => {
|
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) {
|
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) {
|
if (typeof candidate.icon === 'string' && candidate.icon.trim().length > 0) {
|
||||||
project.icon = candidate.icon.trim();
|
project.icon = candidate.icon.trim();
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ export const createNotificationTemplateRuntime = (deps) => {
|
|||||||
|
|
||||||
const formatProjectLabel = (label) => {
|
const formatProjectLabel = (label) => {
|
||||||
if (!label || typeof label !== 'string') return '';
|
if (!label || typeof label !== 'string') return '';
|
||||||
return label
|
// Folder names are shown exactly as they are on disk — no title-casing.
|
||||||
.replace(/[-_]/g, ' ')
|
return label.trim();
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveNotificationTemplate = (template, variables) => {
|
const resolveNotificationTemplate = (template, variables) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user