Skip to content

feat(gui): find a setting on the Control page by name - #131

Merged
donislawdev merged 4 commits into
masterfrom
feat/control-search
Aug 18, 2026
Merged

feat(gui): find a setting on the Control page by name#131
donislawdev merged 4 commits into
masterfrom
feat/control-search

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

The Control page renders the whole field registry - a dozen sections, forty-odd fields, several of them folded away on a fresh install - and there was no way to reach a setting except by opening every section and reading. This adds a search box for it.

What it does

Type part of a field or section name, or a flag such as --loss. Every match is highlighted, the one you are on is filled in, and the counter in the corner points at it. Enter walks forward, Shift+Enter back, Escape clears, Ctrl+F puts the caret in the box.

  • It marks and jumps, it does not filter. Hiding what does not match would fight the page's own layout: fields are packed left to right in rows, so a hidden field leaves a hole, and sections are spread over two columns by weights computed at build time, so sections disappearing rebalances the page under the reader.
  • A folded section opens for the search and folds back afterwards. Through set_open, never toggle - toggling runs the accordion's callback, which persists the fold state, so searching would permanently unfold sections the user had chosen to keep closed.
  • Names and CLI flags, not tooltip bodies. Searching the tips would make one word light up half the page, which is a symptom index rather than a way to find a field.
  • Accents are optional. Polish labels carry diacritics and people type without them, so matching folds them away through NFKD. Without this "opoznienie" finds nothing while the accented spelling works, and no user can tell which half of the feature is broken.
  • A field that renders in the Settings window is named, not missed. It is indexed but never offered as a jump, because this page cannot scroll to it.

Structure

gui/form_search.py holds the matching: registry plus the current translation in, entries in page order out. It has no Tk, so it is tested directly instead of through the fake-Tk subprocess. The page owns the highlighting, the scrolling and the fold rules.

The index is built after the language is set rather than at import time - a language switch rebuilds the UI precisely so nothing keeps the old words. The query survives that rebuild (and the two-column one) and dies with the process; ControlForm gained on_rebuilt so the marks can be put back on widgets that no longer exist.

Ctrl+F now goes through one dispatcher. Both pages with a search box bind it on the root, and a root binding without add= REPLACES the one before it, so the page built second would otherwise have taken the shortcut away from the other. From a page with no box it keeps the older behaviour and brings the connection table forward.

What the live pass found

The fake tkinter has one widget class for everything and never validates a style name, so the whole feature was also driven on real Tk. That is where the last defect came from: two hits can claim the SAME widget - a dropdown has no text of its own so it marks its section header, and that header is often a hit itself. It was painted as the current match and then repainted as an ordinary one by the later hit, so "1 / 5" appeared with nothing filled anywhere. The second claim on a widget is dropped now, which is what keeps the count honest: as many marks as it says, each in its own place.

Guarded

  • 8 tests in tests/test_form_search.py (pure): the index covers every registry entry, blank and punctuation-only queries find nothing, case and substring matching, section names, CLI flags in three spellings, page order, the Settings-window split, and an accented label reachable without its accents.
  • 12 in tests/test_gui_layout.py: marking and clearing, current versus other and its wrap-around, the highlight not reusing the page accent, the section-header fallback, both fold rules, the "it lives in the Settings window" answer, the empty result, the widget collision, surviving a form rebuild, one Ctrl+F reaching whichever box is in front, and the Settings window - which renders the same ControlForm - having no search box.
  • 4 new entries in the mutation registry, all caught. One existing entry was replaced: it used to break the table's Ctrl+F by moving the binding off the root, and with two pages binding the same dispatcher that no longer breaks anything, so it now breaks the dispatcher's decision instead.

Full suite 1156 passed, smoke_gui.py OK, mutation registry 106 caught with the canary BROKEN, both real-driver smokes clean.

donislawdev and others added 4 commits August 18, 2026 10:35
The Control page renders the whole registry - a dozen sections, forty-odd
fields, several folded away on a fresh install - and there was no way to reach
a setting by name. This is the pure half of that: `build_index` turns the
registry plus the current translation into entries in page order, `find`
matches a query against them, `summarise` separates what this page can jump to
from what lives in the Settings window.

Matching covers field and section NAMES plus CLI flags, deliberately not tooltip
bodies: searching the tips would make one word light up half the page, which is
a symptom index rather than a way to find a field.

`fold()` strips diacritics through NFKD before comparing. Polish labels carry
them and people type without them, so without this "opoznienie" finds nothing
while the accented spelling works - a half-broken feature nobody can diagnose
from the outside.

The index is built after the language is set rather than at import time: a
language switch rebuilds the UI precisely so that nothing keeps the old words,
and an import-time index would keep answering in them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page renders the whole registry and there was no way to reach a setting
except by opening every section and reading. A box at the top now marks what
matches and scrolls to it: Enter walks the matches, Escape clears, Ctrl+F puts
the caret in the box.

It marks and jumps rather than filtering. Hiding what does not match would fight
the page's own layout - fields are packed left to right in rows, so a hidden
field leaves a hole, and sections are spread over two columns by weights
computed at build time, so sections disappearing rebalances the page under the
reader.

Revealing a hit uses set_open, never toggle. Toggling runs the accordion's
callback, which persists the fold state, so searching would permanently unfold
the sections a user had chosen to keep closed. The folds are snapshotted once
per search - not per keystroke, or the second letter would record what the first
letter opened - and restored when the box is cleared.

A field that renders in the Settings window is indexed but never offered as a
jump: the page says where it lives instead of coming back empty.

The query survives a rebuild (a language switch rebuilds the UI, the two-column
threshold rebuilds the form) and dies with the process. ControlForm gained
on_rebuilt so the marks can be put back on widgets that no longer exist.

One dispatcher now owns Ctrl+F. Both pages bind it on the root, and a root
binding without add= REPLACES the one before it, so the page built second would
otherwise have taken the shortcut away from the other. From a page with no
search box it keeps the older behaviour and brings the connection table forward.

The mutation that used to break the table's Ctrl+F survives now for a good
reason - two bindings mean losing one changes nothing - so it was replaced by
one that breaks the dispatcher's decision instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both READMEs describe the window as it is, so a visible feature that is not in
them reads as one that does not exist. The shortcut table gained Ctrl+F, which
now means "search" on both pages that have a box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things the search got wrong, all reported after using it.

With several matches nothing said which one you were on: Enter moved the page
and every hit looked the same, so the counter pointed at a place the eye could
not pick out. Marking is two-strength now - the current match is filled, the
rest are tinted - and Shift+Enter walks back.

The highlight was the page accent, which is the colour of every section header,
link and "?" button, so it disappeared into the page. Hits are amber now: the
one meaning this page had left, with blue for the accent, green for running, red
for a fault and pink for the support button.

The bar moved to the right, against the page margin. The row is built so nothing
shifts while typing: the count is pinned to the margin at a fixed width, the box
and its label sit left of it, and the sentence naming another window is a
separate label further left, free to grow into empty space.

Found on real Tk while checking those: two hits can claim the SAME widget - a
dropdown has no text of its own so it marks its section header, and that header
is often a hit itself. The header was painted current and then repainted as an
ordinary match, so "1 / 5" showed with nothing filled anywhere. The second claim
on a widget is dropped, which is also what keeps the count honest: as many marks
as it says, each in its own place. The fake tkinter cannot catch this class -
one widget class for everything, no style validation.

Two more edge cases: the page now tracks what the SEARCH opened instead of
snapshotting what was closed when it started, so a section the user opens by
hand mid-search stays open; and unmarking hands the last word to
apply_overrides, so a style that changed while a field was marked cannot be
restored into a stale one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit d13213c into master Aug 18, 2026
8 checks passed
@donislawdev
donislawdev deleted the feat/control-search branch August 18, 2026 13:08
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