Skip to content

History window: cap a row's badges, count the rest in words, hint the whole line - #151

Merged
AliceLafox merged 7 commits into
mainfrom
fix/history-row-badges
Oct 17, 2026
Merged

AliceLafox merged 7 commits into
mainfrom
fix/history-row-badges

Conversation

@AliceLafox

Copy link
Copy Markdown
Member

Closes #143.

The first-sighting row painted a badge for every one of its 99 slots over its own words, and the hit test answered everywhere on it, so a click anywhere silently filtered the window to a slot.

  • One function (app/HistoryBadges.h) decides which badges a row wears; painting and the hit test both read it, so they cannot drift apart. A snapshot row wears none and says "99 slots" in the badge strip instead; a multi-slot operation shows the first badges and a "+N" chip that is not a filter target. A slot filter keeps the filtered slot in sight even when it would sit behind the chip.
  • Every row has a hint with its whole line (when, slots, action, detail, state, audio, pinned); the History window's host owns the TooltipWindow it needs, since the main window's cannot reach a view in another window.
  • The detail is cut in the middle, not at the end (app/MiddleEllipsis.h), so "nothing to do" survives; the cut never splits a grapheme cluster.
  • show() refuses a row whose slots are not the pedal's (outside 1..99, duplicates, unsorted); a double-click inside the badge strip never plays.
  • One hint delay for both windows (app/HintDelay.h).

Tests: history_badges_tests (53), middle_ellipsis_tests (2183), history_window_tests (303, the snapshot row built through rows::forCard). Non-GUI ctest 58/58; the six GUI harnesses 6/6 locally; a headless --history-window render on a fixture card shows the snapshot row as "99 slots · Card first seen · 0 takes, 0 bytes".

The History window painted a badge for every slot an operation touched,
but the layout only made room for three. The card's first sighting
touches all 99, so 96 badges ran over the action, the detail and the
audio column (#143). The hit test walked the same unbounded list, and a
click anywhere on that row silently filtered to a slot.

Which badges a row wears is now one answer, history::badges::shown,
read by both the painting and the hit test: up to three slots, then a
"+N" chip in the last position counting the rest, and none at all for a
snapshot, whose sentence already says "99 slots". The chip is a number,
not a filter; the counted slots still answer the slot filter, and a
restore out of a snapshot still goes by the filtered slot.

A click in a row is now a mouse-free action, clickAt, so the tests can
press the chip and see nothing happen.
A detail that did not fit was cut at its end with an ellipsis, which is
exactly the part that matters: a normalize that says "already peaking at
the -1 dBTP ceiling (measured -19.2 LUFS), nothing to..." lost the
"nothing to do" (#143).

elideMiddle fits a sentence into a width by replacing as little of its
middle as has to go with one ellipsis, the tail never shorter than the
head. It is pure: the width of a string comes from a function the
painting site provides, so the fit is tested through a stand-in face,
and every candidate is measured, so what comes back has been seen to
fit — or is the ellipsis alone when not even that does.
With the detail cut to the row's width, the rest of a sentence could be
read nowhere: the window had no hints at all (#143). Each row now
answers getTooltipForRow with its whole line — when, action, detail,
state, audio — joined by a dash, the parts that are empty left out.

A juce::TooltipWindow shows tips only for components sharing its own
peer, so the main window's never reached a view living in its own
desktop window. HistoryWindowHost, the frame the view lives in, owns one
now, with the main window's delay so the two feel alike.
…red slot in sight

The snapshot row wore no badges, but nothing said its slots in words
either: rows::forCard words the row as "Card first seen" and "<N>
takes, <bytes>", and the window's test had typed "99 slots" into the
detail by hand (#143, review). Now a row counted in words paints
"<N> slots" across the badge strip in the chip's quiet ink, and its hint
carries the same words. The test feeds a snapshot row worded by
rows::forCard from a 99-slot entry, so the words are proven to be the
window's own.

Filtering to a slot behind the chip left the row with no violet badge,
so nothing said why it was listed. The filtered slot now takes the last
badge position in place of the row's slot there — the chip keeps its
count, one slot out of hiding as one went in — and the hit test reads
the same answer.

The hint also says what the row paints and the strip only counts:
"slots 3, 7, 12, 40, 99" for a row with a chip, and "pinned" for a
pinned row. badgeStripText reads the strip in words — the badges' numbers,
the chip, or the words — from the same list the painting draws.
… in the strip from playing

show() took any row: slot 0, slot 100, a slot twice, slots out of
order. The badge for such a slot then threw from inside JUCE's mouse
dispatch when clicked, far from the mistake (#143, review). The store
hands slots ascending, each once, and the pedal has 1..99 of them; a row
that says otherwise is now refused by its op before the window changes.

A double-click returned early only where badgeAt answered, so a
double-click on the chip, or on the "99 slots" words, played the row.
The guard is now the whole badge strip — badge, chip or words, the first
click has answered — and a double-click past it still plays. doubleClickAt
is the mouse-free version, like clickAt, so the tests can press it.
elideMiddle cut by character, so a cut could land between a letter and
its combining accent, inside a family joined by zero-width joiners, or
between the two letters of a flag: valid UTF-8 that renders wrong, and
take names from macOS often come decomposed (#143, review).

Each side of the cut now pulls back to the cluster it would have cut
into — the head's end down, the tail's start up — over the clusters
UAX #29 has for what names and sentences carry: combining marks,
variation selectors, skin-tone modifiers, the joiner and what follows
it, and regional-indicator pairs. The fit is still measured, candidate
by candidate.

"One ellipsis" means one put in: a sentence carrying its own keeps it,
and the comment now says so.
The History window's frame had its own 600 next to the main window's
600 (#143, review). kHintDelayMs lives in HintDelay.h now, and both
windows read it.
@AliceLafox
AliceLafox merged commit 7204dab into main Oct 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

History window: a 99-slot snapshot paints its badges over the whole row

1 participant