Skip to content

gui: the batch screen stops slowing down - a control is wired once, and the minimal set remembers each format's smallest size - #128

Merged
donislawdev merged 4 commits into
mainfrom
gui/listeners-once
Sep 23, 2026
Merged

donislawdev merged 4 commits into
mainfrom
gui/listeners-once

Conversation

@donislawdev

@donislawdev donislawdev commented Sep 23, 2026

Copy link
Copy Markdown
Owner

What was wrong

Measured in the real window (Windows, hardware OpenGL) with a probe that drives it through the toolkit's own queue and times a heartbeat on its one goroutine:

  • The Start from a preset switch on Several batches got slower with every press: 0.7 s at the first, 6.1 s at the twentieth.
  • With a preset switched on, every key typed took ~0.4 s and allocated ~379 MB.

Two causes:

  1. Fields.listen and Fields.counter wrapped a control's change callback on every registration. The batch screen registers every field again on every rebuild (an address carries the batch's position) and keeps the controls (so typed text survives). After k rebuilds one change was reported k times, under every address the control had ever had, and a size box counted into every caption it had ever been drawn with.
  2. Every report re-settled the form, and with a preset on that expanded empty-and-minimal, which works out the smallest size of each format by planning it - encoding the pictures. 50.4 MB allocated per expansion.

What changed

  • wiredOnce / chainOnce (internal/gui/parts/fields.go): the handler is put into a control once and re-pointed at the latest address and caption on every registration. The state lives on the control (Entry, Chooser, Toggle), not in a map on Fields, which would keep a removed batch's boxes alive.
  • preset.smallest remembers each format's smallest size per process, by id (format.Register refuses a second descriptor under one id). 0.51 MB per expansion.
  • No change to recipes, manifests or bytes: the same number is returned, so the expanded recipe is the same.

Measured after (interleaved with main, two runs each)

main this branch
preset switch, median 357-369 ms 25-49 ms
key typed with a preset on, median 1.7-1.9 s ~0 ms (3-4 ms on the window's thread)
CPU for the whole probe run 33.6-34.3 s 2.5 s
process memory after typing 509-510 MB 363-382 MB

Guards

  • TestAControlRegisteredAgainReportsOnceUnderItsLatestAddress - a box, a menu and a switch registered five times report once, under the last address, and count into the last caption (red before the fix).
  • TestNoControlIsRegisteredUnderTwoAddressesAtOnce - the assumption the fix rests on, asked of all three work screens, the batch screen with three batches, archive contents and a preset.
  • TestTheMinimalSetIsWorkedOutOnceAndNotAtEveryExpansion - a second expansion stays under 5 MB.
  • All new guards proven by mutation (5/5), three moved mutation entries re-proven (7/7). crowdedDepthFunctions ratcheted 50 -> 49.

Not in this PR: most of the window's remaining memory is fonts parsed once per theme-override scope (26 sets of ~7.4 MB). That is the next, separate change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Updates and format selections on the Several batches screen no longer trigger repeated recalculations for every box, reducing work and memory use while typing.
    • Control changes and byte counts are now reported for the currently selected setting, avoiding duplicate or misplaced updates.

donislawdev and others added 3 commits September 23, 2026 17:23
…atest address, and the minimal set remembers the smallest size of each format

The batch screen registers every field again on every rebuild and keeps the
controls. Fields.listen and Fields.counter wrapped the control's callback on
every registration, so after k rebuilds one change was reported k times,
under every address the control had ever had, and a size box counted into
every caption it had been drawn with. The handler is now put in once
(wiredOnce, kept on the control) and pointed at the latest address and
caption on every registration.

With a preset switched on, every change expanded empty-and-minimal, which
works out the smallest size of each format by planning it - encoding the
pictures. preset.smallest remembers it per format id: 50.4 MB allocated per
expansion before, 0.51 MB after.

Measured in the real window, interleaved, two runs each: the preset switch
357-369 ms -> 25-49 ms, a key typed with a preset on 1.7-1.9 s -> ~0 ms,
the process after typing 509 MB -> 363-382 MB.

Guards: a control registered five times reports once under its last address
and counts into its last caption, no control stands under two addresses on
any screen, and a second expansion of the minimal set stays under 5 MB.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…tes, so listen nests four deep

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 37bc265e-4ae0-494f-afc8-4df9de6d847d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: f8300f20-1ebc-4f10-9242-9982cf1b6a33

📥 Commits

Reviewing files that changed from the base of the PR and between 0886700 and 7afcef0.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • internal/guard/branching_test.go
  • internal/guard/livecheck_test.go
  • internal/guard/minimalset_test.go
  • internal/gui/parts/entry.go
  • internal/gui/parts/fields.go
  • internal/gui/parts/ring.go
  • internal/gui/parts/toggle.go
  • internal/preset/emptyandminimal.go

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (14)
  • GitHub Check: test on windows-latest
  • GitHub Check: linters
  • GitHub Check: bill of materials
  • GitHub Check: test on macos-latest
  • GitHub Check: coverage gate
  • GitHub Check: reference tools actually installed
  • GitHub Check: semgrep
  • GitHub Check: import table of the window binary
  • GitHub Check: staticcheck
  • GitHub Check: known vulnerabilities
  • GitHub Check: test on ubuntu-latest
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (go)
🧰 Additional context used
📓 Path-based instructions (10)
Applies to text shown to the user (labels, buttons, tooltips, placeholders, dialogs, errors, status messages, empty states, translations).

⚙️ CodeRabbit configuration file

Files:

  • internal/gui/parts/ring.go
  • internal/guard/minimalset_test.go
  • internal/preset/emptyandminimal.go
  • internal/guard/branching_test.go
  • internal/gui/parts/entry.go
  • internal/gui/parts/toggle.go
  • internal/gui/parts/fields.go
  • internal/guard/livecheck_test.go
Verify tests check real behavior and would fail if the implementation were broken.

⚙️ CodeRabbit configuration file

Files:

  • internal/guard/minimalset_test.go
  • internal/guard/branching_test.go
  • internal/guard/livecheck_test.go
Performance is a known weak spot of these projects.

⚙️ CodeRabbit configuration file

Files:

  • internal/gui/parts/ring.go
  • internal/guard/minimalset_test.go
  • internal/preset/emptyandminimal.go
  • internal/guard/branching_test.go
  • internal/gui/parts/entry.go
  • internal/gui/parts/toggle.go
  • internal/gui/parts/fields.go
  • internal/guard/livecheck_test.go
Applies only to code that builds or styles a GUI.

⚙️ CodeRabbit configuration file

Files:

  • internal/gui/parts/ring.go
  • internal/guard/minimalset_test.go
  • internal/preset/emptyandminimal.go
  • internal/guard/branching_test.go
  • internal/gui/parts/entry.go
  • internal/gui/parts/toggle.go
  • internal/gui/parts/fields.go
  • internal/guard/livecheck_test.go
User-facing changelog.

⚙️ CodeRabbit configuration file

Files:

  • CHANGELOG.md
SECURITY, HIGH PRIORITY.

⚙️ CodeRabbit configuration file

Files:

  • internal/gui/parts/ring.go
  • internal/guard/minimalset_test.go
  • internal/preset/emptyandminimal.go
  • internal/guard/branching_test.go
  • internal/gui/parts/entry.go
  • internal/gui/parts/toggle.go
  • internal/gui/parts/fields.go
  • internal/guard/livecheck_test.go
Go code.

⚙️ CodeRabbit configuration file

Files:

  • internal/gui/parts/ring.go
  • internal/guard/minimalset_test.go
  • internal/preset/emptyandminimal.go
  • internal/guard/branching_test.go
  • internal/gui/parts/entry.go
  • internal/gui/parts/toggle.go
  • internal/gui/parts/fields.go
  • internal/guard/livecheck_test.go
Check that documentation matches the actual code in this PR: commands, flags, config keys, file paths, build steps and examples must exist.

⚙️ CodeRabbit configuration file

Files:

  • CHANGELOG.md
All code in this repository is written by an AI coding agent (Claude Code).

⚙️ CodeRabbit configuration file

Files:

  • internal/gui/parts/ring.go
  • CHANGELOG.md
  • internal/guard/minimalset_test.go
  • internal/preset/emptyandminimal.go
  • internal/guard/branching_test.go
  • internal/gui/parts/entry.go
  • internal/gui/parts/toggle.go
  • internal/gui/parts/fields.go
  • internal/guard/livecheck_test.go
Source excerpt: **Words a user reads are English, with a flat hyphen and no semicolons.**

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Files:

  • CHANGELOG.md
🔇 Additional comments (6)
internal/gui/parts/entry.go (1)

37-43: LGTM!

internal/gui/parts/ring.go (1)

201-203: LGTM!

internal/gui/parts/toggle.go (1)

48-50: LGTM!

internal/guard/livecheck_test.go (1)

4-16: LGTM!

Also applies to: 261-406

internal/guard/branching_test.go (1)

60-64: LGTM!

internal/gui/parts/fields.go (1)

417-428: 🎯 Functional Correctness

The finding is refuted. b.sizeWay is a *parts.Segments control and is passed to Fields.Named, not Fields.Add or Fields.AddToggle. Fields.Named does not call listen, and Fields.listen only wraps Entry, Chooser, and Toggle. Therefore chainOnce never installs a reporting wrapper on b.sizeWay, so reassigning its OnChanged cannot remove one.


📝 Walkthrough

Walkthrough

The change installs GUI reporting and byte-count callbacks once per control while updating their targets on registration. It also caches each format’s smallest accepted size by format ID. Tests cover reporting targets, control registries, and allocations across repeated preset expansions.

Changes

GUI and preset updates

Layer / File(s) Summary
Control reporting callbacks
internal/gui/parts/*, internal/guard/livecheck_test.go, internal/guard/branching_test.go
Controls install reporting and byte-count callbacks once, then use the latest registered setting. Tests cover re-registration, field registries, and the updated depth-function ceiling.
Preset size cache
internal/preset/emptyandminimal.go, internal/guard/minimalset_test.go, CHANGELOG.md
The smallest accepted size is cached by format ID. An allocation test measures repeated expansions, and the changelog describes the update.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: bug, performance, ui

Merge Risk: ⚪ Minimal · up to 7afce

Batch-screen controls now report each change once under their current setting, and preset activation computes each format's smallest size once. No outstanding defects were found, and the change appears ready to merge.

🚥 Pre-merge checks | ✅ 14
✅ Passed checks (14 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the two main changes: wiring GUI controls once and caching each format’s smallest size. It is specific, user-relevant, and within the approximate 140-character limit.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Tests For Changed Behavior ✅ Passed The PR changes non-UI runtime behavior in Fields.listen/counter and preset.smallest, and it adds tests that cover both behaviors. livecheck_test.go verifies single reporting under the latest a…
No Secrets Or Debug Leftovers ✅ Passed No prohibited files were added. The changed paths contain no .env, CLAUDE.md, CLAUDE.local.md, AGENTS.md, or .claude/ entries. Added-line scans found no credentials, tokens, URLs, absolute l…
No Hardcoded Ui Styling ✅ Passed The PR changes Fyne GUI control wiring in internal/gui/parts, but the added code only stores reporting state and chains callbacks. It does not add literal colors, fonts, font sizes, margins, padding…
No Obvious Performance Problems ✅ Passed No clear performance problem was introduced. Fields.listen and Fields.counter now install one callback per control and only update its current target, removing callback growth during rebuilds. `pr…
Desktop Robustness ✅ Passed PASS. The changed production code only rewires in-memory GUI callbacks and adds an in-memory sync.Map cache for existing minimal-size calculations. The diff adds no working-directory asset loads, fi…
Safe File Parsing ✅ Passed No changed code reads, imports, exports, or parses files. The production changes use in-memory GUI callbacks and a sync.Map cache for computed format sizes. The added tests create controls and selec…
System Changes Are Reversible ✅ Passed PASS — The PR changes GUI callback wiring, format-size caching, tests, and the changelog. The authoritative diff contains no network filters, proxy, firewall, system-time, process-hooking/injection, W…
Clear User-Facing Text ✅ Passed The production diff changes callback wiring and preset-size caching. It does not add or change a user-facing control, icon-only button, error, confirmation, or tooltip. The only user-visible prose add…
No Resource Leaks ✅ Passed No resource leak is introduced. Fields.listen and Fields.counter now use chainOnce, which installs one callback per control and only updates its current target on later registrations (`internal/…
Scope, Duplication And Docs ✅ Passed The reviewed diff contains only the stated callback-registration fix, per-format minimal-size cache, related guard tests, a depth-threshold adjustment, and a CHANGELOG entry. The new wiredOnce/`chai…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
✨ Simplify code
  • Commit to this branch
  • Create a new PR

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

@coderabbitai coderabbitai Bot added bug Something isn't working performance ui labels Sep 23, 2026
…, where concurrency is already declared

CI on #128 was red on every system with one guard:
TestConcurrencyStaysWhereItWasPutOnPurpose - the preset package had grown a
sync.Map of its own. The memory moves to format.SmallestWithLabel in
registry.go, which is already declared as the place the registry's reads
meet its writes, and sits beside Register, whose refusal of a second
descriptor under one id is what makes the id a safe key. The size is worked
out without the lock held, because planning an archive reads the registry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@donislawdev
donislawdev merged commit 404f5df into main Sep 23, 2026
20 checks passed
@donislawdev
donislawdev deleted the gui/listeners-once branch September 23, 2026 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working performance ui

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant