fix(gui): stop the settings scroller leaving its content, and fill the table when columns are hidden - #130
Merged
Conversation
…ave the content
Reported: dragging the Settings window scrollbar upwards pushed everything down
and left a blank band above the first setting.
Cause, measured on real Tk: viewport 855 px, content 452 px, scroll region
"0 0 807 452". Tk holds a view inside its region only while the region is TALLER
than the window - with a shorter region the canvas moves its origin above the
content, and yview keeps reporting (0.0, 1.0) while it does. One scroll-up put
canvasy(0) at -360. After the fix the region reads "0 0 807 855" and the same
scroll leaves canvasy(0) at 0.0.
The wheel never showed this because ScrollableFrame.scroll() guards on
can_scroll(), while the scrollbar is wired straight to canvas.yview and never
passed through that guard. Fixing the region instead of duplicating the guard
covers the wheel, the scrollbar and any future caller at once.
A second fault in the same lines, found while fixing the first: bbox("all")
returns None on an empty canvas and configure(scrollregion=None) CLEARS the
region, which is the same unconfined canvas by another route.
- clamped_scrollregion() is pure, because the fake tkinter canvas has no
canvasy/bbox/scrollregion and cannot express a drifted origin - so the guard
is on the arithmetic, and the test says why
- both callers now share _apply_scrollregion; the canvas handler clamps to
event.height, since winfo_height can still report the old size mid-resize
- four tests in test_wheel_and_scroll.py, two MUTATIONS entries, both confirmed
caught by internal_tools/mutate.py
- verified no regression on the Control page, whose content IS taller than its
viewport: region unchanged at "0 0 493 1199", down still scrolls, up clamps
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reported: hiding columns in Connections left the remaining ones huddled on the left with bare background beside them. Cause: this table refuses ttk stretch deliberately, because ttk recomputes a stretch column on the next <Configure> and a dragged width visibly snaps back. The cost of that decision only shows once columns can be hidden - nothing was absorbing the space they left. Measured on real Tk in a 1076 px tree: 17 columns sum to 1611 px, so the horizontal scrollbar has work and that is correct, but 2 columns sum to 205 px and leave 871 px of dead space. fitted_widths() is pure and carries four rules, each with its reason: - only ever WIDEN, so a width the user dragged to is never taken back - the very property that refusing stretch was protecting - share the slack in proportion to NATURAL width, so a column born wide stays the wide one instead of a PID column growing as wide as a path - cap each column at MAX_WIDTH_FACTOR x natural, the same ceiling clamp_widths applies after a drag, so a fit cannot reach a width a drag could not - when the caps bind, leave the remainder as dead space rather than break one Measured after: 17 columns unchanged; 4 columns - the reported case - fill exactly, slack 0; 2 columns leave 461 px and 1 leaves 674 px, every column standing on its 3x cap. The residue is the cap working, and only one or two narrow columns can reach it. The memo (_fitted_for) is a rule rather than an optimisation: a fit is asked for when the visible set changes or the widget is resized, and a column drag changes neither, so the chosen width stands. Verified live - narrowing a column to 90 px and calling fit_columns() leaves it at 90. - seven tests in test_virtual_tables.py, six on the pure function and one on the memo through the fake tkinter, whose Treeview.column does work as a getter (checked first, so the assertion can actually fail) - three MUTATIONS entries, all three confirmed caught Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The field rendered "50000 rows (0 = off) 0 = no limit" - the unit and the hint both explaining what zero does, which is convention 1b broken in the smallest possible way. Checked rather than assumed: max_size (fields.unit_b_off) and nat_timeout (fields.unit_s_off) carry the "(0=off)" inside their unit precisely because they have NO hint to carry it. row_limit was the only field in the registry with both, so the convention for the others is left alone. - fields.py points row_limit at a bare fields.unit_rows - fields.unit_rows_off removed from both language files; its only consumer was fields.py, verified across the whole tree with zero hits in tests/ or site/ - renders as "Row limit: [50000] rows 0 = no limit" and "Limit wierszy: [50000] wierszy 0 = bez limitu" Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test_the_repository_scanners_stay_out_of_what_is_not_in_the_repository asserted that HANDOFF-UI-CLI.md is not scanned - a brief deleted on 2026-08-10. repo_text_files could never return it whatever SKIP_PREFIXES held, so emptying that tuple changed nothing and the guard still read as proof. The only thing that said otherwise was internal_tools/mutate.py, reporting SURVIVED for "guards: a HANDOFF brief falls back into the scanned set". The test now plants a HANDOFF-scanner-probe.md for the length of the scan, so the skip has something to skip, and removes it in a finally. The name is covered by the existing HANDOFF-*.md ignore rule, so the tree stays clean, and the test refuses to run rather than overwrite a file of that name that already exists. Its content carries an em-dash built with chr(0x2014): a literal one is impossible here, because this file is repository text and is scanned by the very rule it defines. Measured after: the mutation is caught. A suspicion the measurement killed, recorded so nobody re-opens it: the neighbouring loop looked like the same class of fault, comparing prefixes with a backslash against paths normalised to forward slashes. It is not - the source uses forward slashes and, checked by patching SKIP_DIRS by hand, that loop reddens correctly with 65 leaked files. Nothing was changed there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The note has claimed lang/*.json are sorted since they existed and nothing checked it. One pair was out of order in both files identically - log.driver_wait before log.driver_still_unloading - and the same pair is out of order in HEAD, so this predates the current work. Swapped at byte level rather than by rewriting the JSON: rewriting a text file on Windows turns LF into CRLF, the repo is LF by .gitattributes, and the tree then looks dirty for a reason the diff does not explain. Verified after: one line changed per file, zero CRLF, both files still parse and hold exactly the same keys and values. test_the_language_files_stay_sorted also checks _meta comes first, which needs no exception because it sorts before every dotted key on its own. The guard was shown able to fail before being believed: swapping the pair back out of order turns it red, restoring it turns it green. Filed in PROVEN_BY_HAND rather than MUTATIONS, because a re-runnable patch would mean pasting the two enormous log.driver_* lines twice into the registry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two reported interface faults, and three things found while fixing them.
Reported
The Settings window scrolled off the top of itself. Dragging its scrollbar upwards pushed
everything down and left a blank band above the first setting.
Cause, measured on real Tk: viewport 855 px, content 452 px, region
0 0 807 452. Tk holds a viewinside its region only while the region is TALLER than the window - with a shorter region the canvas
moves its origin above the content, and
yviewkeeps reporting(0.0, 1.0)while it does. Onescroll-up left
canvasy(0)at -360. After: the region reads0 0 807 855and the same scrollleaves it at 0.0.
The wheel never showed this because
ScrollableFrame.scroll()guards oncan_scroll(), while thescrollbar is wired straight to
canvas.yviewand never passed through that guard. Clamping theregion instead of duplicating the guard covers the wheel, the scrollbar and any future caller at
once.
Hiding columns left the Connections table half empty. The table refuses ttk
stretchdeliberately - ttk recomputes a stretch column on the next
<Configure>and a dragged width visiblysnaps back - and the cost of that decision only shows once columns can be hidden. Measured in a
1076 px tree: 17 columns sum to 1611 px, so the horizontal scrollbar has work and that is correct,
but 2 columns sum to 205 px and leave 871 px of bare background.
fitted_widths()carries four rules, each with its reason: only ever WIDEN, so a width the userdragged to is never taken back; share the slack in proportion to natural width, so a column born
wide stays the wide one; cap each column at three times its natural width, the same ceiling a drag
gets; and when the caps bind, leave the remainder rather than break one. The memo (
_fitted_for) isa rule too, not an optimisation - a fit is asked for when the visible set changes or the widget is
resized, and a column drag changes neither, so the chosen width stands.
Measured after: 4 columns, the reported case, take the full width; 2 columns leave 461 px and 1
leaves 674 px, every column standing on its cap.
Found on the way
An assertion that could not fail, inside the guard that guards the guards.
test_the_repository_scanners_stay_out_of_what_is_not_in_the_repositoryasserted that a maintainerbrief is not scanned - by naming a file deleted in August. The scanner could never have returned it
whatever the skip list held, so emptying that list changed nothing and the guard still read as
proof. Only the mutation runner said otherwise. The test now plants a brief for the length of the
scan, so the skip has something to skip, and the mutation is caught.
The language files were not sorted, and nothing checked. One pair was out of order in both files
identically, and the same pair is out of order in the merge base, so it predates this work. Swapped
at byte level, because rewriting a text file on Windows turns LF into CRLF and the repository is LF.
The new guard was shown able to fail before being believed.
The row limit said the same thing twice -
50000 rows (0 = off) 0 = no limit. Checked ratherthan assumed: the two other fields with an "(0=off)" unit have no hint to carry it, so
row_limitwas the only one with both. The unit is now bare.
Verification
reporting BROKEN as it must. The registry had one survivor before this branch.
PrintWindow, not reasoned about: the settingswindow sits flush at the top after a scroll-up, and the four columns span the table.
the content height, scrolling down works and scrolling up clamps at 0.
Said out loud rather than left to be discovered
winfo_width(), and a Treeview's usable width is 4 px less -2 px of border a side, constant, checked at two different widths. So
xviewreads0.9963and thehorizontal scrollbar stays technically active over those 4 px. Not corrected by subtracting 4: that
constant belongs to the theme and the platform and cannot be checked on the Linux runner, which is
exactly the class of thing a Windows-only check waves through. There is no visible difference.
not an oversight - lifting it would mean a fit reaching a width no drag could.
the same vacuous-assertion fault, comparing path prefixes with a backslash against paths normalised
to forward slashes. It is not - the source uses forward slashes, and patching the skip list by hand
reddens that loop correctly with 65 leaked files. Nothing was changed there.
those rows apart are the hidden ones.