Give ToggleButton a style: neither native theme defined it - #5757
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae2406c15e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Native fidelity (Android, Material 3)54 pairs compared -- median 95.6%, worst 91.3% ( Distribution --
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Side-by-side comparisons (worst first)
|
Cloudflare Preview
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a202cfe18f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 36ce34171f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7edb34e4c4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a82e157ee8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44c3478f9c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e63432278
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e20e36db1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d85e7ecdfc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5bd6b2976e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a889c4ff4b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 144 screenshots: 144 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a69692520
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 217 screenshots: 217 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17fc52d2ad
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de9ec003d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cdfcdcab7c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 364d35f3ae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1754b039b5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50f63a2c45
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1cc25b766
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6d2f22004
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ff087588f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22f499fa02
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 181 screenshots: 181 matched. |
The state audit in the previous commit only closed the light block. Its dark pass skipped any UIID that had dark state overrides but no dark base entry, and six did: the five media transport aliases and MenuButtonRight, each carrying a dark pressed and disabled from an earlier round and nothing else. A dark state that does not exist resolves to the LIGHT rule of the same name -- createStyle falls back to the original id when no $Dark entry is found -- so focusing one of those controls in dark mode picked up the light accent, #6750a4 against a dark surface on Material. All six now carry a dark base and a dark selected taken from the dark FlatButton they derive from, and the audit is re-run over both themes: no UIID this change adds has partial coverage in either block. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous audit asked the wrong question again. It looked for UIIDs with partial dark coverage, which only finds names that already have some dark entry. A name with no dark entry at all and a hard-coded light colour in its light states is broken in exactly the same way and was invisible to that check: with no $Dark rule the light one is what resolves, so PopupItem.selected painted #1d1b20 on the dark popup and the disabled TimeSpinner labels sat at #49454f on the dark surface -- both nearly unreadable, both reported. The audit is now the right one: for every UIID this change adds, every state whose light rule names a colour must have a dark rule. The 32 missing ones per theme are generated by mapping the light body through the theme's own light-to- dark token pairs, and any colour the map does not cover is printed rather than passed through silently. Two came up and both are correct as they stand: GlassTutorial's rgba(0,0,0,0.72) scrim, which is a dark overlay in both appearances behind white text, and #000000, which only appears in dark rules that predate this change. Separately, MenuButtonRight no longer derives FlatButton. SideMenuBar drops that icon-only button into the TitleArea's east slot, where a content button's horizontal padding and pill widen the title bar and crowd the centred title. It derives TitleCommand now, which is the compact treatment that slot expects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Re-pointing MenuButtonRight at TitleCommand last commit meant deleting the states that had been expanded from FlatButton, and I did not regenerate them, so it went back to a base rule with nothing behind it -- the same defect the audit had just closed, reintroduced by hand two commits later. Both audit scripts are re-run here rather than the states being written out again by hand. TODAY was derived from the wrong thing entirely. DateTimeRenderer extends SpinnerRenderer and swaps its own UIID to TODAY for the current date, restoring the original afterwards, so TODAY is a spinner row that happens to be today -- not a calendar cell. Deriving CalendarDate gave it different padding and typography from the rows above and below it, and a transparent selected state where the wheel expects its selection band, so the row changed size as it scrolled past. The two themes need different answers because only one of them themes the row. iOS defines SpinnerRenderer, so TODAY derives it there and overrides nothing but the accent colour and the weight. Android Material does not define SpinnerRenderer at all -- its spinner rows take the default -- so styling only today's row would make that one row the odd one out, which is the same defect from the other side. TODAY is simply not defined in the Material theme. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two bugs in the audit tooling, both of which hid work rather than doing it. The driver read its UIID list from the Material theme's diff and applied it to both files, so any name that exists in only one theme was never checked. TODAY is now iOS-only, which is exactly how it came back with a base rule and no states one commit after the audit reported zero gaps. The list is the union of both diffs now. The generator expanded cn1-derive from the base UIID's *base* rule for every state, so TODAY.selected inherited SpinnerRenderer rather than SpinnerRenderer.selected -- and SpinnerRenderer.selected is where the picker wheel's selection band lives, the pill background and its margins. Today's row would have scrolled through the wheel without the band the other rows get. The expansion is state-aware now: X.selected takes B.selected when B defines one and falls back to B otherwise. While regenerating, the expanded bodies are also deduped -- a base declaration followed by the derived UIID's override of the same property is correct CSS but leaves a dead value in the rule, and one of those dead values was a #000000 that kept surfacing in the unmapped-colour report and would eventually have masked a real one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…led itself Both reported rules are symptoms of one bug in the generator. The palette map was applied with a chain of str.replace calls, and Material maps #49454f to #cac4d0 and #cac4d0 to #49454f. Run in order, the first substitution produces a value the second turns straight back, so every on-surface-variant foreground came out of the generator still holding its light value: the disabled TimeSpinner labels at #49454f on the #141218 dark surface, and 35 rules like them. The map is one simultaneous regex pass now, which is the only form that is correct when the palette contains a swap. The iOS half had a plain omission -- no entry for #000000, which is what Label carries, so PopupItem's expanded states painted black text on the dark popup. I had seen #000000 in the unmapped-colour report and dismissed it as belonging to rules that predate this change. It did not; it was in the generated ones too. Fixing the two values in place turned out to be the wrong instinct and I undid it: replacing every #49454f in my dark rules also hit the 16 that were correct, having come from a light #cac4d0, and put a light outline on the dark popup panes. The dark side for these UIIDs is regenerated wholesale from the light rules instead, so one rule produces it rather than a mix of hand edits and patches. Regenerating surfaced four more colours the map did not cover, all of them iOS values reachable only through an expanded derive -- the picker wheel's selection band, FlatButton's pressed fill and glass border. Each is mapped to the value the theme's own dark block already uses for that surface. The report is empty now, including GlassTutorial's scrim, which is declared an identity rather than left to reappear every run: a permanently-noisy report is what let #000000 through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The iOS selected toggle painted a hard-coded white label over a fill bound to var(--accent-color). Both themes expose an on-accent token precisely so an app can replace the accent at runtime through addThemeProps and have the text follow it; the existing rules in each theme already pair the two. Hard-coding the foreground means an app that supplies a light accent gets white on light. Enumerating rather than fixing the reported rule found 36, evenly split. iOS: the four ToggleButton names in selected and pressed, plus CalendarDate.selected, in both appearances. Material had the same defect from the other direction -- ComboBoxFocus, CommandFocus and PopupFocus put a literal on-surface colour over var(--accent-container-color), so overriding the accent container would have left the focused row's text at the old value. Each foreground now uses the token the theme itself pairs with that fill: --accent-on-color over --accent-color, --accent-on-container-color over --accent-container-color, and their dark counterparts where the theme declares them. iOS declares one on-accent token and uses it in both appearances, which the dark map now records as an identity so regeneration preserves it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The macOS UI suite went red on this branch: AppReviewDialog, ChatView light and dark, ListTheme light and dark, and ValidatorLightweightPicker all differ from their references, and master is green at the commit this branch is based on, so the failures are mine. Those references are the project's own regression guard for shipped screens, and 154 of the 160 tests still match, so this is not a wholesale theme shift -- it is a handful of my definitions changing components that already looked the way someone intended. Two are visible in the diffs and neither is a bug fix. ListTheme's focused row had a blue focus ring and my ListRendererFocus replaced it with a filled band, which is a style opinion about an appearance that was working. AppReviewDialog's command text changed because DialogCommandText, absent before, now centres and accents it. ChatView's messages shift vertically and I could not attribute that to a specific rule by reading the CSS -- which is the point: I was inferring causes from test names rather than knowing them. So the sweep is cut back to the names whose gap is demonstrable rather than plausible: the ToggleButton family, which is the bug this PR was opened for; FloatingHint and ErrorLabel, where the framework seeds ErrorLabel, InputComponentAction and DescriptionLabel to derive from a UIID that nothing defines; and TableCell and TableHeader, where the seed sets transparency and no padding and the guide's own table figures show the difference. Twenty-nine other names go back to being undefined. They may well deserve styling. What they do not deserve is a sweep that changes how shipped screens look with no before-and-after for any of them, which is what this had become. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ToggleButtonFirst, ToggleButtonLast and ToggleButtonOnly are written as pure aliases -- cn1-derive: ToggleButton and nothing else -- and the dark generator skipped exactly that shape on the reasoning that an alias inherits its base. It does, but it inherits the LIGHT base: cn1-derive in the light block names the light rule, and dark resolution falls back to the light entry when no $Dark one exists. So an unselected edge segment of a group kept the light capsule on the dark surface, #1d1b20 text on #141218 in Material. All three now carry a dark base written out from the dark ToggleButton, and the generator no longer treats an alias as safe to skip -- it looks for the base's dark rule for that state and copies it. The audit is tightened to match. It was asking whether every light rule that names a colour has a dark counterpart, which an alias passes trivially by naming no colour at all. It now requires all four states in both blocks for every UIID the change adds, and reports zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Button.isPressedStyle() sends a checked toggle to press# when it has no focus and to sel# when it does, and an unchecked toggle with focus lands on sel# too. That one slot therefore says "focused" for either value, and cannot say which value. An earlier round moved the checked fill into press#, correctly, but left the same fill in sel# -- so on a keyboard or TV device, moving focus onto an unchecked radio painted it exactly like the checked one beside it, and the actual value disappeared. sel# is now the base appearance with an accent ring: focus stays visible and never claims a value the control does not have. The cost is that a focused checked toggle shows the ring rather than the fill for as long as it holds focus, which is a framework limit -- one style slot, two dimensions -- and the lesser of the two, because it understates the focused control rather than inventing a selection that is not there. The figure needed the same care. Focus lands on the first focusable control, so the sample's checked CheckBox came out ringed and looked unchecked. Trying to render in pure touch mode did not help: shouldRenderSelection() also returns true when the last interaction was a keypad. The figure focuses an unchecked control instead, so the two filled rows read as checked and the ring reads as focus, and the caption says which is which. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The focus ring changed both toggle figures, and the copies committed with it were local renders. Java2D rounds font metrics differently here than on the runner, so those never match CI byte for byte. Both are replaced with the run's own artifact, and all 48 committed figures now match that run exactly, none of them leaning on a tolerance sidecar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The review is right that a focused checked toggle is now indistinguishable from a focused unchecked one, and its own conclusion is the answer: preserving both needs separate state resolution, which is not something a theme can express. sel# is one slot and focus plus value are two dimensions, so every CSS assignment loses one of them. The two losses are not equal, and the choice between them is now written where the next person will meet it rather than in a review thread. Putting the checked fill in sel# makes a focused unchecked control claim to be selected -- arrowing through a radio group lights up every control in turn. Putting the base plus a ring there makes a focused checked control understate itself while it holds focus. The second never asserts a value the control does not have, so it stays, and the comment says explicitly not to move the fill back, because that trade has been made once already in this branch. The real fix belongs in Component.getStyle(): resolve focus separately from state so a checked control keeps press# and focus is drawn some other way. That is a framework change with its own blast radius, not a line in a theme. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ts base The disabled rules on FloatingHint and ErrorLabel carried a foreground and nothing else. createStyle does not fill the rest in from the UIID's base -- a state entry with no derivation is built from the global default style -- so disabling a TextComponent dropped the hint's font size, padding and margin and resized the field's layout as it went. The generated states did not have this problem because they were expanded from the base when they were written. These four were hand-written earlier in the branch, and the audit only asked whether a state rule exists. Existing is not the same as complete, which is the third distinct way this branch has managed to report zero: a missing state, a state missing in only one theme, and now a state that is present but says almost nothing. Every state rule of every UIID this change adds is now merged against its base: the base's declarations first, the state's own after, so a deliberate override still wins. ToggleButton's accent ring survives that merge, which is the case worth checking, and the audit is extended from "does the state exist" to "does it carry its base's property set". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Component.getStyle() returns getDisabledStyle() before it ever consults isPressedStyle(), so a disabled toggle resolves dis# whether it is checked or not. The review is right that disabling a form therefore hides which of its toggles were on, and right again that a theme cannot fix it: dis# is one slot and it is being asked to carry both enabled-ness and value. The answer is the one already recorded for sel#, so it is recorded in the same place rather than argued twice. Muting the unselected appearance declines to state a value; muting the checked fill would state a wrong one on every disabled unchecked control. Declining is the lesser failure. Both cases want the same framework change and neither is patched here: getStyle() would have to combine state with focus and with enabled, rather than collapsing three dimensions onto one lookup, and that reaches every focusable component in the framework. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
iOS dark FloatingHint and ErrorLabel came out at #38383a, which measures 1.45:1 against the theme's #1c1c1e surface -- the number in the review, and unreadable. The cause is a limit of the light-to-dark table: the light value it came from, #c6c6c8, is both a separator and a disabled label in this theme, and the table maps by value, so a colour with two roles gets whichever mapping was written for the other one. #636366 is systemGray2 in dark and measures 2.84:1, nearly double, while staying clearly below the #8e8e93 the enabled hint uses, so disabled still reads as disabled. The table entry is corrected too, since no separator among these UIIDs uses that light value any more. Material was checked rather than assumed: its dark disabled sits at 2.73:1, the same band, and matches the convention its own Button.disabled already sets, so it is left alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Defining ToggleButton makes two long-standing bugs in Button.setToggle visible,
which is what the review is pointing at, so they are fixed with it.
setToggle(false) never restored the UIID. A CheckBox or RadioButton taken back
out of toggle mode kept ToggleButton, so it painted the toggle's pill and
padding while drawing its own state glyph again. Invisible while nothing defined
ToggleButton; not invisible now. The UIID it had before entering toggle mode is
remembered and put back, and setUIID clears preferredSize, so the size computed
without the glyph is recalculated on the next pass.
The condition was also mis-parenthesised: && binds tighter than ||, so
if (toggle && "CheckBox".equals(getUIID()) || "RadioButton".equals(getUIID()))
reads (toggle && isCheckBox) || isRadioButton, and leaving toggle mode assigned
ToggleButton whenever the UIID was RadioButton at that moment -- the exact
opposite of the intent.
Both are covered by tests, and the tests were checked against the old code
rather than assumed: with the original condition restored, both fail. The first
version of the precedence test did not, because a control that was never a
toggle returns at setToggle's equality guard and never reaches the condition, so
asserting on a fresh RadioButton proved nothing. It now enters toggle mode and
sets its own UIID first, which is the path an application takes to get there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
check-copyright-headers went red on RadioButtonTest. The file never had a header, and neither does CheckBoxTest beside it, but the gate only looks at files a PR touches -- so adding tests to it brought a pre-existing omission into scope. It takes the Codename One GPLv2 with Classpath Exception header, not the Oracle one, since the file is ours. CheckBoxTest is left alone: it is not part of this change, and fixing it here would be unrelated churn in a PR that has already grown once. The gate was not re-run after the core commit, which is why CI found this rather than I did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
setHorizontal(true) on a ComponentGroup sets its element prefix to ToggleButton, so its members are renamed ToggleButtonFirst, ToggleButton and ToggleButtonLast -- or ToggleButtonOnly when there is one. The guard added with the restore matched the bare name alone, so setToggle(false) silently skipped the restore for any grouped toggle, which is the common case for the control this whole change is about. Worse, the group records the UIID it replaced in $origUIID and puts that back when the control leaves it, so a control that had quietly failed to restore then had ToggleButton reinstated on the way out. setToggle now updates $origUIID alongside the UIID when one is present, so the group's own restore agrees with the control's state. Covered by a test that builds the horizontal group, checks the rename landed, disables toggle mode and then removes the control -- and the test was probed by narrowing the guard back to the bare name, which fails it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…erms A vertical ComponentGroup keeps the default prefix, so its members are renamed GroupElement, GroupElementFirst, GroupElementLast or GroupElementOnly -- names with nothing toggle-ish about them -- while $origUIID still holds ToggleButton. The previous guard matched the live UIID against a list of toggle names, so it skipped that case entirely, and removing the control afterwards put ToggleButton back on a plain radio. Enumerating GroupElement* as well would have been the third revision of the same list, so the list is no longer the mechanism. The toggle UIID can be held in two places and each is now corrected on its own terms: the live UIID when it is still a toggle name, and $origUIID when it holds one, whatever prefix the group happens to use. Neither depends on knowing the group's orientation. Both orientations are covered by tests, and each was probed against the revision it fixes: the vertical test fails against the live-UIID-only guard this commit replaces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A horizontal ComponentGroup renames every member -- toggle or not -- so the bar gets its segmented edges, and it does not re-apply that when a member's UIID changes underneath it. Resetting the live UIID on setToggle(false) therefore stripped that member's styling for as long as it took some later structural or theme update to put it back. Only $origUIID needs correcting while the control is grouped; the restore then happens where it always happens, on removal. $origUIID is not the test for "is grouped", which is the trap here: ComponentGroup sets it on the first rename and never clears it, not even in removeComponentImpl, so it means "was grouped at some point". The parent answers the question directly and is used instead. The horizontal test now asserts the group's alias survives setToggle(false) and that removal yields the plain UIID, and it was probed against the previous revision -- which resets the live UIID -- where it fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Only one direction was doing it. Leaving toggle mode moved $origUIID from ToggleButton back to the plain UIID; entering it looked at the live UIID, saw a group alias, recognised nothing and left $origUIID holding the plain name. So a control toggled off and on again inside a group was handed a radio UIID and its glyph on the way out -- the same defect as before, from the other side. Both directions now treat the two holders the same way. The live UIID is converted when it is a name this method owns, and the group's saved name is converted whenever it is out of step with the mode, in either direction. That also covers a plain radio added to a horizontal group and toggled afterwards, which never passed through the bare-name branch at all. Probed against the previous revision, where the round-trip test fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three macOS screenshot tests kept failing on screens that use none of the UIIDs this change adds -- ChatView in both appearances and ValidatorLightweightPicker. Narrowing the theme from 58 UIIDs to 8 did not move them by a single pixel: both runs produced byte-identical images, which is what finally ruled the CSS out. build-native-themes.sh prefers maven/css-compiler/target and falls back to ~/.m2. I never built the module, so every theme build in this branch used a jar some other checkout installed at some other commit. That jar does not merely compile the rules that changed -- it re-emits the entire theme its own way. The proof: compiling MASTER's own CSS with it produces a .res of 178627 bytes where master has 178705 committed; compiling the same CSS with the jar built from this tree reproduces master's file byte for byte. So the committed .res carried a compiler's worth of unrelated differences, and those are what moved the two screens. Rebuilt with the jar from this tree, the theme now differs from master only in the UIIDs this change adds -- 658 entries added, none removed, all of them ToggleButton*, FloatingHint, ErrorLabel, TableCell and TableHeader -- and the guide's other 46 figures are untouched. The script logs which jar it picked and I did not read it, but a silent fallback that changes output this much deserves more than a log line, so it now warns when the jar comes from ~/.m2 and says how to build the matching one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two findings, both correct, both from the same wrong assumption in the previous commit: that a ComponentGroup parent means the group has taken the UIID. It has not when the group is inactive. updateUIIDs() returns without renaming anything unless ComponentGroupBool is on or the group is forced, and that constant is off by default -- Android Material never sets it, only the iOS theme does. A toggle in such a group still carries its own ToggleButton UIID, so skipping the restore because the parent happened to be a group left a plain radio wearing the toggle style. The test is now whether the group recorded what it replaced, which is the thing that says it took ownership. And the grouped path was converting any saved UIID, not just the defaults. setToggle has only ever converted CheckBox and RadioButton -- an application that assigned its own UIID keeps it, which is what the original condition did and what the guide says. My version replaced a custom name with ToggleButton in the group's record, so removal handed back ToggleButton instead of the application's UIID. Only the two defaults are converted now. Both are covered, and both tests fail against the previous revision's guards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
d31a0e2 to
5128dcd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5128dcd526
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…moved
$origUIID outlives the ownership it records. refreshTheme with grouping switched
off calls restoreUIID, which hands the live UIID back to the control and leaves
that property set, so reading it as "the group owns this" kept answering yes
after the group had let go -- and setToggle(false) then skipped the restore,
leaving a plain radio painted as a toggle. I had noted that this property is
never cleared two commits before I started using it as the ownership test.
ComponentGroup now answers the question itself, with the same condition
updateUIIDs uses to decide whether to rename at all, so there is one definition
of "grouping is active" rather than a marker that approximates it. Covered by a
test that switches grouping off, refreshes, and then disables toggle mode; it
fails against the marker-based predicate.
The macOS ValidatorLightweightPicker golden is reseeded from the run's own
artifact. That screen builds PickerComponent.createDate(...).label("Birthdate"),
and a PickerComponent is an InputComponent whose label is a FloatingHint -- the
UIID that had been deriving from nothing -- so giving it a real style moves the
row and everything under it. The change is intended and the reference was stale.
Only that one golden moved. The same test has references for ten other ports,
and the iOS, JavaScript and Mac Catalyst suites all ran it on this branch and
matched, so the shift is specific to the AppKit port's layout of that row rather
than to the theme in general.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 21d75d61ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| boolean isGroupingActive() { | ||
| return getUIManager().isThemeConstant(groupFlag, false) || forceGroup; |
There was a problem hiding this comment.
Track actual group UIID ownership
When setForceGroup(true) is called after a toggle has already been added to an inactive group, the setter only flips forceGroup and never calls updateUIIDs(), so the child still owns its live ToggleButton UIID and has no $origUIID; this method nevertheless reports that the group owns it. A subsequent setToggle(false) therefore skips restoring RadioButton/CheckBox, and removing the component leaves the non-toggle control permanently styled as ToggleButton. Fresh evidence beyond the earlier ownership comment is that the final setForceGroup() implementation does not apply the group aliases, so ownership must track an actual rename (or activation must update the members), not just the configuration flag.
Useful? React with 👍 / 👎.
|
Compared 160 screenshots: 160 matched. Benchmark Results
Detailed Performance Metrics
|












































































































Button.setToggle(true)rewrites the UIID toToggleButton(Button.java:1043-1044), andCheckBox.createToggle/RadioButton.createTogglego through the same path. No theme in the tree defined that UIID — not iOS Modern, not Android Material — so every toggle button fell through to the default style and rendered as bare text with no shape.The Components chapter documents the feature and illustrated it with a 2015 screenshot from a theme that did define it, so the guide promised an appearance the shipped themes couldn't produce.
What each theme draws now
Selection changes the fill in both, not only the label color, so it doesn't rest on hue alone.
Why this needs no rasterized fallback
cn1-pill-borderalready carries a stroke:createRoundBorderreadsborder-top-color/border-top-widthand callsRoundBorder.stroke(...), andusesRoundBordershort-circuitsrequiresBackgroundImageGeneration.FlatButtonin both themes already relies on exactly that. Both themes compile clean understrictNoCef=true.Two conventions from the files are followed deliberately:
Stylemerge otherwise drifts the label by a pixel as the selection moves.cn1-background-typeand the border, for the reason the neighbouring dark button entries already give: the compiler doesn't carry either into the$Dark<UIID>entries.Blast radius
Checked before touching the themes:
fidelity-tests.yamlhas no toggle entry, theSamples/that usecreateTogglehave no committed baselines, and the cn1ss harness doesn't install these themes. The change adds a previously-undefined UIID, so the only rendering it can alter is a control that had no style at all.Guide side
The chapter's figure is regenerated from the sample that draws it, on both themes side by side, replacing the screenshot of a theme nobody ships.
COVERAGE.md's two "segmented control — not started" rows now say the UIID is themed and say plainly it isn't in the fidelity suite, because no measurement backs a score for it.Verification
Rendered both variants locally and looked at them. Guide gates: structure, xrefs, unused images, snippets, missing-code-blocks (34, unchanged),
asciidoctor --failure-level WARN,asciidoctor-pdf, Vale 0/0/0, LanguageToolstatus: ok0, paragraph capitalization, control characters, 16k alignment, copyright headers (4 files).Note the committed baselines came from a local run; the four table-style figures are known to differ from CI by a few pixels of height (Java2D font-metric rounding), so if the new pair mismatches there I'll reseed from CI output.
🤖 Generated with Claude Code