feat: let apps mark their params changed for the configurator to poll - #691
Merged
Merged
Conversation
ArthurGibert
added a commit
to ATOVproject/faderpunk-community-apps
that referenced
this pull request
Sep 17, 2026
Both apps now call ParamStore::update() from their Mode/Range panel gesture instead of keeping a private working copy outside run(). Replaces the workaround from #59, whose two limitations are gone: a panel edit is now saved, and a Configurator save of another parameter no longer reverts it. Needs ATOVproject/faderpunk#691.
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.
What
Replaces #640: apps can now change their own parameters (a panel gesture that
picks a value the user could otherwise only set from the configurator), and
the change reaches the configurator without a reconnect.
Why not #640 as written
Full review is on the PR, summarized here. Three blocking problems:
#640only addedupdate()to thefirmware's own
ParamStore; thefpapp-sdkmirror never got one, andcommunity apps ship exclusively as installed apps. As written, it had no
possible caller.
AppStatemessage. The configurator has no path for a message it isn'twaiting for — any frame that arrives between requests gets handed to
whichever request comes next as that request's reply.
useConnectionHealthCheckpolls
GetGlobalConfigevery 2s; a push landing in that window gets readas the wrong response and disconnects the session.
AppState, thesame variant
GetAppParams/SetAppParamsuse, so even a smarter clientcouldn't tell a push apart from an answer to something it just asked.
Design
The device never sends anything unprompted. The configurator asks.
ParamStore::update<F>(&self, modifier: F)— mutates, persists toflash, and marks the app's channel dirty. No MIDI I/O. On both the
firmware store and, this time, its
fpapp-sdkmirror, so installed appsget it too.
APP_PARAM_DIRTY: AtomicU16). A firmwareapp sets its own bit directly; an installed app sets it through a new
write-only, empty-payload
blob_kind::PARAM_DIRTY— the only new item.FPAPP_ABI_MINOR4 -> 5.ConfigMsgIn::GetChangedAppParams— swaps the bitmask to 0, filters tochannels still in the layout, and answers with only the apps that actually
changed (usually none). Values are read fresh at answer time, the same
signal + waitprimitivesGetAppParamsalready uses — never a valuesnapshotted when the change happened, so there's no stale-value or
reused-channel case to get wrong.
GetAllAppParamsand the new handler share onesend_app_param_batchhelper. The "don't announce a count before that many messages exist"
invariant (FPApps: parameter batch can end short of its announced count, wedging the config connection #688) now lives in one place instead of two copies that could
drift.
GetChangedAppParamsalongside its existingconnection check, every 2s, and merges results into the params store.
Deliberately its own try/catch, never allowed to cause a disconnect — an
older firmware that predates this message must not read as a dropped
connection.
SetAppParamsstill does). A gesture already runs inside the app's owncode; restarting it out from under itself would be wrong.
Also fixed
The configurator never displayed a live update at all, found while
testing this.
react-hook-form's fields are uncontrolled —defaultValueseeds a field once, when it registers, and isn't revisited on a later prop
change. A poll-driven update reached the store correctly but no on-screen
field ever reflected it. Fixed by splitting
ActiveApp's params form intoits own component, keyed by the params' content, so it's
useForm()itselfthat gets recreated when a value changes — not just the DOM under it, and not
the collapsible card around it (which uses native, not React, state, so it
needs to stay outside the remount or every live update closes the card).
Verified on hardware
A throwaway single-param probe app confirmed the wire mechanism first,
isolated from any real app's complexity. Then on Manifold (migrated in a
companion community-apps PR): the gesture reaches the app, the new value
shows up in the configurator within ~2s without the card closing, it survives
a power cycle, and a configurator save of an unrelated param no longer
reverts it.
ParamStore::updatecaller in a layout, trigger itsgesture, confirm the value updates in the configurator within ~2s
gesture-set value isn't reverted
with params to a layout
🤖 Generated with Claude Code