diff --git a/ui/src/components/OperationPanel.jsx b/ui/src/components/OperationPanel.jsx
index 45e770a..a4df1de 100644
--- a/ui/src/components/OperationPanel.jsx
+++ b/ui/src/components/OperationPanel.jsx
@@ -166,45 +166,7 @@ function scopeText(viewScope = []) {
.join(' · ')
}
-// The selection, in one line you can open. What it always shows is what is not
-// visible anywhere else: how many slices, what they total, and the pivot filter
-// they sit inside -- which scopes every figure in the ledger and every row the
-// write touches, while appearing in none of the slices themselves.
-function SliceSummary({ slices, viewScope, distinctSlices, currentTotals, open, onToggle }) {
- const scope = scopeText(viewScope)
- const one = slices.length === 1 ? sliceLabel(slices[0]) : null
- const total = currentTotals
- ? (currentTotals.total?.value || 0) + (currentTotals.excluded?.value || 0)
- : null
- const dupes = distinctSlices != null && distinctSlices < slices.length
-
- return (
-
-
- {one ? '1 slice' : `${slices.length} slices`}
-
- {one && {one}}
- {total != null && (
- {fmtNum(total)}
- )}
- {scope && (
-
- within
- {scope}
-
- )}
- {dupes && (
-
- {distinctSlices} distinct
-
- )}
- {open ? '▾ hide' : '▸ peek'}
-
- )
-}
-
-function SelectionList({ slices, viewScope = [], currentTotals, onRemove, onClear }) {
+function SelectionList({ slices, viewScope = [], distinctSlices, currentTotals, onRemove, onClear }) {
const multi = slices.length > 1
const perSlice = currentTotals?.perSlice || []
const valueCol = currentTotals?.valueCol
@@ -226,6 +188,15 @@ function SelectionList({ slices, viewScope = [], currentTotals, onRemove, onClea
return (
+ {/* Cells that differ only by a column operations cannot filter on collapse
+ to the same slice — say so, rather than implying more reach than there is */}
+ {distinctSlices != null && distinctSlices < slices.length && (
+
+ {slices.length} cells cover {distinctSlices} distinct{' '}
+ {distinctSlices === 1 ? 'slice' : 'slices'} — some differ only by a column
+ operations cannot target. The duplicates are applied once.
+
+ )}
{scopeLine && (
within
@@ -1450,13 +1421,10 @@ export default function OperationPanel({
{hasSlice && (
-
-
setPeek(o => !o)} />
- {opBusy && writing rows…}
- submitOp(activeOp)} busy={!!opBusy}>
- {opBusy ? POSTING : POST}
-
+
+ setPeek(o => !o)}>
+ {peek ? '▾' : '▸'} {slices.length} {slices.length === 1 ? 'slice' : 'slices'}
+
)}
@@ -1476,6 +1444,7 @@ export default function OperationPanel({
setSlices(prev => prev.filter((_, x) => x !== i))}
onClear={() => setSlices([])}
@@ -1506,6 +1475,23 @@ export default function OperationPanel({
{labelZone}
)}
+
+ {/* Last, because it is last: pick the operation, read the ledger, name
+ the change, post it. The blank label keeps Post in the same column as
+ every other button in the panel rather than at an edge of its own.
+ Sticky, because the bottom dock is a fixed height with its own scroll
+ -- a primary action that has to be scrolled to is a primary action
+ people miss. */}
+ {hasSlice && (
+
+
+ submitOp(activeOp)} busy={!!opBusy}>
+ {opBusy ? POSTING : POST}
+
+
+ {opBusy && writing rows — the pivot updates when it finishes}
+
+ )}
)
}