ValueCommandParameter<T>: value radio-sets + live preview, native BarComboBox/BarGallery support#7
Merged
Merged
Conversation
…over shared commands
Adds the Actipro-style value-carrying command parameter on top of the FB-27
checkable machinery, so several controls can share ONE command while each
contributes its own value:
- ValueCommandParameter<T> : CheckableCommandParameter — a Value the control
contributes on Execute, plus the live-preview trio (PreviewValue +
ValueCommandParameterAction {Commit, Preview, CancelPreview}), all raising
INotifyPropertyChanged. IValueCommandParameter is the non-generic surface
framework controls write through without knowing T.
- Radio sets: on each re-query the command's CanExecute receives every bound
control's own parameter, compares Value to the model, and reflects the match
(IsChecked at the Bars layer; Override on plain ToggleButton) — exactly the
matching member shows checked; Execute applies the clicked member's Value.
- ComboBox grows OnDropDownOpened/OnDropDownClosed(bool committed) hooks with a
commit-flagged close channel (Enter / non-editable Space / item click commit;
Escape / light-dismiss / focus-out / toggle-close dismiss). Non-editable
Space now accepts like Enter (WPF parity).
- BarComboBox (and BarGallery, rebased onto it) natively route a value
parameter: highlight-change previews (apply-without-commit), commit sends
CancelPreview-then-Commit (one real operation), dismissal cancels and
restores the pre-open selection. Payload writes and every execute are
CanExecute-gated; Action always rests at Commit; the combo greys per CD25.
No default parameter is auto-provisioned (T is not inferrable) — without the
command+parameter pair, behavior is byte-for-byte as before.
Tests: parameter unit contract, framework + Bars radio sets (incl. FB-27
context-gate composition), gallery/combo preview-cancel-commit flows, and
no-parameter backward-compat. Suites: Cursorial.UI.Tests 2919, Bars 259, all
green; solution builds clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxMEA2t8CHYmYgNg3BZi3w
…ough the gate Mike's ruling on the mid-session gate question: a live preview is ATOMIC — either committed or entirely rolled back, no in-between. Accordingly: - BarCommand.Execute exempts a parameter carrying Action=CancelPreview from its CanExecute self-gate; Preview and Commit stay gated (a gated command refuses NEW tentative state and refuses commits). Custom ICommands with an internal gate are documented to honor the same exemption. - BarComboBox dispatches the session's CancelPreview without a CanExecute check (Preview/Commit keep their gates, payload writes included), and a refused commit now nets a full rollback: the pre-open selection returns to the face, so the face never diverges from the unchanged model. - Docs: the "keep CanExecute stable across a session" guidance is replaced by the atomicity contract on ValueCommandParameterAction.CancelPreview, ValueCommandParameter<T>, BarCommand.Execute, and the BarComboBox remarks — CancelPreview is a cleanup obligation, always dispatched and executed regardless of CanExecute; a command gated mid-session has its active preview rolled back and any commit refused. - Tests: gate flips false mid-preview → dismissal still reverts byte-exact (cancel delivered through the gate); gate flips false mid-preview → commit gesture nets a full rollback (cancel executed, commit refused un-applied, face restored). The shared test command now carries the gate (default open), so the whole existing suite pins the stable-gate case unchanged. Suites: Cursorial.UI.Tests 2919, Bars 261, TextBox 75 — all green; solution builds clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UxMEA2t8CHYmYgNg3BZi3w
…-side Action channel Design revision from Mike, combining the best of Actipro's two preview models: the WPF ribbon's value-carrying parameter stays as the VALUE channel, while the preview verbs move to the newer Avalonia Bars command-side shape. - New IPreviewableCommand : ICommand in Cursorial.UI (mirrors Avalonia's Preview(object?)/CancelPreview(object?) exactly), documented as a DRY-RUN model: Preview is a dry-run Execute (produce the effect, commit nothing), CancelPreview unwinds it byte-exact, and Execute just does the thing for real — ordinary execution with zero preview awareness, since a previewing control always cancels the active dry-run before executing. - ValueCommandParameterAction and IValueCommandParameter.Action are DROPPED — the verb is now the method, so nothing reads a mode off the parameter and a preview can never be mis-executed as the real thing by an unaware command. - BarCommand implements IPreviewableCommand via optional ctor delegates (previewExecute/cancelPreviewExecute); without them the verbs no-op (not preview-capable). Preview self-gates on CanExecute (no new tentative state on a gated command); CancelPreview is NEVER gated — Mike's atomicity ruling is now structural, so the previous Execute-side CancelPreview gate exemption is reverted. - BarComboBox/BarGallery: the value session (commit-on-close via Execute, restore-on-dismiss) works with ANY ICommand; the preview verbs dispatch only when Command is IPreviewableCommand. Gate-flip semantics unchanged: dry-run unwound, execution refused, face restored. - Tests revised to the delegate shape; new UnawareCommand_NoPreview_ CommitStillLands pins that a plain ICommand never sees a preview verb yet still receives the value commit; both gate-flip atomicity tests now pass structurally. Suites: Cursorial.UI.Tests 2919, Bars 262, TextBox 75 — all green; solution builds clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UxMEA2t8CHYmYgNg3BZi3w
…iewableCommand gains CanPreview
Final scope addition folding the BarCommand refactor into this PR (no
intermediate step, per Mike):
- BarCommand : UIObject now wraps a BINDABLE inner command: a Command
StyledProperty holds the behavior (<BarCommand Command="{Binding Save}"/>)
while the Bars display metadata stays on the wrapper — Text / Icon /
InputGestureText / IsCheckable / Description are StyledProperties too
(bindable; source-compatible with every existing object initializer).
The delegate ctors become sugar over a new plain DelegateCommand
(execute/canExecute + optional previewExecute/cancelPreviewExecute)
assigned to Command.
- Every ICommand member forwards to the inner; CanExecute is false while
Command is null (surfaces grey until a command binds). The inner's
CanExecuteChanged surfaces through the wrapper's; an inner SWAP unhooks
the old, hooks the new, and raises so every bound control re-queries
immediately; Execute keeps the post-run COURTESY re-raise, so even a raw
inner ICommand that never raises still re-syncs bound controls (the FB-27
convenience). The parameters are untouched — they live on the control and
pass through verbatim.
- IPreviewableCommand gains bool CanPreview — the STRUCTURAL gate ("do I
support dry-runs at all") vs CanExecute's runtime gate ("may I run now") —
a deliberate departure from Actipro's Avalonia interface (theirs never
wraps), so wrapper probes never lie. BarCommand implements the interface
EXPLICITLY by delegation: CanPreview => inner is IPreviewableCommand
{ CanPreview: true }; Preview forwards self-gated on CanExecute;
CancelPreview forwards NEVER gated (atomicity ruling unchanged).
DelegateCommand answers CanPreview from its supplied delegates.
- Probe sites (BarComboBox/BarGallery session start + close cancel) tightened
to `is IPreviewableCommand { CanPreview: true }`.
Tests: new DelegateCommandTests (structural/runtime gates, ungated cancel, no
auto-raise) and BarCommandTests (null-inner semantics, courtesy raise over a
mute raw inner, inner-forwarded raises, swap re-raise + bound-control
re-query + old-inner unhook, probe honesty ends a wrapped non-previewable
session before it starts while the value commit still lands, full dry-run
session through the wrapper, delegate-ctor sugar regression-pin). The entire
pre-existing suite passed the refactor unmodified.
Suites: Cursorial.UI.Tests 2922, Bars 268, TextBox 75 — all green; solution
builds 0 warnings / 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxMEA2t8CHYmYgNg3BZi3w
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.
ValueCommandParameter<T>+ live preview + theBarCommandrefactor — the full converged design from the review discussion, everything where we want it in one PR.Final shape (head
29c96cb)The two axes are independent: command shape (plain / wrapped / previewable) × parameter shape (none / checkable / value).
Commands (the verbs)
IPreviewableCommand : ICommand—Preview(object?)+CancelPreview(object?)+bool CanPreview. Dry-run contract:Preview= a dry-run Execute (produce the effect, commit nothing),CancelPreview= unwind byte-exact,Execute= do the thing for real — ordinary execution, zero preview awareness (the session cancels any active dry-run first).CanPreviewis the structural gate ("do I support dry-runs at all") vsCanExecute's runtime gate — a deliberate departure from Actipro's Avalonia interface, needed because we wrap (probes must never lie).DelegateCommand— the plain delegate-backedIPreviewableCommandbuilding block (CanPreview= preview delegate supplied; no auto-raise — that's the wrapper's courtesy).BarCommand : UIObject— wraps a bindableCommandStyledProperty (<BarCommand Command="{Binding SaveCommand}"/>); delegate ctors are sugar over an innerDelegateCommand; implementsIPreviewableCommandexplicitly, delegation-only (CanPreviewforwards off the inner;CancelPreviewnever gated). Inner-swap unhooks/rehooksCanExecuteChanged+ raises; post-Executecourtesy re-raise preserved (the FB-27 convenience — works even over a mute rawICommand). All five metadata props (Text/Icon/InputGestureText/IsCheckable/Description) became StyledProperties — source-compatible, whole solution compiles unchanged.Parameters (the value channel — untouched by the wrapper, they live on the control)
ValueCommandParameter<T> : CheckableCommandParameter+ non-genericIValueCommandParameter:Value(chosen) +PreviewValue(candidate) — two verbs ↔ two slots (PreviewreadsPreviewValue,ExecutereadsValue). NoActionenum. Radio sets:CanExecutewritesIsChecked = (Value == current); FB-27 reflects/coerces; composes withHandled/Overridegating.Sessions
BarComboBox/BarGallery: value session (commit-on-close viaExecute, restore-on-dismiss) works with ANYICommand; the live dry-run activates only onCommand is IPreviewableCommand { CanPreview: true }. No default parameter provisioned (Tnot inferrable; no pair = byte-for-byte today's behavior, test-pinned).ComboBoxgainedOnDropDownOpened/OnDropDownClosed(bool committed)hooks. Atomicity (your ruling) is structural: mid-session gate flip → dry-run unwound, execution refused, face restored — no in-between.Verification
Cursorial.UI.Tests2922 ·Cursorial.UI.Bars.Tests268 · TextBox 75 · 0 warnings. FB-27/ComboBox/Bars matrices untouched-green; probe-honesty, inner-swap re-query, courtesy-raise, and delegate-sugar regression-pin tests added.Semantics calls flagged for your review
BarCommand.CanExecuteisfalsewhileCommandis null — surfaces grey until a command binds (the swap-raise re-enables). The old class always had delegates, so this state is new.DelegateCommandhas no post-Execute auto-raise (building block); the courtesy raise lives inBarCommand.CheckableCommandParameter.Setprotected, stale-check-when-locally-disabled (FB candidate).🤖 Generated with Claude Code
https://claude.ai/code/session_01UxMEA2t8CHYmYgNg3BZi3w