// ==UserScript==
// @name Displays Date & Times within Anthropic Claude Conversations (ES5-safe)
// @namespace http://tampermonkey.net/
// @version 2.5
// @license MIT
// @description Reveal hidden timestamps in Claude conversations with robust DOM/API handling, XHR support, and debounced observer; always show year; larger font.
// @author Wayne
// @match https://claude.ai/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
var CONFIG = {
timestampClass: 'claude-timestamp',
messageSelector: "[data-testid*='message'], [class*='message'], .font-claude-message, [role='article']",
observerDelay: 500,
debounceDelay: 250,
timestampFormat: 'absolute',
position: 'inline',
maxJsonScriptSize: 200000, // no numeric separator
timestampFontSizePx: 15 // increased from 12px (~+2pt)
};
function formatTimestamp(isoString) {
var date = new Date(isoString);
return date.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: true,
year: 'numeric'
});
}
function injectStyles() {
if (document.getElementById('claude-timestamp-styles')) return;
var style = document.createElement('style');
style.id = 'claude-timestamp-styles';
style.textContent =
'.' + CONFIG.timestampClass + ' {' +
'font-size: ' + CONFIG.timestampFontSizePx + 'px;' +
'color: #5d6269;' +
'margin-bottom: 6px;' +
"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;" +
'display: block;' +
'}' +
'@media (prefers-color-scheme: dark) {' +
'.' + CONFIG.timestampClass + ' { color: #a2acba; }' +
'}';
document.head.appendChild(style);
}
function extractTimestamp(messageElement) {
var dataAttrs = ['data-timestamp', 'data-created-at', 'data-time'];
for (var i = 0; i < dataAttrs.length; i++) {
var value = messageElement.getAttribute(dataAttrs[i]);
if (value) return value;
}
var parent = messageElement.closest("[data-testid*='conversation'], .conversation, main");
if (parent) {
var scripts = parent.querySelectorAll("script[type='application/json']");
for (var j = 0; j < scripts.length; j++) {
var txt = scripts[j].textContent || '';
if (txt.length > CONFIG.maxJsonScriptSize) continue;
try {
var data = JSON.parse(txt);
if (data.created_at) return data.created_at;
if (data.timestamp) return data.timestamp;
} catch (e) {}
}
}
var messageId = messageElement.id || messageElement.getAttribute('data-message-id');
if (messageId && window.conversationData && window.conversationData.messages) {
var message = null;
for (var m = 0; m < window.conversationData.messages.length; m++) {
if (window.conversationData.messages[m].id === messageId) {
message = window.conversationData.messages[m];
break;
}
}
if (message && message.created_at) return message.created_at;
}
var keys = Object.keys(messageElement);
var reactKey = null;
for (var k = 0; k < keys.length; k++) {
if (keys[k].indexOf('__reactFiber$') === 0 || keys[k].indexOf('__reactProps$') === 0 || keys[k].indexOf('__reactInternalInstance') === 0) {
reactKey = keys[k];
break;
}
}
if (reactKey) {
try {
var fiber = messageElement[reactKey];
var props = (fiber && fiber.return && fiber.return.memoizedProps) ||
(fiber && fiber.memoizedProps) ||
(fiber && fiber.pendingProps) ||
(fiber && fiber.return && fiber.return.pendingProps);
var ts = (props && props.message && props.message.created_at) ||
(props && props.timestamp) ||
(props && props.createdAt);
if (ts) return ts;
} catch (e) {}
}
return null;
}
function interceptNetworkData() {
var originalFetch = window.fetch;
window.fetch = function () {
var args = arguments;
var req = args[0];
var url = typeof req === 'string' ? req : (req && req.url ? req.url : '');
return originalFetch.apply(this, args).then(function (res) {
if (url && /(\/conversations|\/chat)\b/.test(url)) {
try {
var clone = res.clone();
var ct = (clone.headers && typeof clone.headers.get === 'function' ? clone.headers.get('content-type') : '') || '';
if (ct.indexOf('application/json') !== -1) {
clone.json().then(function (data) {
if (data && (data.messages || data.conversation)) {
window.conversationData = data;
setTimeout(processMessages, 100);
}
})["catch"](function () {});
}
} catch (e) {}
}
return res;
});
};
}
function interceptXHR() {
var originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
this._url = url;
return originalOpen.apply(this, arguments);
};
var originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
this.addEventListener('load', function () {
if (this._url && /(\/conversations|\/chat)\b/.test(this._url)) {
try {
var ct = this.getResponseHeader('content-type') || '';
if (ct.indexOf('application/json') !== -1) {
var data = JSON.parse(this.responseText);
if (data && (data.messages || data.conversation)) {
window.conversationData = data;
setTimeout(processMessages, 100);
}
}
} catch (e) {}
}
});
return originalSend.apply(this, arguments);
};
}
function addTimestamp(messageElement, timestamp) {
if (messageElement.querySelector('.' + CONFIG.timestampClass)) return;
var timestampElement = document.createElement('div');
timestampElement.className = CONFIG.timestampClass;
timestampElement.textContent = formatTimestamp(timestamp);
messageElement.insertBefore(timestampElement, messageElement.firstChild);
}
function debounce(fn, delay) {
var timer;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
function setupObserver() {
var debouncedProcess = debounce(processMessages, CONFIG.debounceDelay);
var observer = new MutationObserver(function (mutations) {
var shouldProcess = false;
for (var i = 0; i < mutations.length; i++) {
var mutation = mutations[i];
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
for (var n = 0; n < mutation.addedNodes.length; n++) {
var node = mutation.addedNodes[n];
if (node.nodeType === 1 && ( (node.matches && node.matches(CONFIG.messageSelector)) ||
(node.querySelector && node.querySelector(CONFIG.messageSelector)) )) {
shouldProcess = true;
break;
}
}
}
if (shouldProcess) break;
}
if (shouldProcess) debouncedProcess();
});
observer.observe(document.body, { childList: true, subtree: true });
return observer;
}
function init() {
injectStyles();
interceptNetworkData();
interceptXHR();
setTimeout(processMessages, 1000);
setupObserver();
setInterval(processMessages, 10000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
setTimeout(init, 100);
}
})();