APVTS::ParameterChanged not being called for toggle buttons

I am having an issue with buttons that have a buttonClicked() implementation and also have APVTS button attachments. For context, it’s a delay that has two buttons that allow time to either be set in milliseconds or 16th note subdivisions. To ensure only one (never 0 or both) time mode is always selected, my buttonClicked() code looks like this:

void DigitalDelayAudioProcessorEditor::buttonClicked(juce::Button* b)
    if (b == &millisecondsButton)
        millisecondsButton.setClickingTogglesState(false);  //ensures you can't repeatedly click the milliseconds button
        stepsButton.setClickingTogglesState(true); //enables you to now click the steps button
        stepsButton.setToggleState(false, juce::NotificationType::dontSendNotification); //unclicks steps button when milliseconds is clicked 
    else if (b == &stepsButton)
        stepsButton.setClickingTogglesState(false); //ensures you can't repeatedly click the steps button 
        millisecondsButton.setClickingTogglesState(true); /enables you to now click the msec button
        millisecondsButton.setToggleState(false, juce::NotificationType::dontSendNotification); //unclicks msec button


Also in my editor, I add the buttons as listeners and create button attachments for the APVTS. In my processor constructor, I add parameter listeners for the buttons, and in my parameter layout, I make AudioParameterBool parameters for the milliseconds and steps like so:

    auto msecParam = std::make_unique<juce::AudioParameterBool>(getMsecParamName(), getMsecParamName(), false, juce::String(), nullptr, nullptr);

    auto stepsParam = std::make_unique<juce::AudioParameterBool>(getStepsParamName(), getStepsParamName(), true, juce::String(), nullptr, nullptr);

My parameterChanged code looks like this:

void DigitalDelayAudioProcessor::parameterChanged(const juce::String& parameter, float newValue)
    bool boolVal = static_cast<bool> (newValue); //I figured it was best to cast the float to a bool, but it doesn't appear to make a difference
    ... //other params before here 
    else if (parameter == getMsecParamName())  
        DBG("msec changed");
        if (boolVal != millisecondsActive)
            millisecondsActive = boolVal;
            stepsActive = !boolVal;
            DBG("I have a longlist of DBG statements here in my actual code")
    else if (parameter == getStepsParamName())
        DBG("steps changed");
        if (boolVal != stepsActive)
            stepsActive = boolVal;
            millisecondsActive = !boolVal;
            DBG("I have a longlist of DBG statements here in my actual code");

Note that stepsActive and millisecondsActive are both boolean private member variables of my processor, and by default steps is active while milliseconds is inactive (which is correctly reflected in my GUI). What I’ve noticed is that upon opening my plugin for the first time, clicking the milliseconds button toggles its state, untoggles the steps button, and prints the DBG statements from parameterChanged(), which also correctly updates the private variables’ states. However, once parameterChanged() has run once, it never runs again.

Additionally, when I recall my plugin state, the parameterChanged code runs again, but it is generally incorrect (e.g., DBG statements I have there will say that both are active when only one can be active), and again after it has run once, it never runs again when I click the buttons. I’m at a total loss here.