Skip to content

fix(console): show each log line once and stop following on user input - #1951

Merged
chsami merged 1 commit into
developmentfrom
claude/console-followups
Oct 13, 2026
Merged

chsami merged 1 commit into
developmentfrom
claude/console-followups

Conversation

@chsami

@chsami chsami commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Follow-ups from the review of #1939: console duplicates, follow/scroll behaviour, the pending buffer, and sidebar tab restore.

Problem

  1. Every log line appeared twice in the console. This predates Feat/client UI console and panel state #1939. Logback's STDOUT ConsoleAppender (ConsoleTarget.SystemOut) looks up System.out again on every write. Once ClientUI.initializeConsoleLogging replaced System.out with the console tee, each logback line reached the panel twice: once through the tee and once through ConsoleLogAppender.
  2. Wheel-up could be undone during a flood. A snap that was already queued still saw followTail == true and pulled the view back down. There was a second path too: each setCaretPosition(end) made while following queues a deferred DefaultCaret.adjustVisibility, which scrolls to the caret no matter what followTail says by then.
  3. Keyboard navigation and drag-select never stopped following. PgUp, the arrow keys, Ctrl+Home and selecting text did not stop it, so the next batch jumped to the bottom and its caret move cleared the selection.
  4. The pending buffer did too much work. While following, a starved EDT could insert up to 400k chars and then trim 300k of them straight away. Every append past the cap also ran an O(n) StringBuilder.delete under the appender lock.
  5. The sidebar restore was not one-shot. If the saved tab's plugin was disabled at startup and enabled later in the session, its panel opened without being asked for. Removing the open tab's plugin also left the removed tab saved instead of the tab now on screen.

Change

  • ConsoleLogAppender.bindStdoutAppenders, called from initializeConsoleLogging, points every System.out ConsoleAppender at the original stdout stream. Logback lines now reach the panel only through ConsoleLogAppender, and the terminal still gets them once. Plain System.out prints still go through the tee. System.err was not captured before and still is not.
  • LogConsolePanel:
    • Wheel-up sets followTail = false straight away; wheel-down still re-checks after the scroll.
    • A key press in the text area stops following. The re-check runs two event passes later, so it reads the view after the caret's deferred scroll (Ctrl+End resumes following).
    • A mouse press or drag in the text area stops following, and the re-check runs on release.
    • Following resumes only when the view is at the bottom and there is no selection.
    • The text area gets a DefaultCaret subclass that skips adjustVisibility for the caret position the console set itself. Caret moves made by the user still scroll.
    • The pending buffer is compacted only after it grows past 2x the scrolled-up cap, so the cost per character stays constant. A flush inserts only the tail the document keeps: 100k chars while following, 400k while scrolled up.
  • ClientUI (upstream file, small diff):
    • sidebarTabRestored is set at the end of show()'s EDT runnable. That runnable runs after the navigation buttons queued by startPlugins, so the restore only happens at startup.
    • removeNavigation saves the tab the user now sees after the fallback openPanel.
    • The save logic moved into saveSidebarTab().

Testing

  • New ConsoleLogAppenderTest: one log line plus one println, each counted in the panel and in the terminal.
  • New LogConsolePanelTest with 6 cases: wheel-up against an already-queued snap/caret update, PgUp, Ctrl+End resuming after PgUp, drag-selection surviving a batch, wheel-down resuming, and a starved EDT given 1M+ chars.
  • Each of these failed before the fix. Against origin/development, 5 of the 7 failed, deterministically across 3 runs: dedupe (expected:<1> but was:<2>), PgUp, drag-select, starved EDT (largest insert 400000), wheel-up. Also checked:
    • without the caret override, only the wheel-up test fails;
    • with a single deferral on key press, only the Ctrl+End test fails.
      wheelDownToTheBottomResumesFollowing is a guard against over-correction and passes on both.
  • ./gradlew :client:runUnitTests --offline: 2344 passed, 2 skipped (ShaderTest, EssencePouchOverlayTest), 0 failed. The ClientThreadGuardrail baseline was unchanged.
  • Live test with the shaded jar, using a probe plugin that posts real AWT events (wheel, keys, mouse drag) to the client frame while flooding about 9k println lines/s for about 20 s (187,100 lines):
    • Dedupe: a logger line showed up x1 and a plain println x1 in the console. The terminal stdout still got the logback lines.
    • Wheel-up mid-flood: the view stayed off the bottom 2 s and 4 s later (scrollbar value 18951 both times).
    • 3x PgUp: stayed off the bottom at 2 s and 4 s. Ctrl+End then resumed following (back at the bottom, document capped at 100k again).
    • Drag-select mid-flood: the selection (258 chars) was still there 3 s later, and the view was not pulled to the bottom.
    • Following, the largest single insert was 23,946 chars (8,598 inserts in total).
    • Sidebar, over three restarts closed through WINDOW_CLOSING (not a kill):
      • the tab saved as Configuration was restored at startup;
      • with the Hiscore tab open, disabling HiScore switched to Configuration and saved Configuration (the old code skipped saving during the removal, so Hiscore stayed saved; read from the code, not re-run);
      • with Hiscore saved and the plugin disabled at startup, enabling it mid-session left the sidebar with no tab selected.
    • Original config values were restored afterwards. Auto Login logged the test account in at each of the 3 starts.

Not verified / known limits

  • Scrolled up with a selection, the 400k cap still trims from the front. Under the flood the content (and the selection's offsets, because of NEVER_UPDATE) shifts under a view that stays put; the probe saw the selected text change while its length stayed the same. This predates this PR and only matters when more than 400k chars arrive while scrolled up.
  • A plugin that adds its navigation button after startup (outside the startup EDT queue) no longer has its saved tab restored. This follows from making the restore one-shot.
  • Not tested on macOS or Windows, and not with trackpad precise-wheel events.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV

Logback's STDOUT ConsoleAppender resolves System.out on every write, so
after ClientUI swapped System.out for the console tee every log line
reached the panel twice: once through the tee and once through
ConsoleLogAppender. Bind System.out console appenders to the original
stdout when the console is installed; plain System.out prints still go
through the tee.

Wheel-up, key presses and mouse presses/drags in the text area now stop
following immediately; following resumes only when the view is back at
the bottom with no selection. The caret moves made while following no
longer scroll the view from their deferred visibility update, which
could undo a wheel-up.

The pending buffer is compacted only once it doubles and a flush inserts
at most what the document keeps (100k chars while following).

The saved sidebar tab is restored only during startup, and removing the
open tab's plugin stores the tab that is shown instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjtEMzQHGuq4M1Mpqk9pPV
@coderabbitai

coderabbitai Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 5 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 19096d8a-d685-449d-a613-e2d0c4b1e599
📥 Commits

Reviewing files that changed from the base of the PR and between 77eec06 and ab64623.

📒 Files selected for processing (5)
  • runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java
  • runelite-client/src/main/java/net/runelite/client/ui/ConsoleLogAppender.java
  • runelite-client/src/main/java/net/runelite/client/ui/LogConsolePanel.java
  • runelite-client/src/test/java/net/runelite/client/ui/ConsoleLogAppenderTest.java
  • runelite-client/src/test/java/net/runelite/client/ui/LogConsolePanelTest.java
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

1 participant