diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53dd6ff375..8695024f99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to
### Added
+- ✨(frontend) make the full last-update date available #1215
- ♿️(frontend) restore skip to content link after header redesign #2510
- 🌐(i18n) rename cn_CN to zh_CN, add eo_PL and zh_TW locales #2486
- ✨(backend) conditional email notification in server to server api #2554
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx
new file mode 100644
index 0000000000..2f36fc5474
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx
@@ -0,0 +1,111 @@
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import i18next from 'i18next';
+import { DateTime } from 'luxon';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { Doc, LinkReach, Role } from '@/docs/doc-management';
+import { AppWrapper } from '@/tests/utils';
+
+import { DocHeaderInfo } from '../components/DocHeaderInfo';
+
+vi.mock('@/core', async () => {
+ const actual = await vi.importActual('@/core');
+
+ return {
+ ...actual,
+ useConfig: () => ({ data: {} }),
+ };
+});
+
+const updatedAt = '2026-08-14T14:23:00';
+const doc = {
+ id: 'doc-1',
+ abilities: {
+ partial_update: true,
+ },
+ ancestors_link_reach: LinkReach.RESTRICTED,
+ computed_link_reach: LinkReach.RESTRICTED,
+ deleted_at: null,
+ link_reach: LinkReach.RESTRICTED,
+ nb_accesses_ancestors: 0,
+ nb_accesses_direct: 1,
+ updated_at: updatedAt,
+ user_role: Role.OWNER,
+} as Doc;
+
+describe('', () => {
+ beforeEach(() => {
+ const now = DateTime.now().set({
+ year: 2026,
+ month: 8,
+ day: 14,
+ hour: 14,
+ minute: 28,
+ second: 0,
+ millisecond: 0,
+ });
+ vi.spyOn(DateTime, 'now').mockReturnValue(now);
+ });
+
+ afterEach(async () => {
+ await act(async () => {
+ await i18next.changeLanguage('en');
+ });
+ vi.restoreAllMocks();
+ });
+
+ it('keeps the relative date and exposes the full date on focus', async () => {
+ const user = userEvent.setup();
+
+ render(, { wrapper: AppWrapper });
+
+ const relativeDate = screen.getByText('5 minutes ago');
+ expect(relativeDate).toHaveTextContent('5 minutes ago');
+ expect(relativeDate.tagName).toBe('TIME');
+ expect(relativeDate).toHaveAttribute('datetime', updatedAt);
+ expect(relativeDate).not.toHaveAttribute('role', 'button');
+ expect(relativeDate).toHaveAccessibleName(
+ '5 minutes ago. 08/14/2026, 02:23 PM',
+ );
+
+ await user.tab();
+
+ expect(relativeDate).toHaveFocus();
+ expect(await screen.findByRole('tooltip')).toHaveTextContent(
+ '08/14/2026, 02:23 PM',
+ );
+ });
+
+ it('uses the current locale for the relative and full dates', async () => {
+ const user = userEvent.setup();
+
+ await act(async () => {
+ await i18next.changeLanguage('fr');
+ });
+
+ render(, { wrapper: AppWrapper });
+
+ const relativeDate = screen.getByText('il y a 5 minutes');
+ await user.tab();
+
+ expect(relativeDate).toHaveFocus();
+ expect(await screen.findByRole('tooltip')).toHaveTextContent(
+ '14/08/2026 14:23',
+ );
+ });
+
+ it('exposes the full date on hover', async () => {
+ const user = userEvent.setup();
+
+ render(, { wrapper: AppWrapper });
+
+ const relativeDate = screen.getByText('5 minutes ago');
+ fireEvent.pointerMove(relativeDate, { pointerType: 'mouse' });
+ await user.hover(relativeDate);
+
+ expect(await screen.findByRole('tooltip')).toHaveTextContent(
+ '08/14/2026, 02:23 PM',
+ );
+ });
+});
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx
index c5f7f8d739..390beeb984 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx
@@ -1,4 +1,6 @@
+import { Tooltip } from '@gouvfr-lasuite/cunningham-react';
import { t } from 'i18next';
+import { ComponentPropsWithoutRef, forwardRef } from 'react';
import PublicSVG from '@/assets/icons/ui-kit/public.svg';
import ProtedtedSVG from '@/assets/icons/ui-kit/vpn_lock.svg';
@@ -21,10 +23,11 @@ interface DocHeaderInfoProps {
export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => {
const { transRole } = useTrans();
const { isEditable } = useIsCollaborativeEditable(doc);
- const { relativeDate, calculateDaysLeft } = useDate();
+ const { relativeDate, formatDate, calculateDaysLeft } = useDate();
const { data: config } = useConfig();
const relativeOnly = relativeDate(doc.updated_at);
+ const fullDate = formatDate(doc.updated_at);
const trashbinCutoff = config?.TRASHBIN_CUTOFF_DAYS;
@@ -62,13 +65,38 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => {
{dateLabel}
-
- {dateValue}
+
+ {trashbinCutoff && doc.deleted_at ? (
+ dateValue
+ ) : (
+
+
+ {relativeOnly}
+
+
+ )}
);
};
+const FocusableTime = forwardRef<
+ HTMLTimeElement,
+ ComponentPropsWithoutRef<'time'>
+>((props, ref) => );
+
+FocusableTime.displayName = 'FocusableTime';
+
const VisibilityDoc = ({ doc }: { doc: Doc }) => {
const docIsPublic = getDocLinkReach(doc) === LinkReach.PUBLIC;
const docIsAuth = getDocLinkReach(doc) === LinkReach.AUTHENTICATED;