Give the segment table room, and size its text columns by content

The page was capped at max-w-4xl. Eleven columns in 896px meant something was
always smashed, and the previous fix just moved which one -- w-full on the note
cell let it claim the slack, and w-48 on label is only a hint in an auto-layout
table, so the browser shrank the label input to min-content. The cap is gone;
the blocks that read better narrow keep their own.

label, note and counts-toward are now measured off the longest value in the
log, in ch, with floors so an empty table keeps its headers and ceilings so one
long note cannot push the numbers off the side.

The note's one-line clip moved onto its inner div: a max-width on a table cell
is only a hint too, so pinning it to the cell could collapse the column to
min-content or let it grow past the measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-17 23:00:27 -04:00
parent 98f7bbef34
commit 602effde73

View File

@ -165,6 +165,19 @@ export default function Baseline({ sources = [], sourceId, versions = [], versio
} catch (err) { flash(err.message, 'error') } } catch (err) { flash(err.message, 'error') }
} }
// Column widths read off the content rather than guessed. ch is the width of a
// '0', so for proportional text it runs slightly generous -- which is what is
// wanted for an input you are about to type a longer name into. The floors keep
// an empty table from collapsing its headers; the ceilings keep one long note
// from pushing the numbers off the side.
function widthCh(values, min, max) {
const longest = values.reduce((n, v) => Math.max(n, String(v || '').length), 0)
return `${Math.min(max, Math.max(min, longest + 2))}ch`
}
const labelW = widthCh(log.map(e => e.label || e.tag || e.note), 18, 40)
const bucketW = widthCh(log.map(e => e.bucket), 16, 28)
const noteW = widthCh(log.map(e => e.note), 24, 60)
function loadLog() { function loadLog() {
fetch(`/api/versions/${versionId}/log`).then(r => r.json()).then(data => { fetch(`/api/versions/${versionId}/log`).then(r => r.json()).then(data => {
setLog(data.filter(e => e.operation === 'baseline' || e.operation === 'reference')) setLog(data.filter(e => e.operation === 'baseline' || e.operation === 'reference'))
@ -341,7 +354,10 @@ export default function Baseline({ sources = [], sourceId, versions = [], versio
return ( return (
<div className="h-full overflow-y-auto bg-gray-50"> <div className="h-full overflow-y-auto bg-gray-50">
<div className="p-4 flex flex-col gap-4 max-w-4xl"> {/* Uncapped: the segment table is eleven columns, and at max-w-4xl (896px)
something always got smashed no matter how the widths were divided. The
blocks that read better narrow keep their own caps. */}
<div className="p-4 flex flex-col gap-4">
{msg && ( {msg && (
<div className={`px-3 py-2 text-xs rounded font-medium ${msg.type === 'error' ? 'bg-red-50 text-red-700' : 'bg-green-50 text-green-700'}`}> <div className={`px-3 py-2 text-xs rounded font-medium ${msg.type === 'error' ? 'bg-red-50 text-red-700' : 'bg-green-50 text-green-700'}`}>
@ -408,9 +424,9 @@ export default function Baseline({ sources = [], sourceId, versions = [], versio
<th className="px-3 py-1.5 font-medium w-6"></th> <th className="px-3 py-1.5 font-medium w-6"></th>
<th className="px-3 py-1.5 font-medium">#</th> <th className="px-3 py-1.5 font-medium">#</th>
<th className="px-3 py-1.5 font-medium w-20">kind</th> <th className="px-3 py-1.5 font-medium w-20">kind</th>
<th className="px-3 py-1.5 font-medium w-48">label</th> <th className="px-3 py-1.5 font-medium" style={{ width: labelW }}>label</th>
<th className="px-3 py-1.5 font-medium w-full max-w-0">note</th> <th className="px-3 py-1.5 font-medium" style={{ width: noteW }}>note</th>
<th className="px-3 py-1.5 font-medium w-36">counts toward</th> <th className="px-3 py-1.5 font-medium" style={{ width: bucketW }}>counts toward</th>
<th className="px-3 py-1.5 font-medium text-right">rows</th> <th className="px-3 py-1.5 font-medium text-right">rows</th>
<th className="px-3 py-1.5 font-medium text-right">{log[0]?.value_col || 'value'}</th> <th className="px-3 py-1.5 font-medium text-right">{log[0]?.value_col || 'value'}</th>
<th className="px-3 py-1.5 font-medium">by</th> <th className="px-3 py-1.5 font-medium">by</th>
@ -464,16 +480,16 @@ export default function Baseline({ sources = [], sourceId, versions = [], versio
focus:border-blue-400 rounded px-1 py-0.5 text-xs focus:border-blue-400 rounded px-1 py-0.5 text-xs
focus:outline-none bg-transparent" /> focus:outline-none bg-transparent" />
</td> </td>
{/* One line, clipped. The note is provenance -- it can run {/* One line, clipped against the measured width above. The
long -- and this row is eleven columns wide, so it used note is provenance and can run long, so left to itself it
to wrap and push every row to two or three lines. w-full wrapped and pushed every row to two or three lines.
plus max-w-0 is what lets a cell in an auto-layout table Expanding the row shows it in full. The cap is on the div,
absorb the slack and still clip: without the max-w-0 the not the cell: a max-width on a cell in an auto-layout table
column simply grows to fit the text. Expanding the row is only a hint, and the column can still collapse to
shows it in full. */} min-content or grow past it. */}
<td className="px-3 py-2 w-full max-w-0"> <td className="px-3 py-2">
{entry.note {entry.note
? <div className="truncate" title={entry.note}>{entry.note}</div> ? <div className="truncate" style={{ maxWidth: noteW }} title={entry.note}>{entry.note}</div>
: <span className="text-gray-300"></span>} : <span className="text-gray-300"></span>}
</td> </td>
<td className="px-3 py-2" onClick={e => e.stopPropagation()}> <td className="px-3 py-2" onClick={e => e.stopPropagation()}>