diff --git a/configurator/src/components/ActiveApp.tsx b/configurator/src/components/ActiveApp.tsx index 7c5d858b..4eccca4c 100644 --- a/configurator/src/components/ActiveApp.tsx +++ b/configurator/src/components/ActiveApp.tsx @@ -23,6 +23,8 @@ interface Props { layoutId: number; startChannel: number; params: Value[]; + /** The app declares params but never answered a request for them. */ + notResponding?: boolean; } // react-hook-form seeds each field's value once, when the form itself is @@ -105,7 +107,13 @@ const ActiveAppParamsForm = ({ ); }; -export const ActiveApp = ({ app, layoutId, params, startChannel }: Props) => { +export const ActiveApp = ({ + app, + layoutId, + params, + startChannel, + notResponding, +}: Props) => { return (
{

App

-

{app.name}

+

+ {app.name} + {notResponding && ( + + Not responding + + )} +

@@ -164,7 +182,13 @@ export const ActiveApp = ({ app, layoutId, params, startChannel }: Props) => {

)}
- {app.paramCount > 0 ? ( + {app.paramCount > 0 && notResponding ? ( +
+ This app hasn't answered a parameter request, so there's nothing real + to show here — showing empty or default values would be misleading, + and saving them could overwrite what the app actually holds. +
+ ) : app.paramCount > 0 ? ( { ) .map(({ app, id, startChannel }) => { const params = allParams.get(id); - if (!params) { - return null; - } + // A missing entry is normal for an app with no params (it never + // gets asked). For one that declares params, it means the app + // never answered a param request — still worth a card, so the + // silence is visible instead of the app just not being listed. + const notResponding = app.paramCount > 0 && !params; return (
  • ); diff --git a/faderpunk/src/tasks/configure.rs b/faderpunk/src/tasks/configure.rs index ae47a849..dcf92c5e 100644 --- a/faderpunk/src/tasks/configure.rs +++ b/faderpunk/src/tasks/configure.rs @@ -246,6 +246,14 @@ pub async fn start_config_loop<'a>(usb_tx: &'a SharedUsbSender<'a>) { proto.send_msg(ConfigMsgOut::GlobalConfig(config)).await } ConfigMsgIn::GetAppParams { layout_id } => { + // layout_id comes straight from the client with no validation + // elsewhere; every other caller indexes it from a real + // Layout, but this one goes over the wire raw. Answering + // nothing for an out-of-range id (same as an app that never + // replies) beats indexing APP_PARAM_SIGNALS out of bounds. + if layout_id as usize >= GLOBAL_CHANNELS { + continue; + } drain_app_param_responses(); APP_PARAM_SIGNALS[layout_id as usize].signal(AppParamCmd::RequestParamValues); match receive_app_params(layout_id).await { @@ -258,6 +266,9 @@ pub async fn start_config_loop<'a>(usb_tx: &'a SharedUsbSender<'a>) { } } ConfigMsgIn::SetAppParams { layout_id, values } => { + if layout_id as usize >= GLOBAL_CHANNELS { + continue; + } drain_app_param_responses(); APP_PARAM_SIGNALS[layout_id as usize].signal(AppParamCmd::SetAppParams { values }); match receive_app_params(layout_id).await {