feat(sidebar): show compact timestamp in recent activity rows

The sidebar's recent activity list (SidebarActivitySections) rendered its
session rows without an inline timestamp on web/desktop — the compact
relative label only appeared in the hover tooltip and on touch runtimes.

Render the existing i18n-backed formatSessionCompactDateLabel inline in
the recent rows' metadata slot, alongside the goal/branch glyphs, for
web/desktop too. It keeps the same hover-fade as the other metadata, so
the hover-revealed row actions never overlap it, and the full date
stays available in the row tooltip.

No new strings: the label reuses common.relative.* keys.

Fixes #2560
This commit is contained in:
Serhii Dziupin
2026-08-05 13:43:04 +03:00
parent 34c221b07f
commit 81e8ee7c33
2 changed files with 54 additions and 1 deletions
@@ -0,0 +1,41 @@
import { describe, expect, test } from 'bun:test';
import { readFileSync } from 'node:fs';
const source = readFileSync(new URL('./SessionNodeItem.tsx', import.meta.url), 'utf8');
describe('SessionNodeItem recent-activity timestamp', () => {
test('the recent activity rows render the compact timestamp in the inline metadata slot', () => {
// The right-slot guard must open for recent rows even when no activity,
// goal glyph, or branch marker is present.
const guard = source.indexOf("showActivityDuration || sessionGoalGlyph || showInlineBranchMarker || renderContext === 'recent'");
expect(guard).toBeGreaterThan(-1);
// The recent-only block sits inside that slot…
const guardOpen = source.indexOf("{renderContext === 'recent' ? (", guard);
expect(guardOpen).toBeGreaterThan(guard);
// …and the compact label rendered there is the first one after it.
const label = source.indexOf('{sessionCompactUpdatedLabel}', guardOpen);
expect(label).toBeGreaterThan(guardOpen);
// The only later occurrence is the pre-existing row tooltip (which shows
// the full date), not a second inline render.
const tooltipLabel = source.indexOf('{sessionCompactUpdatedLabel}', label + 1);
expect(tooltipLabel).toBeGreaterThan(label);
expect(source.indexOf('title={sessionUpdatedLabel}', tooltipLabel - 80)).toBeGreaterThan(-1);
});
test('the timestamp shares the hover-fade of the other metadata so revealed actions never overlap it', () => {
const guard = source.indexOf("showActivityDuration || sessionGoalGlyph || showInlineBranchMarker || renderContext === 'recent'");
// The slot content fades out while the row is hovered (hideOnHoverClass)
// and while the row menu is open — the same span that now carries the
// recent timestamp.
const hideOnHover = source.indexOf('hideOnHoverClass', guard);
expect(hideOnHover).toBeGreaterThan(guard);
expect(hideOnHover).toBeLessThan(source.indexOf("{renderContext === 'recent' ? (", guard));
});
test('the compact label uses the existing i18n-backed relative time helper', () => {
// formatSessionCompactDateLabel (already used by touch runtimes and the
// row tooltip) is the source of the label — no new formatting code.
expect(source.indexOf('const sessionCompactUpdatedLabel = formatSessionCompactDateLabel(sessionTimestamp);')).toBeGreaterThan(-1);
expect(source.indexOf('{sessionCompactUpdatedLabel}')).toBeGreaterThan(-1);
});
});
@@ -1257,7 +1257,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
</>
)}
</span>
) : (showActivityDuration || sessionGoalGlyph || showInlineBranchMarker) ? (
) : (showActivityDuration || sessionGoalGlyph || showInlineBranchMarker || renderContext === 'recent') ? (
<div className="relative ml-1 flex h-4 flex-shrink-0 items-center justify-end">
<span className={cn(
'inline-flex items-center gap-1 whitespace-nowrap text-right transition-opacity duration-150',
@@ -1281,6 +1281,18 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
style={prIconColor ? { color: prIconColor } : undefined}
/>
) : null}
{/* The recent activity list shows its compact
timestamp inline (touch runtimes already get
it through the alwaysShowActions branch);
it shares the slot with the goal/branch
metadata and hides on hover exactly like
them, so the revealed row actions never
overlap it. */}
{renderContext === 'recent' ? (
<span className="flex-shrink-0 text-[0.72rem] leading-none text-muted-foreground/75 tabular-nums">
{sessionCompactUpdatedLabel}
</span>
) : null}
</>
)}
</span>