fix(#6099): make transparent stream activity timestamps optional (#6130)

Add an opt-in setting to hide Transparent Stream's per-event timestamp chips while keeping the response footer time visible, for users who found the per-event chips noisy. Thanks @rodboev. Closes #6099.
This commit is contained in:
Rod Boev 2026-07-18 23:56:44 -04:00 committed by GitHub
parent 17c2f2086c
commit 5771a511e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 141 additions and 4 deletions

View file

@ -9154,6 +9154,7 @@ _SETTINGS_DEFAULTS = {
"structured_code_auto_tree_lines": 10, # in 'auto' mode, minimum line count to default a JSON/YAML block to Tree view (preserves the original hardcoded >=10 behavior)
"session_endless_scroll": False, # auto-load older transcript pages while scrolling upward
"chat_activity_display_mode": "compact_worklog", # compact_worklog | transparent_stream | hide_all_activity
"transparent_stream_event_timestamps": True, # show per-event timestamp chips inside Transparent Stream
"auto_scroll_follow": True, # follow new output to the bottom while streaming (Codex/Claude-Code-style sticky bottom); the user scrolling up unpins and is respected
"worklog_details_expanded_default": False, # opt-in: expand Worklog details by default; default remains folded
"hide_composer_attach": False, # hide attach button in composer footer
@ -9470,6 +9471,7 @@ _SETTINGS_BOOL_KEYS = {
"large_text_paste_as_attachment",
"project_quick_create_buttons",
"session_endless_scroll",
"transparent_stream_event_timestamps",
"auto_scroll_follow",
"worklog_details_expanded_default",
"auth_disabled_acknowledged",

View file

@ -3241,6 +3241,7 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer;
? s.chat_activity_display_mode
: 'compact_worklog';
window._transparentStream=window._chatActivityDisplayMode==='transparent_stream';
window._transparentEventTimestamps=s.transparent_stream_event_timestamps!==false;
window._terminalAutoExpandOnOutput=!!s.terminal_auto_expand_on_output;
window._worklogDetailsExpandedByDefault=!!(
Object.prototype.hasOwnProperty.call(s,'worklog_details_expanded_default')
@ -3389,6 +3390,7 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer;
window._simplifiedToolCalling=true;
window._chatActivityDisplayMode='compact_worklog';
window._transparentStream=false;
window._transparentEventTimestamps=true;
window._terminalAutoExpandOnOutput=false;
window._workspaceTodosTab=false;
if(typeof _applyWorkspaceTodosTabVisibility==='function') _applyWorkspaceTodosTabVisibility();

View file

@ -681,6 +681,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -2436,6 +2438,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -4174,6 +4178,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: '有効にすると、新しい Worklog の詳細が展開された状態で始まり、ツール、Thinking、進捗カードをクリックなしで確認できます。無効の場合、Worklog の詳細はデフォルトで折りたたまれます。ターンごとの手動折りたたみ/展開は優先されます。',
settings_label_chat_activity_display_mode: 'アクティビティ表示',
settings_desc_chat_activity_display_mode: '「コンパクトワークログ」はバックグラウンドアクティビティを簡潔なサマリーにまとめます。「トランスペアレントストリーム」は思考プロセスやツールの呼び出しイベントを、時系列順に展開可能な個別の行として表示し続けます。 Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: '最初のトークン出力までの時間',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -6665,6 +6671,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'Если включено, новые детали Worklog открываются раскрытыми, чтобы инструменты, рассуждения и прогресс были видны без дополнительного клика. Если выключено, Worklog по умолчанию остаётся свёрнутым; ручной выбор для конкретного хода сохраняется.',
settings_label_chat_activity_display_mode: 'Отображение активности',
settings_desc_chat_activity_display_mode: 'Компактный Worklog группирует вспомогательную активность в спокойную сводку. Прозрачный поток оставляет каждое рассуждение и событие инструмента отдельной хронологической раскрываемой строкой. Режим «Только финальный ответ» скрывает строки активности, оставляя видимым финальный ответ.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Время до первого токена',
processed_elapsed: _i18nProcessedElapsedRu,
worklog_thinking: 'Размышление',
@ -8372,6 +8380,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -9719,6 +9729,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -11743,6 +11755,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: '当启用此功能时,新的工作日志详情会自动展开,因此无需额外点击即可查看工具、思路和进度卡片。当禁用此功能时,工作日志详情默认保持折叠状态;不过每次仍可手动选择折叠或展开。',
settings_label_chat_activity_display_mode: '活动显示',
settings_desc_chat_activity_display_mode: '紧凑的工作日志将支持活动归纳为简洁摘要。透明流模式则将每个思考/工具使用事件按时间顺序单独列为可展开的行。 Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: '首个令牌生成时间',
processed_elapsed: _i18nProcessedElapsedZh,
worklog_thinking: '正在思考',
@ -12660,6 +12674,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedZhHant,
worklog_thinking: '正在思考',
@ -14293,6 +14309,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -15930,6 +15948,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -17682,6 +17702,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'Lorsqu\'il est activé, les nouveaux détails du journal de travail commencent développés, de sorte que les cartes d\'outils, de réflexion et de progression soient visibles sans clic supplémentaire. Lorsqu\'il est désactivé, les détails du journal de travail restent repliés par défaut ; les choix manuels de réduction/développement par tour prévalent.',
settings_label_chat_activity_display_mode: 'Affichage des activités',
settings_desc_chat_activity_display_mode: 'Journal de travail compact regroupe les activités de support dans un résumé discret. Flux transparent conserve chaque événement de réflexion/outil comme une ligne extensible chronologique. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Délai jusqu\'au premier jeton',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Réflexion',
@ -19628,6 +19650,7 @@ const LOCALES = {
settings_desc_auxiliary_models: 'Směrování vedlejších úloh pro vision, kompresi, generování názvů atd. "Auto" používá váš hlavní chatovací model.',
settings_desc_bot_name: 'Použito pouze pro výchozí profil. Ostatní profily používají vlastní názvy profilů.',
settings_desc_chat_activity_display_mode: 'Kompaktní Worklog seskupuje podpůrnou aktivitu do tichého souhrnu. Transparentní Stream zachovává každou událost přemýšlení/nástroje jako vlastní chronologicky rozbalitelný řádek. Final answer only hides activity rows while keeping the final answer visible.',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
settings_desc_check_updates: 'Zobrazit banner když jsou dostupné novější verze WebUI nebo Agenta. Spouští periodické git fetch na pozadí.',
settings_desc_claude_code_sessions: 'Zobrazit importované relace Claude Code v bočním panelu, nebo je skrýt při zachování ostatních externích relací viditelných.',
settings_desc_composer_controls: 'Přepínat které ovládací prvky jsou zobrazeny v zápatí chatu. Přetáhněte čipy pro změnu pořadí každé skupiny zápatí.',
@ -19700,6 +19723,7 @@ const LOCALES = {
settings_label_auxiliary_models: 'Pomocné modely',
settings_label_bot_name: 'Výchozí název asistenta',
settings_label_chat_activity_display_mode: 'Zobrazení aktivity',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_label_check_updates: 'Kontrolovat aktualizace',
settings_label_update_channel: 'Kanál aktualizací',
settings_update_channel_stable: 'Stabilní (doporučeno)',
@ -21085,6 +21109,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'When enabled, new Worklog details start expanded so tool, thinking, and progress cards are visible without an extra click. When off, Worklog details stay folded by default; per-turn manual collapse/expand choices still win.',
settings_label_chat_activity_display_mode: 'Activity display',
settings_desc_chat_activity_display_mode: 'Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Time to first token',
processed_elapsed: _i18nProcessedElapsedEn,
worklog_thinking: 'Thinking',
@ -22837,6 +22863,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'Gdy ta opcja jest włączona, nowe szczegóły Worklog zaczynają rozwinięte, dzięki czemu karty narzędzi, Thinking i postępu są widoczne bez dodatkowego kliknięcia. Gdy jest wyłączona, szczegóły Worklog pozostają domyślnie zwinięte. Ręczne decyzje o zwinięciu/rozwinięciu w danej turze mają pierwszeństwo.',
settings_label_chat_activity_display_mode: 'Wyświetlanie aktywności',
settings_desc_chat_activity_display_mode: 'Kompaktowy dziennik pracy grupuje aktywność wspierającą w dyskretne podsumowanie. Transparentny strumień zachowuje każde zdarzenie myślenia/narzędzia jako osobny chronologiczny wiersz z możliwością rozwinięcia. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Czas do pierwszego tokenu',
processed_elapsed: _i18nProcessedElapsedPl,
worklog_thinking: 'Myślenie',
@ -24467,6 +24495,8 @@ const LOCALES = {
settings_desc_worklog_details_expanded_default: 'Khi bật, chi tiết Worklog mới sẽ được mở sẵn để hiển thị tool, suy nghĩ và thẻ tiến trình mà không cần bấm thêm. Khi tắt, Worklog sẽ mặc định thu gọn; các lựa chọn mở/thu gọn thủ công theo từng lượt vẫn được ưu tiên.',
settings_label_chat_activity_display_mode: 'Hiển thị hoạt động',
settings_desc_chat_activity_display_mode: 'Compact Worklog gom nhóm hoạt động thành bản tóm tắt gọn. Transparent Stream giữ mỗi sự kiện thinking/tool thành một dòng theo thứ tự thời gian có thể mở rộng. Final answer only hides activity rows while keeping the final answer visible.',
settings_label_transparent_stream_event_timestamps: 'Show Transparent Stream event timestamps',
settings_desc_transparent_stream_event_timestamps: 'Keeps the response footer time visible while hiding the per-event timestamp chips.',
first_token_time: 'Thời gian tới token đầu tiên',
processed_elapsed: _i18nProcessedElapsedVi,
worklog_thinking: 'Đang suy nghĩ',

View file

@ -1065,6 +1065,11 @@
<option value="hide_all_activity" data-i18n="settings_option_final_answer_only">Final answer only</option>
</select>
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_chat_activity_display_mode">Compact Worklog groups supporting activity into a quiet summary. Transparent Stream keeps each thinking/tool event as its own chronological expandable row. Final answer only hides activity rows while keeping the final answer visible.</div>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin-top:8px">
<input type="checkbox" id="settingsTransparentEventTimestamps" checked onchange="_pickTransparentEventTimestamps(this.checked)" style="width:15px;height:15px;accent-color:var(--accent)">
<span data-i18n="settings_label_transparent_stream_event_timestamps">Show Transparent Stream event timestamps</span>
</label>
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="settings_desc_transparent_stream_event_timestamps">Keeps the response footer time visible while hiding the per-event timestamp chips.</div>
</div>
<div class="settings-field" style="margin-top:8px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">

View file

@ -8408,6 +8408,7 @@ function _syncStructuredCodeLinesEnabled(){
function _appearancePayloadFromUi(){
const worklogDetailsExpanded=!!($('settingsWorklogDetailsExpandedDefault')||{}).checked;
const chatActivityModeSel=$('settingsChatActivityDisplayMode');
const transparentEventTimestamps=$('settingsTransparentEventTimestamps');
return {
theme: ($('settingsTheme')||{}).value || localStorage.getItem('hermes-theme') || 'dark',
skin: ($('settingsSkin')||{}).value || localStorage.getItem('hermes-skin') || 'default',
@ -8415,6 +8416,7 @@ function _appearancePayloadFromUi(){
chat_activity_display_mode: chatActivityModeSel&&(chatActivityModeSel.value==='transparent_stream'||chatActivityModeSel.value==='hide_all_activity')
? chatActivityModeSel.value
: 'compact_worklog',
transparent_stream_event_timestamps: transparentEventTimestamps ? transparentEventTimestamps.checked : true,
session_jump_buttons: !!($('settingsSessionJumpButtons')||{}).checked,
session_endless_scroll: !!($('settingsSessionEndlessScroll')||{}).checked,
auto_scroll_follow: !!($('settingsAutoScrollFollow')||{}).checked,
@ -8443,9 +8445,22 @@ function _syncChatActivityDisplayModeControl(mode){
});
window._chatActivityDisplayMode=next;
window._transparentStream=next==='transparent_stream';
if(typeof _syncTransparentEventTimestampsControl==='function') _syncTransparentEventTimestampsControl(window._transparentEventTimestamps,next);
if(next==='hide_all_activity'&&typeof window._hideLiveActivityForFinalAnswerOnly==='function') window._hideLiveActivityForFinalAnswerOnly();
}
function _syncTransparentEventTimestampsControl(enabled, mode){
const next=enabled!==false;
const activeMode=mode==='transparent_stream'||mode==='hide_all_activity' ? mode : (window._chatActivityDisplayMode||'compact_worklog');
const checkbox=$('settingsTransparentEventTimestamps');
if(checkbox){
checkbox.checked=next;
checkbox.disabled=activeMode!=='transparent_stream';
checkbox.style.opacity=activeMode==='transparent_stream'?'':'0.5';
}
window._transparentEventTimestamps=next;
}
function _pickChatActivityDisplayMode(mode){
_syncChatActivityDisplayModeControl(mode);
if(typeof clearMessageRenderCache==='function') clearMessageRenderCache();
@ -8454,6 +8469,14 @@ function _pickChatActivityDisplayMode(mode){
}
if(typeof window!=='undefined') window._pickChatActivityDisplayMode=_pickChatActivityDisplayMode;
function _pickTransparentEventTimestamps(enabled){
_syncTransparentEventTimestampsControl(enabled,window._chatActivityDisplayMode);
if(typeof clearMessageRenderCache==='function') clearMessageRenderCache();
if(typeof renderMessages==='function') renderMessages({preserveScroll:true});
_scheduleAppearanceAutosave();
}
if(typeof window!=='undefined') window._pickTransparentEventTimestamps=_pickTransparentEventTimestamps;
function _setAppearanceAutosaveStatus(state){
const el=$('settingsAppearanceAutosaveStatus');
if(!el) return;
@ -8502,8 +8525,10 @@ async function _autosaveAppearanceSettings(payload){
window._sessionJumpButtonsEnabled=!!saved.session_jump_buttons;
if(Object.prototype.hasOwnProperty.call(saved,'chat_activity_display_mode')){
const beforeMode=window._chatActivityDisplayMode;
const beforeTimestamps=window._transparentEventTimestamps!==false;
_syncChatActivityDisplayModeControl(saved.chat_activity_display_mode);
if(window._chatActivityDisplayMode!==beforeMode){
_syncTransparentEventTimestampsControl(saved.transparent_stream_event_timestamps, saved.chat_activity_display_mode);
if(window._chatActivityDisplayMode!==beforeMode||((window._transparentEventTimestamps!==false)!==beforeTimestamps)){
if(typeof clearMessageRenderCache==='function') clearMessageRenderCache();
if(typeof renderMessages==='function') renderMessages({preserveScroll:true});
}
@ -8935,12 +8960,19 @@ async function loadSettingsPanel(){
}
const worklogDetailsExpandedCb=$('settingsWorklogDetailsExpandedDefault');
const chatActivityModeSel=$('settingsChatActivityDisplayMode');
const transparentEventTimestampsCb=$('settingsTransparentEventTimestamps');
if(chatActivityModeSel){
_syncChatActivityDisplayModeControl(settings.chat_activity_display_mode);
_syncTransparentEventTimestampsControl(settings.transparent_stream_event_timestamps, settings.chat_activity_display_mode);
chatActivityModeSel.addEventListener('change',()=>{
_pickChatActivityDisplayMode(chatActivityModeSel.value);
},{once:false});
}
if(transparentEventTimestampsCb){
transparentEventTimestampsCb.addEventListener('change',()=>{
_pickTransparentEventTimestamps(transparentEventTimestampsCb.checked);
},{once:false});
}
if(worklogDetailsExpandedCb){
const worklogDetailsExpanded=Object.prototype.hasOwnProperty.call(settings,'worklog_details_expanded_default')
? settings.worklog_details_expanded_default
@ -11794,6 +11826,7 @@ function _applySavedSettingsUi(saved, body, opts){
window._showThinking=body.show_thinking!==false;
window._simplifiedToolCalling=true;
_syncChatActivityDisplayModeControl(body.chat_activity_display_mode);
_syncTransparentEventTimestampsControl(body.transparent_stream_event_timestamps, body.chat_activity_display_mode);
window._terminalAutoExpandOnOutput=!!body.terminal_auto_expand_on_output;
window._workspaceTodosTab=!!body.workspace_todos_tab;
if(typeof _applyWorkspaceTodosTabVisibility==='function') _applyWorkspaceTodosTabVisibility();
@ -12438,6 +12471,7 @@ async function saveSettings(andClose){
||(($('settingsChatActivityDisplayMode')||{}).value==='hide_all_activity'))
? ($('settingsChatActivityDisplayMode')||{}).value
: 'compact_worklog';
body.transparent_stream_event_timestamps=(($('settingsTransparentEventTimestamps')||{}).checked)!==false;
body.auto_scroll_follow=!!($('settingsAutoScrollFollow')||{}).checked;
body.render_user_markdown=!!($('settingsRenderUserMarkdown')||{}).checked;
body.large_text_paste_as_attachment=!!($('settingsLargeTextPasteAsAttachment')||{}).checked;

View file

@ -6307,6 +6307,7 @@ function _transparentEventTimestampSeconds(row, opts){
function _syncTransparentEventTimestamp(row, header, opts){
if(!row||!header) return null;
opts=opts||{};
const showEventTimestamp=!(typeof window!=='undefined'&&window._transparentEventTimestamps===false);
const live=opts.live===true||row.getAttribute&&(
row.getAttribute('data-live-tid')==='1'||
row.getAttribute('data-live-thinking')==='1'||
@ -6340,6 +6341,12 @@ function _syncTransparentEventTimestamp(row, header, opts){
return null;
}
const source=explicitTs||toolTs||attrTs?'event':'live';
row.setAttribute('data-event-at',String(ts));
row.setAttribute('data-event-at-source',source);
if(!showEventTimestamp){
if(timeEl) timeEl.remove();
return null;
}
if(!timeEl){
timeEl=document.createElement('span');
timeEl.className='transparent-event-time';
@ -6352,8 +6359,6 @@ function _syncTransparentEventTimestamp(row, header, opts){
if(fullLabel) timeEl.setAttribute('title',fullLabel); else timeEl.removeAttribute('title');
timeEl.setAttribute('data-event-at',String(ts));
timeEl.setAttribute('data-event-at-source',source);
row.setAttribute('data-event-at',String(ts));
row.setAttribute('data-event-at-source',source);
const anchor=header.querySelector('.transparent-event-status,.thinking-card-btn-row,.tool-card-toggle,.thinking-card-toggle');
if(timeEl.parentNode!==header){
if(anchor&&anchor.parentNode===header) header.insertBefore(timeEl,anchor);

View file

@ -86,6 +86,21 @@ def test_chat_activity_display_mode_persists_transparent_stream_hide_all_activit
assert saved["chat_activity_display_mode"] == "hide_all_activity"
assert json.loads(settings_path.read_text(encoding="utf-8"))["chat_activity_display_mode"] == "hide_all_activity"
def test_transparent_stream_event_timestamps_default_true_and_persist_boolean(monkeypatch, tmp_path):
import api.config as config
settings_path = tmp_path / "settings.json"
monkeypatch.setattr(config, "SETTINGS_FILE", settings_path)
loaded = config.load_settings()
assert loaded["transparent_stream_event_timestamps"] is True
saved = config.save_settings({"transparent_stream_event_timestamps": False})
assert saved["transparent_stream_event_timestamps"] is False
assert json.loads(settings_path.read_text(encoding="utf-8"))["transparent_stream_event_timestamps"] is False
def test_chat_activity_display_mode_supports_three_values():
assert "function chatActivityMode()" in UI_JS
assert "function isTransparentStream()" in UI_JS
@ -418,29 +433,42 @@ def test_transparent_stream_live_branch_uses_direct_rows():
def test_settings_ui_exposes_chat_activity_display_mode_selector():
assert 'id="settingsChatActivityDisplayMode"' in INDEX_HTML
assert 'id="settingsTransparentEventTimestamps"' in INDEX_HTML
assert 'data-chat-activity-mode="compact_worklog"' in INDEX_HTML
assert 'data-chat-activity-mode="transparent_stream"' in INDEX_HTML
assert 'data-chat-activity-mode="hide_all_activity"' in INDEX_HTML
assert "_pickTransparentEventTimestamps(this.checked)" in INDEX_HTML
assert 'value="compact_worklog"' in INDEX_HTML
assert 'value="transparent_stream"' in INDEX_HTML
assert 'value="hide_all_activity"' in INDEX_HTML
assert 'data-i18n="settings_label_chat_activity_display_mode"' in INDEX_HTML
assert 'data-i18n="settings_label_transparent_stream_event_timestamps"' in INDEX_HTML
assert 'data-i18n="settings_desc_transparent_stream_event_timestamps"' in INDEX_HTML
assert 'settings_option_final_answer_only' in INDEX_HTML
assert "chat_activity_display_mode" in PANELS_JS
assert "transparent_stream_event_timestamps" in PANELS_JS
assert "settingsChatActivityDisplayMode" in PANELS_JS
assert "function _syncChatActivityDisplayModeControl" in PANELS_JS
assert "function _syncTransparentEventTimestampsControl" in PANELS_JS
assert "function _pickChatActivityDisplayMode" in PANELS_JS
assert "function _pickTransparentEventTimestamps" in PANELS_JS
assert "body.chat_activity_display_mode" in PANELS_JS
assert "renderMessages({preserveScroll:true})" in PANELS_JS
assert "settings_label_chat_activity_display_mode" in I18N_JS
assert "settings_desc_chat_activity_display_mode" in I18N_JS
assert "settings_label_transparent_stream_event_timestamps" in I18N_JS
assert "settings_desc_transparent_stream_event_timestamps" in I18N_JS
assert I18N_JS.count("settings_option_final_answer_only") == I18N_JS.count("settings_option_transparent_stream")
def test_chat_activity_display_mode_plumbing_preserves_hide_all_activity():
assert "s.chat_activity_display_mode==='transparent_stream'||s.chat_activity_display_mode==='hide_all_activity'" in BOOT_JS
assert "window._transparentEventTimestamps=s.transparent_stream_event_timestamps!==false;" in BOOT_JS
assert "window._transparentEventTimestamps=true;" in BOOT_JS
assert "chatActivityModeSel&&(chatActivityModeSel.value==='transparent_stream'||chatActivityModeSel.value==='hide_all_activity')" in PANELS_JS
assert "const next=mode==='transparent_stream'||mode==='hide_all_activity' ? mode : 'compact_worklog';" in PANELS_JS
assert "if(typeof _syncTransparentEventTimestampsControl==='function') _syncTransparentEventTimestampsControl(window._transparentEventTimestamps,next);" in PANELS_JS
assert "window._transparentEventTimestamps=next;" in PANELS_JS
assert "hide_all_activity" in PANELS_JS
@ -490,12 +518,15 @@ def test_appearance_autosave_rerenders_only_when_activity_mode_changes():
autosave_block = PANELS_JS[start:end]
assert "const beforeMode=window._chatActivityDisplayMode;" in autosave_block
assert "const beforeTimestamps=window._transparentEventTimestamps!==false;" in autosave_block
assert "_syncChatActivityDisplayModeControl(saved.chat_activity_display_mode);" in autosave_block
changed_guard = "if(window._chatActivityDisplayMode!==beforeMode){"
assert "_syncTransparentEventTimestampsControl(saved.transparent_stream_event_timestamps, saved.chat_activity_display_mode);" in autosave_block
changed_guard = "if(window._chatActivityDisplayMode!==beforeMode||((window._transparentEventTimestamps!==false)!==beforeTimestamps)){"
assert changed_guard in autosave_block
guarded = autosave_block[autosave_block.index(changed_guard):]
assert "clearMessageRenderCache()" in guarded
assert "renderMessages({preserveScroll:true})" in guarded
assert PANELS_JS.count("_syncTransparentEventTimestampsControl(settings.transparent_stream_event_timestamps, settings.chat_activity_display_mode);") == 1
def test_attach_copy_button_declares_local_button():

View file

@ -798,6 +798,34 @@ process.stdout.write(JSON.stringify({
assert data["omittedEventAt"] is None
def test_transparent_event_timestamp_toggle_hides_chip_but_preserves_event_anchor():
data = _run_node(
"""
window._transparentEventTimestamps = false;
const row = makeToolRow(null, false);
const header = row.querySelector('.tool-card-header');
_syncTransparentEventTimestamp(row, header, { ts: 1700000910 });
const footer = document.createElement('span');
footer.className = 'msg-time';
footer.textContent = '10:15 PM';
row.appendChild(footer);
process.stdout.write(JSON.stringify({
count: header.querySelectorAll('.transparent-event-time').length,
eventAt: row.getAttribute('data-event-at'),
source: row.getAttribute('data-event-at-source'),
footerCount: row.querySelectorAll('.msg-time').length,
footerText: footer.textContent,
}));
"""
)
assert data["count"] == 0
assert data["eventAt"] == "1700000910"
assert data["source"] == "event"
assert data["footerCount"] == 1
assert data["footerText"] == "10:15 PM"
def test_ordered_tool_use_row_prefers_later_valid_timestamp_field():
data = _run_node(
"""