Make the change log fit its dialog

Adding the Value column pushed the table past max-w-4xl: roughly 624px of
declared column widths inside an 896px dialog, leaving Slice and Note to
fight over the rest. With auto table layout the cells won and the table grew
wider than its container, so the dialog got a horizontal scrollbar along the
bottom.

table-fixed makes the declared widths hold and the free-text columns
truncate instead of widening the table -- which is what the truncate classes
on them already assumed. Dialog widened to max-w-6xl, gutters down from
px-4 to px-3, and Note given an explicit share rather than whatever was
left.

max-w-xs on the slice and note cells was doing nothing useful under fixed
layout and is now overflow-hidden, which is what makes truncate work in a
table cell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-16 23:42:36 -04:00
parent 1d2fc97eee
commit 2bc6c5ec1a

View File

@ -1394,7 +1394,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
{/* History modal */}
{showLog && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40" onClick={() => setShowLog(false)}>
<div className="bg-white rounded-lg shadow-xl w-full max-w-4xl mx-4 flex flex-col max-h-[80vh]" onClick={e => e.stopPropagation()}>
<div className="bg-white rounded-lg shadow-xl w-full max-w-6xl mx-4 flex flex-col max-h-[85vh]" onClick={e => e.stopPropagation()}>
<div className="flex items-center justify-between px-5 py-3 border-b border-gray-200 shrink-0">
<span className="font-medium text-gray-700 text-sm">Change History</span>
<button onClick={() => setShowLog(false)} className="text-gray-400 hover:text-gray-600 text-lg leading-none">×</button>
@ -1406,29 +1406,29 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
) : logEntries.length === 0 ? (
<div className="p-8 text-center text-sm text-gray-400">No log entries yet.</div>
) : (
<table className="w-full text-xs border-collapse">
<table className="w-full text-xs border-collapse table-fixed">
<thead className="sticky top-0 bg-gray-50 text-gray-400 uppercase tracking-wide" style={{fontSize:'10px'}}>
<tr>
<th className="text-left px-4 py-2 font-medium w-32">Time</th>
<th className="text-left px-4 py-2 font-medium w-24">Op</th>
<th className="text-left px-4 py-2 font-medium">Slice</th>
<th className="text-left px-4 py-2 font-medium w-40">Tag</th>
<th className="text-left px-4 py-2 font-medium">Note</th>
<th className="text-right px-4 py-2 font-medium w-28">Value</th>
<th className="text-right px-4 py-2 font-medium w-16">Rows</th>
<th className="px-4 py-2 w-16"></th>
<th className="text-left px-3 py-2 font-medium w-28">Time</th>
<th className="text-left px-3 py-2 font-medium w-20">Op</th>
<th className="text-left px-3 py-2 font-medium">Slice</th>
<th className="text-left px-3 py-2 font-medium w-32">Tag</th>
<th className="text-left px-3 py-2 font-medium w-48">Note</th>
<th className="text-right px-3 py-2 font-medium w-28">Value</th>
<th className="text-right px-3 py-2 font-medium w-16">Rows</th>
<th className="px-3 py-2 w-16"></th>
</tr>
</thead>
<tbody>
{logEntries.map(entry => (
<tr key={entry.id} className="border-t border-gray-100 hover:bg-gray-50">
<td className="px-4 py-2 text-gray-400 whitespace-nowrap">{fmtStamp(entry.stamp)}</td>
<td className="px-4 py-2">
<td className="px-3 py-2 text-gray-400 whitespace-nowrap">{fmtStamp(entry.stamp)}</td>
<td className="px-3 py-2">
<span className={`px-1.5 py-0.5 rounded text-xs font-medium ${opBadge(entry.operation)}`}>
{entry.operation}
</span>
</td>
<td className="px-4 py-2 text-gray-600 font-mono max-w-xs">
<td className="px-3 py-2 text-gray-600 font-mono overflow-hidden">
<button
onClick={() => setExpandedLog(prev => prev === entry.id ? null : entry.id)}
className="text-left w-full truncate hover:text-blue-600"
@ -1447,13 +1447,13 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
)} />
<LogCell entry={entry} field="note" placeholder="add note"
editing={editingCell} setEditing={setEditingCell} onSave={saveLogField} />
<td className={`px-4 py-2 text-right tabular-nums whitespace-nowrap ${
<td className={`px-3 py-2 text-right tabular-nums whitespace-nowrap ${
entry.value_total > 0 ? 'text-green-700' : entry.value_total < 0 ? 'text-red-600' : 'text-gray-400'}`}>
{entry.value_total == null ? '—'
: entry.value_total.toLocaleString(undefined, { maximumFractionDigits: 0 })}
</td>
<td className="px-4 py-2 text-right text-gray-500 tabular-nums">{entry.row_count ?? '—'}</td>
<td className="px-4 py-2">
<td className="px-3 py-2 text-right text-gray-500 tabular-nums">{entry.row_count ?? '—'}</td>
<td className="px-3 py-2">
<button
onClick={() => undoEntry(entry.id)}
disabled={undoingId === entry.id}
@ -1467,7 +1467,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
if (expandedLog !== entry.id) return [row]
return [row, (
<tr key={`${entry.id}-detail`} className="bg-gray-50 border-t border-gray-100">
<td colSpan={8} className="px-4 py-3">
<td colSpan={8} className="px-3 py-3">
<div className="grid gap-3 md:grid-cols-2">
<LogJson label="slice" value={entry.slice} />
<LogJson label="params" value={entry.params} />
@ -1616,7 +1616,7 @@ function LogCell({ entry, field, placeholder, editing, setEditing, onSave, listI
if (active) {
return (
<td className="px-4 py-2">
<td className="px-3 py-2">
<div className="flex items-center gap-1">
<input autoFocus value={editing.text} list={listId}
onChange={e => setEditing(c => ({ ...c, text: e.target.value }))}
@ -1635,7 +1635,7 @@ function LogCell({ entry, field, placeholder, editing, setEditing, onSave, listI
}
return (
<td className="px-4 py-2 text-gray-700 max-w-xs">
<td className="px-3 py-2 text-gray-700 overflow-hidden">
<span onClick={() => setEditing({ id: entry.id, field, text: value })}
className="cursor-text hover:bg-blue-50 rounded px-1 -mx-1 block truncate"
title={value || `Click to ${placeholder}`}>