Fix conditional visibility, panel grouping, and the settings they silently dropped - #75
Merged
Merged
Conversation
Chroma Fixes was reported as unintuitive: every repair's tuning sliders were on
screen whether the repair was switched on or not. The grouping was not the cause
— none of the conditions were being read.
`visibleWhen` is declared only on ParameterUiConfig, so a schema that writes it
one level up has it dropped at parse time and the control is always visible.
That was 35 parameters across 8 schemas, plus 12 sections (UiSection has no
visibleWhen either), all written during the 2026-08-17 build-out. Beyond Chroma
Fixes it meant Noise Reduction showed mClean and TemporalDegrain2 knobs under
SMDegrain, SpotLess showed RemoveDirt's, Chroma Denoise showed CCD's under Cnr4,
and Deflicker showed both methods' at once. Section conditions are pushed down
onto their member parameters, which is the only place they work.
DynamicFilterPanelCompact also printed a heading only for an advancedOnly section
in advanced mode, so every ordinary section ran together into one flat list and a
`title` was in practice a comment. It now prints titles whenever a schema
declares more than one section; a single-section schema still gets none, since
there is nothing to distinguish and most of them call it "Settings".
With headings on screen for the first time, all 21 schemas were audited:
- Color Correction and Crop & Resize each declared methods that gated nothing.
No parameter was conditional on the choice, neither model has a `method`
field, and the converter hardcoded one value or emitted none — the dropdown
rendered, responded and did nothing. Crop & Resize was the sharper case: its
real upscaler choice is the `upscaleMethod` parameter, so the dropdown was an
inert duplicate of a working control. Both are single-method now.
- deblock's `quant1` was ungated while DCTFilter, which does not take it, was
one of the three methods on offer.
- Three schemas held a section containing only `method`, which renders nothing.
Titles that read as comments rather than headings are named after the filter
whose controls they hold ("CCD settings", "SpotLess settings", "SMDegrain").
Chroma Fixes is regrouped into its five actual repairs — a switch, the controls
you would reach for, and an advancedOnly tuning section per filter — and Color
Correction pairs each automatic control with the manual one it supersedes instead
of collecting them in a separate "Automatic" group.
filter_schema_curation_test lints all of it: nothing may declare visibleWhen
outside a parameter's ui, no section may carry one, every condition must name a
parameter that exists (a condition that can never be satisfied hides a control
forever, which is the worse failure), a schema with two or more methods must gate
at least one parameter on the choice, and a parameter used by a subset of methods
must name exactly those. Two panel tests pump the real schemas through the real
panel, which is the only level that can show what is on screen.
Three settings applied on top of a correction that had already been made, and one switch the worker never read. All silent. Chroma Fixes: `_auto_chroma_fix` measures the chroma misalignment and corrects it, and its block runs before the manual Y/C shift — so with both set the picture was shifted by a measured amount and then again by a number the user guessed. Color Correction: automatic levels measures the picture and places black and white itself, again running first, so manual input/output points on top graded an already-graded picture with numbers measured against the original. Gamma is deliberately kept, because automatic levels never touches the midtones and the Levels group is the only place in the app to reach them. Color Correction again: `apply_levels` — the panel's switch — was never read. `has_levels` was true whenever any level differed from its default, so unticking the switch left the adjustment running with nothing on screen able to stop it, since the sliders hide with the switch. White balance is the mirror case and stays composable: automatic white balance neutralises the cast and temperature/tint then offset the result deliberately. An offset still means what its label says after a correction; a mapping does not. pass_advice says which order they happen in rather than hiding anything. Precedence lives in one derivation per pair — effective_apply_chroma_shift, effective_levels_points, effective_apply_levels, and their Dart twins — so the panel, the generated script and the pass summary cannot disagree. None of them clears the stored value, so switching the automatic side off restores what the user set by hand. Covered by test_150/test_151 in the Rust suite and their Dart counterparts in integration_filter_parameters_test, which is the only side that can catch a @jsonvalue drifting from a serde name.
ProcessingPipeline.togglePass rebuilds a pass through copyWith, so a field the
method never gained is a field that reverts to its default the moment the user
flicks the pass switch. It compiles, it runs, and the pass then does something
other than what the panel says.
All 26 were added during the 2026-08-17 build-out:
- Colour Correction: all six automatic levels / automatic white balance fields
- Chroma Fixes: automatic alignment and DeDot, with their tuning — which VHS
Cleanup and DV Camcorder Tape turn on, so those presets lost settings to a
single click
- Noise Reduction: thirteen, including every mClean and TemporalDegrain2 value
- SpotLess: `method`, so RemoveDirt silently reverted to SpotLess
- QTGMC: bwdifEdeint
- Subtitles: burnInPath
parameter_copy_with_test checks it two ways, and needs both: the behavioural pass
fills every field through fromJson and compares after copyWith(), but it cannot
perturb a string or enum value — an invented one would not decode — which is
exactly the shape of the SpotLess and Subtitles bugs. So a second pass reads the
model source and asserts every final field of the parameter class appears in the
copyWith body. Both were verified against the actual defects.
preset_visibility_test covers the other direction: a preset is the main way
settings arrive without anyone touching a control, so it is the main way a hidden
setting arrives. It walks every built-in's enabled passes through the real
converter and fails if a value differing from the schema default is not reachable
in that preset's own state. The nine built-ins pass. Values hidden only by a
`method` condition are exempt — builtin-fast selects Bwdif and still sets a QTGMC
preset on purpose, so that switching method in the UI lands somewhere sensible —
and three advanced-only QTGMC values are allowed through a named list, with a
companion test that fails if the list gains a stale entry.
CLAUDE.md gains the four findings and what they cost: visibleWhen being read only inside `ui`, sections becoming visible headings, Color Correction's automatic and manual halves belonging in one group, and copyWith resetting what it forgets. Also records that the nine built-in presets map cleanly, and why — most of it luck, in that no built-in uses Colour Correction at all. FILTER_SCHEMA.md states where visibleWhen belongs and what happens when it is written elsewhere, that a section is a heading only where there is more than one, and that a method which gates nothing should not exist. Notes the two schemas that are now worked examples: chroma_fixes for progressive disclosure, color_correction for pairing an automatic control with the manual one it supersedes. Left deliberately unchanged and noted as such: deinterlace's fourteen section titles, and the mixed Title Case / sentence case of parameter labels, which is now more visible under headings but is several hundred strings and should be its own change.
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.
Started as "the Chroma Fixes parameters aren't intuitive" and turned out to be four defects, three of them affecting most of the app.
What was wrong
visibleWhenwas being ignored. It is declared only onParameterUiConfig, so a schema that writes it one level up has it dropped at parse time and the control is always visible. That was 35 parameters across 8 schemas, plus 12 sections (UiSectionhas novisibleWheneither) — all written during the 2026-08-17 build-out. Beyond Chroma Fixes: Noise Reduction showed mClean and TemporalDegrain2 knobs under SMDegrain, SpotLess showed RemoveDirt's, Chroma Denoise showed CCD's under Cnr4, Deflicker showed both methods' at once.Sections were not headings. The compact panel printed a title only for an
advancedOnlysection in advanced mode, so every ordinary section ran together into one flat list and atitlewas in practice a comment.Three settings applied on top of corrections already made, and one switch was never read. Automatic chroma alignment measures the misalignment and corrects it, then the manual Y/C shift was applied on top. Automatic levels places black and white, then the manual input/output points graded the already-graded picture. And
apply_levels— the panel's own Levels switch — never reached the worker: the block was emitted whenever a value differed from its default, so unticking it left the adjustment running with nothing on screen able to stop it.copyWithsilently reset 26 fields in six models.togglePassrebuilds a pass throughcopyWith, so flicking a pass switch discarded them — including the DeDot that VHS Cleanup turns on and the automatic alignment that DV Camcorder Tape turns on.What changed
advancedOnlytuning section per filter. Default panel went from ~25 always-on controls to 8 switches.tweak/white_balance) and Crop & Resize (standard/nnedi3_2x/eedi3_2x) each declared methods that gated nothing — no parameter conditional on the choice, no model field, the converter hardcoding one value or emitting none. Crop & Resize was the sharper case: its real upscaler choice is theupscaleMethodparameter, so the dropdown was an inert duplicate of a working control.deblock.quant1was ungated while DCTFilter, which does not take it, was one of the three methods offered.methodremoved.effective_apply_chroma_shift,effective_levels_points,effective_apply_levels, and their Dart twins), so the panel, the script and the pass summary cannot disagree. None clears the stored value, so switching the automatic side off restores what was set by hand.Behaviour changes worth flagging
applyLevelsunticked no longer applies them. The switch is authoritative now; no built-in preset is affected.Guards added
filter_schema_curation_test: nothing may declarevisibleWhenoutside a parameter'sui; no section may carry one; every condition must name a parameter that exists (a condition that can never be satisfied hides a control forever — the worse failure); a schema with 2+ methods must gate at least one parameter on the choice; a parameter used by a subset of methods must name exactly those; no section may hold onlymethod.chroma_fixes_panel_test,color_correction_panel_test: pump the real schemas through the real panel — the only level that shows what is actually on screen.parameter_copy_with_test: two passes, behavioural and source-level. The behavioural one cannot perturb a string or enum (an invented value would not decode), which is exactly the shape of the SpotLessmethodand SubtitlesburnInPathbugs.preset_visibility_test: walks every built-in preset's enabled passes through the real converter and fails if a value differing from the schema default is not reachable in that preset's own state. Values hidden only by amethodcondition are exempt (builtin-fastselects Bwdif and sets a QTGMC preset deliberately); three advanced-only QTGMC values pass through a named allowlist, with a companion test that fails if the list goes stale.test_150,test_151in the Rust suite, with Dart counterparts.Each new guard was verified to fail when its fix is removed.
Testing
flutter test --exclude-tags heavy— 746 passcargo test— green apart fromsubtitle_integration_test, which needs the whispersmallmodel that is not installed locally (unrelated to this branch)Not in this PR
🤖 Generated with Claude Code