fix: respect time format preference in UI
Add shared time formatting helpers that apply the Appearance time format preference consistently. Update visible time labels across chat, quota usage, scheduled tasks, tunnels, context details, git views, PR metadata, and passkey settings to use the selected 12-hour, 24-hour, or automatic format. Leave date-only and non-UI formatting untouched so unrelated behavior does not change.
This commit is contained in:
@@ -11,6 +11,8 @@ import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useSessions, useSessionMessageRecords } from '@/sync/sync-context';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { formatDateTimeForPreference } from '@/lib/timeFormat';
|
||||
import { useUIStore, type TimeFormatPreference } from '@/stores/useUIStore';
|
||||
|
||||
type SessionMessage = { info: Message; parts: Part[] };
|
||||
|
||||
@@ -229,9 +231,9 @@ const formatMoney = (value: number): string => {
|
||||
return `$${value.toFixed(2)}`;
|
||||
};
|
||||
|
||||
const formatDateTime = (timestamp: number | null): string => {
|
||||
const formatDateTime = (timestamp: number | null, timeFormatPreference: TimeFormatPreference): string => {
|
||||
if (!timestamp || !Number.isFinite(timestamp)) return '-';
|
||||
return new Date(timestamp).toLocaleString(undefined, {
|
||||
return formatDateTimeForPreference(timestamp, timeFormatPreference, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
@@ -240,9 +242,9 @@ const formatDateTime = (timestamp: number | null): string => {
|
||||
});
|
||||
};
|
||||
|
||||
const formatMessageDateMeta = (timestamp: number | null): string => {
|
||||
const formatMessageDateMeta = (timestamp: number | null, timeFormatPreference: TimeFormatPreference): string => {
|
||||
if (!timestamp || !Number.isFinite(timestamp)) return '-';
|
||||
return new Date(timestamp).toLocaleString(undefined, {
|
||||
return formatDateTimeForPreference(timestamp, timeFormatPreference, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
@@ -273,6 +275,7 @@ const resolveProviderAndModel = (
|
||||
export const ContextPanelContent: React.FC = () => {
|
||||
const { t } = useI18n();
|
||||
const { currentTheme } = useThemeSystem();
|
||||
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
|
||||
const syntaxTheme = React.useMemo(() => generateSyntaxTheme(currentTheme), [currentTheme]);
|
||||
const [expandedRawMessages, setExpandedRawMessages] = React.useState<Record<string, boolean>>({});
|
||||
const [copiedRawMessageId, setCopiedRawMessageId] = React.useState<string | null>(null);
|
||||
@@ -416,7 +419,7 @@ export const ContextPanelContent: React.FC = () => {
|
||||
{viewModel.createdAt && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span>{formatDateTime(viewModel.createdAt)}</span>
|
||||
<span>{formatDateTime(viewModel.createdAt, timeFormatPreference)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -547,7 +550,7 @@ export const ContextPanelContent: React.FC = () => {
|
||||
<span className="typography-ui-label text-foreground shrink-0">{capitalizeRole(role)}</span>
|
||||
<span className="min-w-0 truncate typography-micro text-muted-foreground">{message.info.id}</span>
|
||||
</span>
|
||||
<span className="typography-micro text-muted-foreground shrink-0">{formatMessageDateMeta(messageCreatedAt)}</span>
|
||||
<span className="typography-micro text-muted-foreground shrink-0">{formatMessageDateMeta(messageCreatedAt, timeFormatPreference)}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ import { formatQuotaValueLabel, formatQuotaResetLabel, formatWindowLabel, QUOTA_
|
||||
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
|
||||
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { formatTimeForPreference } from '@/lib/timeFormat';
|
||||
import { eventMatchesShortcut, formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts';
|
||||
import type { TimeFormatPreference } from '@/stores/useUIStore';
|
||||
import {
|
||||
getAllModelFamilies,
|
||||
getDisplayModelName,
|
||||
@@ -356,6 +358,7 @@ type DesktopServicesMenuProps = {
|
||||
remoteUpdateError: string | null;
|
||||
onOpenRemoteUpdate: () => void;
|
||||
showPredValues: boolean;
|
||||
timeFormatPreference: TimeFormatPreference;
|
||||
};
|
||||
|
||||
const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
@@ -391,6 +394,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
remoteUpdateError,
|
||||
onOpenRemoteUpdate,
|
||||
showPredValues,
|
||||
timeFormatPreference,
|
||||
}: DesktopServicesMenuProps) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
@@ -511,7 +515,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
<div className="flex items-center justify-between gap-3 border-b border-[var(--interactive-border)] px-4 py-2.5">
|
||||
<div className="flex min-w-0 items-baseline gap-2">
|
||||
<span className="typography-ui-header font-semibold text-foreground">{t('header.services.rateLimits')}</span>
|
||||
<span className="truncate typography-micro text-muted-foreground">{formatTime(quotaLastUpdated)}</span>
|
||||
<span className="truncate typography-micro text-muted-foreground">{formatTime(quotaLastUpdated, timeFormatPreference)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="h-7 w-[10.5rem]">
|
||||
@@ -572,7 +576,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
|
||||
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
|
||||
: null;
|
||||
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
|
||||
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
|
||||
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted, timeFormatPreference);
|
||||
return (
|
||||
<div key={`${group.providerId}-${label}`} className="flex flex-col gap-1.5">
|
||||
<div className="flex min-w-0 items-center justify-between gap-3">
|
||||
@@ -714,13 +718,10 @@ const formatCompactHeaderLabel = (value: string): string => {
|
||||
return trimmed.length > 12 ? `${trimmed.slice(0, 9).trimEnd()}...` : trimmed;
|
||||
};
|
||||
|
||||
const formatTime = (timestamp: number | null) => {
|
||||
const formatTime = (timestamp: number | null, timeFormatPreference: 'auto' | '12h' | '24h') => {
|
||||
if (!timestamp) return '-';
|
||||
try {
|
||||
return new Date(timestamp).toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
return formatTimeForPreference(timestamp, timeFormatPreference, { fallback: '-' });
|
||||
} catch {
|
||||
return '-';
|
||||
}
|
||||
@@ -791,6 +792,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
const activeMainTab = useUIStore((state) => state.activeMainTab);
|
||||
const setActiveMainTab = useUIStore((state) => state.setActiveMainTab);
|
||||
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
|
||||
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
|
||||
|
||||
const getCurrentModel = useConfigStore((state) => state.getCurrentModel);
|
||||
const runtimeApis = useRuntimeAPIs();
|
||||
@@ -2089,6 +2091,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
remoteUpdateChecking={remoteUpdateChecking}
|
||||
remoteUpdateError={remoteUpdateError}
|
||||
onOpenRemoteUpdate={openRemoteInstanceUpdate}
|
||||
timeFormatPreference={timeFormatPreference}
|
||||
/>
|
||||
<HeaderIconActionButton
|
||||
title={t('header.actions.terminalPanelWithShortcut', { shortcut: shortcutLabel('toggle_terminal') })}
|
||||
@@ -2445,7 +2448,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
<div className="flex flex-col min-w-0 gap-0.5">
|
||||
<span className="typography-ui-header font-semibold text-foreground">{t('header.services.rateLimits')}</span>
|
||||
<span className="truncate typography-micro text-muted-foreground">
|
||||
{formatTime(quotaLastUpdated)}
|
||||
{formatTime(quotaLastUpdated, timeFormatPreference)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
@@ -2533,7 +2536,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
: calculateExpectedUsagePercent(paceInfo.elapsedRatio))
|
||||
: null;
|
||||
const metricLabel = formatQuotaValueLabel(window.valueLabel, displayPercent);
|
||||
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted);
|
||||
const resetLabel = formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted, timeFormatPreference);
|
||||
return (
|
||||
<div key={`${group.providerId}-${label}`} className="flex flex-col gap-1.5">
|
||||
<div className="flex min-w-0 items-center justify-between gap-3">
|
||||
|
||||
@@ -30,19 +30,18 @@ import { formatQuotaValueLabel, formatQuotaResetLabel, formatWindowLabel, QUOTA_
|
||||
import { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore';
|
||||
import { useUpdateStore } from '@/stores/useUpdateStore';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { formatTimeForPreference } from '@/lib/timeFormat';
|
||||
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
|
||||
import type { UsageWindow } from '@/types';
|
||||
import type { SessionContextUsage } from '@/stores/types/sessionTypes';
|
||||
import { useUIStore, type TimeFormatPreference } from '@/stores/useUIStore';
|
||||
|
||||
const SettingsView = lazyWithChunkRecovery(() => import('@/components/views/SettingsView').then(m => ({ default: m.SettingsView })));
|
||||
|
||||
const formatTime = (timestamp: number | null) => {
|
||||
const formatTime = (timestamp: number | null, timeFormatPreference: TimeFormatPreference) => {
|
||||
if (!timestamp) return '-';
|
||||
try {
|
||||
return new Date(timestamp).toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
return formatTimeForPreference(timestamp, timeFormatPreference, { fallback: '-' });
|
||||
} catch {
|
||||
return '-';
|
||||
}
|
||||
@@ -583,6 +582,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
const quotaLastUpdated = useQuotaStore((state) => state.lastUpdated);
|
||||
const quotaDisplayMode = useQuotaStore((state) => state.displayMode);
|
||||
const showPredValues = useQuotaStore((state) => state.showPredValues);
|
||||
const timeFormatPreference = useUIStore((state) => state.timeFormatPreference);
|
||||
const dropdownProviderIds = useQuotaStore((state) => state.dropdownProviderIds);
|
||||
const loadQuotaSettings = useQuotaStore((state) => state.loadSettings);
|
||||
const setQuotaDisplayMode = useQuotaStore((state) => state.setDisplayMode);
|
||||
@@ -839,7 +839,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
</DropdownMenuLabel>
|
||||
</div>
|
||||
<div className="border-b border-[var(--interactive-border)] px-2 pb-2 typography-micro text-muted-foreground text-[10px]">
|
||||
{t('vscodeLayout.quota.lastUpdated', { time: formatTime(quotaLastUpdated) })}
|
||||
{t('vscodeLayout.quota.lastUpdated', { time: formatTime(quotaLastUpdated, timeFormatPreference) })}
|
||||
</div>
|
||||
{!hasRateLimits && (
|
||||
<DropdownMenuItem className="cursor-default" closeOnClick={false}>
|
||||
@@ -899,7 +899,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
</div>
|
||||
)}
|
||||
<span className="flex items-center justify-between typography-micro text-muted-foreground text-[10px]">
|
||||
<span>{formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted)}</span>
|
||||
<span>{formatQuotaResetLabel(window.resetAt, window.resetAfterFormatted ?? window.resetAtFormatted, timeFormatPreference)}</span>
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
Reference in New Issue
Block a user