Skip to content

Make long alert messages scrollable instead of silently clipped - #2833

Closed
Natalie-the-technician wants to merge 1 commit into
bardsoftware:masterfrom
Natalie-the-technician:alert-scroll
Closed

Natalie-the-technician wants to merge 1 commit into
bardsoftware:masterfrom
Natalie-the-technician:alert-scroll

Conversation

@Natalie-the-technician

Copy link
Copy Markdown
Contributor

UIFacade.showOptionDialog renders plain-text messages with
Alert.setContentText. That path goes through DialogPane.createContentLabel,
which hard-codes label.setPrefWidth(360). The dialog therefore never grows
sideways: a long message only grows downwards until the window manager clips it
at the bottom of the screen, and the label's default ELLIPSIS overrun drops
the remainder. Nothing is logged and nothing is offered — the only hint is the
trailing ellipsis — and the buttons can be pushed off the screen along with the
text.

Measured on a 1024x768 screen with a 2398-character message: the dialog stops
at 360x708, its top edge already at y=0, and 986 characters (41 %) are
unreachable. Two of the five listed items are not visible at all.

This puts the plain-text message in a ScrollPane:

  • Short messages keep the 360px column and the layout they have today.
    Measured at three call sites (ProjectUIFacadeImpl,
    NetworkOptionPageProvider, ProjectOpenStrategy): 360x149 before,
    360x152 after — same width, three pixels taller, no scroll bar, no box.
  • Long messages become scrollable and the buttons stay on the screen: the
    same message now measures 360x541 and every line can be reached.
  • Each line becomes its own Label whose leading spaces become left padding,
    so a wrapped line hangs at the depth of the line it continues instead of
    falling back to the left margin.

Two details of modena.css matter here and are commented in the code. The
content label is padded only while it is a direct child of the dialog pane, so
the padding has to be restored inside the scroll pane. And -fx-background
must be left alone: -fx-text-background-color is a ladder() over it, so
setting it to transparent picks -fx-light-text-color and turns every label
inside the scroll pane white on white.

The four alert types are untouched — the change sits after the alertType
branch and before the button types are set. Icon, header and buttons were
compared before and after for INFORMATION, WARNING, QUESTION and ERROR.

Testing: AlertMessageLinesTest covers the line splitting — indent, blank
lines, whitespace-only lines, CRLF, empty input — in seven tests that need no
toolkit and no window. The full suite passes: 382 tests, 0 failures. What the
tests do not cover is the resulting window geometry: the sizes quoted above
were measured on screen with xdotool getwindowgeometry, not asserted in a
test.

showOptionDialog hands plain-text messages to Alert.setContentText, which
routes them through DialogPane.createContentLabel. That method hard-codes
label.setPrefWidth(360), so the dialog never grows sideways: a long message
only grows downwards, until the window manager clips it at the bottom of the
screen. The label's default ELLIPSIS overrun then drops the rest with no
indication beyond a trailing ellipsis, and the buttons can be pushed off the
screen along with it.

Put the message in a scroll pane instead. Short messages keep the familiar
360px column and the layout they have today; long ones become scrollable and
the buttons stay reachable. Each line becomes its own label whose leading
spaces are turned into left padding, so a wrapped line hangs at the depth of
the line it continues instead of falling back to the left margin.

The line splitting is covered by AlertMessageLinesTest.
@dbarashev
dbarashev self-requested a review September 2, 2026 22:08
@dbarashev

Copy link
Copy Markdown
Contributor

Hi, I am a bit concerned about that string-splitting code in this PR. I asked Claude if we could solve the problem in a simpler way, and it came up with this code:
master...alert-scroll-simple

It seems to work, at least in my tests, and I could not see any clearly visible difference compared to your PR. Can you look at my branch and if it solves the problem, then maybe we can just accept it

@dbarashev

Copy link
Copy Markdown
Contributor

@Natalie-the-technician review ⬆️

@Natalie-the-technician

Copy link
Copy Markdown
Contributor Author

Thanks for the pointer to alert-scroll-simple, and sorry for the long silence.

I took the branch seriously and measured it instead of defending mine. Three results.

1. Your version does the job, and I would rather ship it than mine.

I built alert-scroll-simple (5d4c4ce) and measured the preferred height of the dialog content — the number DialogPane sizes itself from — for a 200-line plain-text message, at the fixed 360 px content width, on a 1920x1080 screen:

content height it asks for
setContentText as it is today 6417 px, on a 1080 px screen
your branch 648 px, exactly 0.6 x screen height
... with the rest still reachable 5769 px below the fold (5769 + 648 = 6417)
a short message today 33 px
the same short message on your branch 33 px

Long messages stop at the cap with the whole message still reachable; short messages keep the size they have today, to the pixel. That is the whole of what #2833 set out to do, in 60 lines instead of my 95.

I ran the same four tests against my own branch as a control. Same four greens, and the same five numbers to the decimal — the two versions are indistinguishable on everything I could measure. Yours is shorter.

On screen, on a 1024x768 display, your branch gives the same window your reviewers would have got from mine, to the pixel:

window position scrollbar
today 360 x 708 pinned at y=0, bottom edge of the usable screen no
your branch 360 x 541 y=84, buttons visible yes

Scrolled to the end, the message finishes on the sentence it is supposed to finish on, and the two list items that were unreachable before are fully readable. A mid-length message in the same run came out 360 x 341 — under the cap, no scrollbar, sized to its content.

2. My tests did not survive the move, and that is a fair criticism of them.

Dropping my AlertMessageLinesTest onto your branch does not fail — it does not compile. All 20 errors are cannot find symbol: splitMessageLines and cannot find symbol: class MessageLine. The tests were tied to the string-splitting helper you were uneasy about, not to any behaviour of the dialog. So they were not evidence that the splitting was needed; they were part of the same problem.

3. I rewrote them as behaviour tests, and they pass on your branch.

AlertContentScrollTest, four tests, needing a toolkit but no visible window:

  • a long message does not outgrow its share of the screen;
  • the part below the fold stays reachable (5769 px of it, in the case above);
  • a short message keeps the height it has today;
  • negative control — the content label that DialogPane.setContentText builds does outgrow the screen with the same message. Without it, the other three could go green on a build where nothing works at all.

They need one change on your branch: the plain-text arm of showOptionDialog moved into a package-private createScrollableContent(String) so a test can reach it. Four lines moved, no behaviour touched.

What I dropped, and why it costs nothing. My version turned leading spaces into a hanging indent, so that a wrapped line stayed at the level of the line it continued. A single wrapped Label cannot do that. I checked every plain-text call site on master: five of them, and not one passes an indented or multi-line message. The longest is openLastDocument.question, one interpolated file name and a blank line. The feature had no caller, so dropping it is free.

Happy to push the tests onto #2833 on top of your version, or to close #2833 if you would rather carry the change yourself — whichever is less work for you.

One more thing worth flagging: your branch also routes the HTML/Markdown arm through the scroll pane, which is the subject of #2836. If alert-scroll-simple lands as it stands, that PR is largely redundant. Tell me how you would like the two split.

@dbarashev

Copy link
Copy Markdown
Contributor

@Natalie-the-technician, I merged my changes. If you find it worthwhile to port your tests on top of that, go ahead :)

@Natalie-the-technician

Copy link
Copy Markdown
Contributor Author

Thanks — and your branch is the right one to have landed.

On porting the tests: I can't, and I think that is the correct outcome rather
than a problem. AlertMessageLinesTest only exercises splitMessageLines and
the MessageLine record, i.e. exactly the string-splitting code you were
concerned about in your first comment. Neither name exists anywhere in the tree
on master now (I checked: zero hits). Porting them would mean reinventing them,
so I am closing #2833 rather than reworking it.

What I can offer instead is tests for what your version actually promises, none
of which is covered today — the merge adds no test file. Three things look worth
pinning down, and the last two are ones you added after the branch I measured on
16 Sept, so they are newer than anything I have numbers for:

  • a long plain-text message stops at MAX_CONTENT_HEIGHT_RATIO of the screen
    and a short one is left exactly as it was (on the earlier branch I measured
    6417 px -> 648 px for 200 lines, and 33 px unchanged for a short message)
  • screenOf() picks the screen the owner window is on, not the primary one,
    and falls back to the primary when the window centre is off-screen
  • the scroll pane is focus-traversable, so a keyboard-only user can reach and
    scroll an overflowing message

The first is a headless preferred-height assertion like the numbers above. The
other two are small and do not need a display at all.

Do you want them? I am happy to open a PR with just those, or to drop them into
a comment here if you would rather not carry a test-only PR. Your call — I did
not want to send one unasked.

@dbarashev

Copy link
Copy Markdown
Contributor

Not sure that focus-traversability is worth testing, but otherwise, why not

@Natalie-the-technician

Copy link
Copy Markdown
Contributor Author

The two tests are in #2848 — the height cap and the screen the cap is taken
from. Focus traversability is left out, as you said.

It is test code only; UIFacadeImpl.java is untouched.

Closing this one. Its own tests only exercise splitMessageLines and the
MessageLine record, and neither name exists on master any more, so there is
nothing here left to port — 16b5f21 is the version to have landed.

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.

2 participants