diff --git a/static/style.css b/static/style.css index dd212efe..1d6c0d26 100644 --- a/static/style.css +++ b/static/style.css @@ -4118,6 +4118,13 @@ body.resizing .sidebar{transition:none!important;} white-space:nowrap; font-size:10px; } +/* Narrow viewports: the ~50px of fixed time-label chrome squeezes the collapsed + row's flexible preview (the most informative part) toward zero. Hide the label + under ~420px; the full timing is still available in Compact/expanded review and + via the title tooltip on wider screens. (#5739 Fable UX fix.) */ +@media (max-width:420px){ + .transparent-event-time{ display:none; } +} .transparent-event-row .thinking-card-label{ display:inline; flex:0 0 auto; diff --git a/static/ui.js b/static/ui.js index c443f035..c1ab9f1d 100644 --- a/static/ui.js +++ b/static/ui.js @@ -5568,6 +5568,18 @@ function _activityClockLabel(ts){ if(!Number.isFinite(stamp)||stamp<=0)return''; try{return new Date(stamp*1000).toLocaleTimeString([], {hour:'numeric',minute:'2-digit'});}catch(_){return'';} } +// Full date+time label for the worklog event-time tooltip (title attr). Guards the +// same valid-Date range as _timestampSeconds so a bad epoch never yields "Invalid +// Date" in the tooltip. (#5739) +function _activityFullClockLabel(ts){ + const stamp=Number(ts); + if(!Number.isFinite(stamp)||stamp<=0||stamp>8.64e12)return''; + try{ + const d=new Date(stamp*1000); + if(isNaN(d.getTime()))return''; + return d.toLocaleString([], {year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}); + }catch(_){return'';} +} function _timestampSeconds(value){ if(value===undefined||value===null||value==='') return null; if(value instanceof Date){ @@ -5663,6 +5675,11 @@ function _syncTransparentEventTimestamp(row, header, opts){ timeEl.className='transparent-event-time'; } timeEl.textContent=label; + // Full date+time tooltip: the bare clock label is date-ambiguous when a settled + // session is reviewed days later (or a run crosses midnight), and timing is the + // whole point of this label. (#5739 Fable UX fix.) + const fullLabel=_activityFullClockLabel(ts); + 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)); diff --git a/tests/test_issue5700_worklog_event_timestamps.py b/tests/test_issue5700_worklog_event_timestamps.py index 4ef424c8..5059d129 100644 --- a/tests/test_issue5700_worklog_event_timestamps.py +++ b/tests/test_issue5700_worklog_event_timestamps.py @@ -247,6 +247,7 @@ global._toolWorklogListEl = () => null; global._activityKeyForLiveTurn = () => 'activity-key'; global._assistantTurnBlocks = () => null; eval(extractFunc('_activityClockLabel')); +eval(extractFunc('_activityFullClockLabel')); eval(extractFunc('_timestampSeconds')); eval(extractFunc('_firstValidTimestampSeconds')); eval(extractFunc('_transparentEventTimestampSeconds')); @@ -458,6 +459,31 @@ process.stdout.write(JSON.stringify({ assert data["normalLabel"] +def test_full_clock_label_tooltip_includes_date_and_rejects_bad_epochs(): + # The worklog time label carries a full date+time tooltip (title attr) so a + # settled session reviewed days later (or a run crossing midnight) is not + # date-ambiguous. Bad epochs must yield '' (never 'Invalid Date'). (#5739 Fable.) + data = _run_node( + """ +process.stdout.write(JSON.stringify({ + normal: _activityFullClockLabel(1700000901), + huge: _activityFullClockLabel(1e20), + zero: _activityFullClockLabel(0), + negative: _activityFullClockLabel(-1), +})); +""" + ) + + # a normal epoch yields a non-empty full label with the year present, no "Invalid Date" + assert data["normal"] + assert "Invalid Date" not in data["normal"] + assert "2023" in data["normal"] or "2024" in data["normal"] or "2025" in data["normal"] or "2026" in data["normal"] + # out-of-range / non-positive epochs yield empty string (no tooltip) + assert data["huge"] == "" + assert data["zero"] == "" + assert data["negative"] == "" + + def test_settled_anchor_scene_rows_use_row_created_at_timestamps(): data = _run_node( """