Grey the measure the plug holds

Absorbed by asks which of price and volume moves, and then said so only in
a sentence underneath itself. The column it holds still now says it: the
header reads "holds" in place of its hint and the three boxes go flat grey,
so what you are editing is legible from the numbers rather than from prose
about them.

The sentence goes, since the greyed column is the same statement made where
it applies. The toggle stays -- it is an input, and with dollars alone
nothing in the data answers it.

A held box is still typeable, and doing so is how you override the toggle:
naming qty or price answers the question directly, which is why the control
only appears while dollars are the sole input.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-21 23:05:54 -04:00
parent 042c85d6c3
commit 46a812dd74

View File

@ -289,7 +289,7 @@ function groupDigits(raw) {
return `${neg ? '-' : ''}${grouped}${dot === -1 ? '' : `.${frac}`}` return `${neg ? '-' : ''}${grouped}${dot === -1 ? '' : `.${frac}`}`
} }
function LedgerInput({ value, active, onChange, onFocus, suffix }) { function LedgerInput({ value, active, held, onChange, onFocus, suffix }) {
const ref = useRef(null) const ref = useRef(null)
const caret = useRef(null) const caret = useRef(null)
const display = groupDigits(value) const display = groupDigits(value)
@ -328,9 +328,12 @@ function LedgerInput({ value, active, onChange, onFocus, suffix }) {
onChange(el.value.replace(/,/g, '')) onChange(el.value.replace(/,/g, ''))
}} }}
onFocus={onFocus} placeholder="—" onFocus={onFocus} placeholder="—"
className={`border rounded py-0.5 text-xs w-24 text-right font-mono tabular-nums title={held ? 'Held by the Absorbed by setting — type here to set it yourself' : undefined}
className={`border rounded py-0.5 text-xs w-24 text-right font-mono tabular-nums transition-colors
${suffix ? 'pl-2 pr-5' : 'px-2'} ${suffix ? 'pl-2 pr-5' : 'px-2'}
${active ? 'border-blue-400 bg-blue-50/40 text-gray-800' : 'border-gray-200 bg-white text-gray-700'}`} /> ${active ? 'border-blue-400 bg-blue-50/40 text-gray-800'
: held ? 'border-gray-100 bg-gray-50 text-gray-400'
: 'border-gray-200 bg-white text-gray-700'}`} />
{suffix && ( {suffix && (
<span className="absolute right-1.5 text-gray-400 pointer-events-none" <span className="absolute right-1.5 text-gray-400 pointer-events-none"
style={{ fontSize: '10px' }}>{suffix}</span> style={{ fontSize: '10px' }}>{suffix}</span>
@ -549,6 +552,14 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
})() })()
const showRunning = !!valueCol && (lines.length + exclLines.length) > 1 const showRunning = !!valueCol && (lines.length + exclLines.length) > 1
// Which measure the plug holds still. Only meaningful while dollars are the
// sole input -- that is the one case where price and volume are both unknown
// and the toggle is answering for them. 'price' means price absorbs the
// change, so qty holds; 'volume' means qty absorbs it, so price holds.
const heldKey = (valueCol && unitsCol && dollarsOnly)
? (scalePlug === 'price' ? 'units' : 'price')
: null
const numCell = 'text-right font-mono tabular-nums whitespace-nowrap px-2' const numCell = 'text-right font-mono tabular-nums whitespace-nowrap px-2'
const rule = <td className="p-0"><div className="border-t border-gray-300 my-1" /></td> const rule = <td className="p-0"><div className="border-t border-gray-300 my-1" /></td>
@ -559,9 +570,13 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
<tr className="text-gray-600" style={{ fontSize: '10px' }}> <tr className="text-gray-600" style={{ fontSize: '10px' }}>
<th className="text-left font-normal pb-1 pr-3"></th> <th className="text-left font-normal pb-1 pr-3"></th>
{measures.map(m => ( {measures.map(m => (
<th key={m.key} className="text-right font-normal pb-1 px-2 whitespace-nowrap"> <th key={m.key}
className={`text-right font-normal pb-1 px-2 whitespace-nowrap
${m.key === heldKey ? 'text-gray-400' : ''}`}>
{m.label} {m.label}
{m.hint && <span className="text-gray-500"> · {m.hint}</span>} {m.key === heldKey
? <span className="text-gray-400"> · holds</span>
: m.hint && <span className="text-gray-500"> · {m.hint}</span>}
</th> </th>
))} ))}
{showRunning && ( {showRunning && (
@ -676,6 +691,7 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
<LedgerInput <LedgerInput
value={shown[m.key]?.[field] ?? ''} value={shown[m.key]?.[field] ?? ''}
active={active} active={active}
held={m.key === heldKey}
onChange={(raw) => setEdit(m.key, field, raw)} onChange={(raw) => setEdit(m.key, field, raw)}
onFocus={() => focusRow(m.key, field)} onFocus={() => focusRow(m.key, field)}
suffix={field === 'pct' ? '%' : null} suffix={field === 'pct' ? '%' : null}
@ -716,10 +732,7 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
<div className="flex flex-col gap-2 mt-3 pt-3 border-t border-gray-100"> <div className="flex flex-col gap-2 mt-3 pt-3 border-t border-gray-100">
{valueCol && unitsCol && dollarsOnly && ( {valueCol && unitsCol && dollarsOnly && (
<Field label="Absorbed by" <Field label="Absorbed by">
hint={scalePlug === 'price'
? `${unitsCol} holds; price moves to reach the number.`
: `Price holds; ${unitsCol} scales with the dollars.`}>
<Segmented <Segmented
value={scalePlug} value={scalePlug}
onChange={setScalePlug} onChange={setScalePlug}