diff --git a/src-ui/app/HasEditor.h b/src-ui/app/HasEditor.h
index 9c594aea..c0911426 100644
--- a/src-ui/app/HasEditor.h
+++ b/src-ui/app/HasEditor.h
@@ -40,6 +40,7 @@ struct HasEditor
 {
     SCXTEditor *editor{nullptr};
     HasEditor(SCXTEditor *e);
+    HasEditor(HasEditor *e);
     virtual ~HasEditor() = default;
 
     template <typename T> void sendToSerialization(const T &msg);
diff --git a/src-ui/app/edit-screen/components/GroupTriggersCard.cpp b/src-ui/app/edit-screen/components/GroupTriggersCard.cpp
index 9034c451..2ef05502 100644
--- a/src-ui/app/edit-screen/components/GroupTriggersCard.cpp
+++ b/src-ui/app/edit-screen/components/GroupTriggersCard.cpp
@@ -27,9 +27,13 @@
 
 #include "GroupTriggersCard.h"
 #include "sst/jucegui/components/ToggleButton.h"
+#include "sst/jucegui/components/TextPushButton.h"
 #include "sst/jucegui/components/MenuButton.h"
+#include "sst/jucegui/components/DraggableTextEditableValue.h"
 
 #include "app/SCXTEditor.h"
+#include "messaging/client/client_messages.h"
+#include "connectors/PayloadDataAttachment.h"
 
 namespace scxt::ui::app::edit_screen
 {
@@ -37,17 +41,34 @@ namespace jcmp = sst::jucegui::components;
 
 struct GroupTriggersCard::ConditionRow : juce::Component, HasEditor
 {
+    using booleanAttachment_t =
+        connectors::BooleanPayloadDataAttachment<scxt::engine::GroupTriggerConditions>;
+
+    using floatAttachment_t =
+        connectors::PayloadDataAttachment<scxt::engine::GroupTriggerConditions>;
+
+    GroupTriggersCard *parent{nullptr};
+    engine::GroupTriggerID lastID{engine::GroupTriggerID::NONE};
+
     int index{-1};
     bool withCondition{false};
-    ConditionRow(int index, bool withCond, SCXTEditor *ed)
-        : index(index), withCondition(withCond), HasEditor(ed)
+    ConditionRow(GroupTriggersCard *p, int index, bool withCond)
+        : parent(p), index(index), withCondition(withCond), HasEditor(p)
     {
+        activeA = std::make_unique<booleanAttachment_t>(
+            "Active",
+            [this](const auto &a) {
+                setupValuesFromData();
+                parent->pushUpdate();
+            },
+            parent->cond.active[index]);
         activeB = std::make_unique<jcmp::ToggleButton>();
         activeB->setLabel(std::to_string(index + 1));
+        activeB->setSource(activeA.get());
         addAndMakeVisible(*activeB);
 
         auto mkm = [this](auto tx, auto cs) {
-            auto res = std::make_unique<jcmp::MenuButton>();
+            auto res = std::make_unique<jcmp::TextPushButton>();
             res->setLabel(tx);
             res->setOnCallback(
                 editor->makeComingSoon(std::string() + "Trigger Condition Pane " + cs));
@@ -55,10 +76,137 @@ struct GroupTriggersCard::ConditionRow : juce::Component, HasEditor
             return res;
         };
         typeM = mkm("TYPE", "Trigger Mode");
-        a1M = mkm("A1", "Argument One");
-        a2M = mkm("A2", "Argument Two");
+        typeM->setOnCallback([w = juce::Component::SafePointer(this)]() {
+            if (!w)
+                return;
+            w->showTypeMenu();
+        });
         if (withCond)
             cM = mkm("&", "Conjunction");
+
+        setupValuesFromData();
+    }
+
+    void setupValuesFromData()
+    {
+        auto &sr = parent->cond.storage[index];
+
+        if (sr.id != lastID)
+        {
+            lastID = sr.id;
+
+            auto onArgChanged = [this](const auto &a) { parent->pushUpdate(); };
+
+            if (sr.id == engine::GroupTriggerID::NONE)
+            {
+                SCLOG("NONE");
+                a1A.reset();
+                a2A.reset();
+                a1M.reset();
+                a2M.reset();
+            }
+            else if ((int)sr.id >= (int)engine::GroupTriggerID::MACRO &&
+                     (int)sr.id <= (int)engine::GroupTriggerID::MACRO + scxt::macrosPerPart)
+            {
+                auto dm = datamodel::pmd()
+                              .asFloat()
+                              .withRange(0, 1)
+                              .withLinearScaleFormatting("")
+                              .withDecimalPlaces(2)
+                              .withDefault(0.5);
+                a1A = std::make_unique<floatAttachment_t>(dm, onArgChanged, sr.args[0]);
+                a2A = std::make_unique<floatAttachment_t>(dm, onArgChanged, sr.args[1]);
+
+                a1M = std::make_unique<jcmp::DraggableTextEditableValue>();
+                a1M->setSource(a1A.get());
+                addAndMakeVisible(*a1M);
+
+                a2M = std::make_unique<jcmp::DraggableTextEditableValue>();
+                a2M->setSource(a2A.get());
+                addAndMakeVisible(*a2M);
+            }
+            else if ((int)sr.id >= (int)engine::GroupTriggerID::MIDICC &&
+                     (int)sr.id <= (int)engine::GroupTriggerID::LAST_MIDICC)
+            {
+                auto dm = datamodel::pmd()
+                              .asFloat()
+                              .withRange(0, 127)
+                              .withLinearScaleFormatting("")
+                              .withDecimalPlaces(0)
+                              .withDefault(64);
+                a1A = std::make_unique<floatAttachment_t>(dm, onArgChanged, sr.args[0]);
+                a2A = std::make_unique<floatAttachment_t>(dm, onArgChanged, sr.args[1]);
+
+                a1M = std::make_unique<jcmp::DraggableTextEditableValue>();
+                a1M->setSource(a1A.get());
+                addAndMakeVisible(*a1M);
+
+                a2M = std::make_unique<jcmp::DraggableTextEditableValue>();
+                a2M->setSource(a2A.get());
+                addAndMakeVisible(*a2M);
+            }
+            else
+            {
+                SCLOG_UNIMPL("No midi CC for type " << (int)sr.id);
+            }
+            resized();
+        }
+
+        typeM->setLabel(engine::getGroupTriggerDisplayName(sr.id));
+        auto showRest = (sr.id != engine::GroupTriggerID::NONE);
+        if (a1M)
+            a1M->setVisible(showRest);
+        if (a2M)
+            a2M->setVisible(showRest);
+        if (cM)
+            cM->setVisible(showRest);
+
+        auto ac = parent->cond.active[index];
+        typeM->setEnabled(ac);
+        if (a1M)
+            a1M->setEnabled(ac);
+        if (a2M)
+            a2M->setEnabled(ac);
+        if (cM)
+            cM->setEnabled(ac);
+
+        repaint();
+    }
+
+    void showTypeMenu()
+    {
+        auto p = juce::PopupMenu();
+        p.addSectionHeader("Trigger Mode");
+        p.addSeparator();
+
+        auto mkv = [this](auto v) {
+            return [w = juce::Component::SafePointer(this), v]() {
+                if (!w)
+                    return;
+                w->parent->cond.storage[w->index].id = (engine::GroupTriggerID)v;
+                w->parent->pushUpdate();
+                w->setupValuesFromData();
+            };
+        };
+
+        p.addItem("NONE", mkv((int)engine::GroupTriggerID::NONE));
+
+        auto mcc = juce::PopupMenu();
+        for (int i = 0; i < 128; ++i)
+        {
+            mcc.addItem(fmt::format("CC {:3d}", i), mkv((int)engine::GroupTriggerID::MIDICC + i));
+        }
+
+        p.addSubMenu("MIDI CC", mcc);
+        auto msm = juce::PopupMenu();
+        for (int i = 0; i < scxt::macrosPerPart; ++i)
+        {
+            msm.addItem("Macro " + std::to_string(i + 1),
+                        mkv((int)engine::GroupTriggerID::MACRO + i));
+        }
+
+        p.addSubMenu("Macros", msm);
+        p.showMenuAsync(editor->defaultPopupMenuOptions());
     }
 
     void resized() override
@@ -69,9 +217,11 @@ struct GroupTriggersCard::ConditionRow : juce::Component, HasEditor
         tb = tb.translated(tb.getWidth() + 2, 0).withWidth(72);
         typeM->setBounds(tb);
         tb = tb.translated(tb.getWidth() + 2, 0).withWidth(32);
-        a1M->setBounds(tb);
+        if (a1M)
+            a1M->setBounds(tb);
         tb = tb.translated(tb.getWidth() + 2, 0).withWidth(32);
-        a2M->setBounds(tb);
+        if (a2M)
+            a2M->setBounds(tb);
 
         if (cM)
         {
@@ -80,15 +230,17 @@ struct GroupTriggersCard::ConditionRow : juce::Component, HasEditor
         }
     }
 
+    std::unique_ptr<booleanAttachment_t> activeA;
+    std::unique_ptr<floatAttachment_t> a1A, a2A;
     std::unique_ptr<jcmp::ToggleButton> activeB;
-    std::unique_ptr<jcmp::MenuButton> typeM, a1M, a2M, cM;
+    std::unique_ptr<jcmp::TextPushButton> typeM, cM;
+    std::unique_ptr<jcmp::DraggableTextEditableValue> a1M, a2M;
 };
 GroupTriggersCard::GroupTriggersCard(SCXTEditor *e) : HasEditor(e)
 {
     for (int i = 0; i < scxt::triggerConditionsPerGroup; ++i)
     {
-        rows[i] =
-            std::make_unique<ConditionRow>(i, i != scxt::triggerConditionsPerGroup - 1, editor);
+        rows[i] = std::make_unique<ConditionRow>(this, i, i != scxt::triggerConditionsPerGroup - 1);
         addAndMakeVisible(*rows[i]);
     }
 }
@@ -117,4 +269,17 @@ void GroupTriggersCard::paint(juce::Graphics &g)
     g.drawText("TRIGGER CONDITIONS", getLocalBounds(), juce::Justification::topLeft);
 }
 
+void GroupTriggersCard::setGroupTriggerConditions(const scxt::engine::GroupTriggerConditions &c)
+{
+    cond = c;
+    for (auto &r : rows)
+        r->setupValuesFromData();
+    repaint();
+}
+
+void GroupTriggersCard::pushUpdate()
+{
+    sendToSerialization(scxt::messaging::client::UpdateGroupTriggerConditions(cond));
+}
+
 } // namespace scxt::ui::app::edit_screen
\ No newline at end of file
diff --git a/src-ui/app/edit-screen/components/GroupTriggersCard.h b/src-ui/app/edit-screen/components/GroupTriggersCard.h
index a90f70d4..c9bd1eaf 100644
--- a/src-ui/app/edit-screen/components/GroupTriggersCard.h
+++ b/src-ui/app/edit-screen/components/GroupTriggersCard.h
@@ -33,6 +33,8 @@
 
 #include <juce_gui_basics/juce_gui_basics.h>
 
+#include "engine/group_triggers.h"
+
 #include "configuration.h"
 #include "app/HasEditor.h"
 
@@ -46,6 +48,10 @@ struct GroupTriggersCard : juce::Component, HasEditor
     ~GroupTriggersCard();
     void paint(juce::Graphics &g) override;
     void resized() override;
+
+    void setGroupTriggerConditions(const scxt::engine::GroupTriggerConditions &);
+    void pushUpdate();
+    scxt::engine::GroupTriggerConditions cond;
 };
 } // namespace scxt::ui::app::edit_screen
 #endif // GROUPTRIGGERSCARD_H
diff --git a/src-ui/app/edit-screen/components/PartGroupSidebar.cpp b/src-ui/app/edit-screen/components/PartGroupSidebar.cpp
index 2379c9b6..ba518f8f 100644
--- a/src-ui/app/edit-screen/components/PartGroupSidebar.cpp
+++ b/src-ui/app/edit-screen/components/PartGroupSidebar.cpp
@@ -240,7 +240,13 @@ struct GroupSidebar : GroupZoneSidebarBase<GroupSidebar, false>
     }
     ~GroupSidebar() = default;
 
-    void updateSelection() { updateSelectionFrom(partGroupSidebar->editor->allGroupSelections); }
+    void updateSelection()
+    {
+        bool anySel = !partGroupSidebar->editor->allGroupSelections.empty();
+        updateSelectionFrom(partGroupSidebar->editor->allGroupSelections);
+        groupSettings->setVisible(anySel);
+        groupTriggers->setVisible(anySel);
+    }
 
     void resized() override
     {
@@ -522,4 +528,10 @@ void PartGroupSidebar::partConfigurationChanged(int i)
     partSidebar->parts[i]->resetFromEditorCache();
     partSidebar->restackForActive();
 }
+
+void PartGroupSidebar::groupTriggerConditionChanged(const scxt::engine::GroupTriggerConditions &c)
+{
+    groupSidebar->groupTriggers->setGroupTriggerConditions(c);
+}
+
 } // namespace scxt::ui::app::edit_screen
\ No newline at end of file
diff --git a/src-ui/app/edit-screen/components/PartGroupSidebar.h b/src-ui/app/edit-screen/components/PartGroupSidebar.h
index e9b92a69..3a7ee232 100644
--- a/src-ui/app/edit-screen/components/PartGroupSidebar.h
+++ b/src-ui/app/edit-screen/components/PartGroupSidebar.h
@@ -59,6 +59,7 @@ struct PartGroupSidebar : sst::jucegui::components::NamedPanel, HasEditor
     std::unique_ptr<PartSidebar> partSidebar;
 
     void partConfigurationChanged(int i);
+    void groupTriggerConditionChanged(const scxt::engine::GroupTriggerConditions &);
 
     void resized() override;
 };
diff --git a/src-ui/app/editor-impl/HasEditor.cpp b/src-ui/app/editor-impl/HasEditor.cpp
index 86553901..08970a6e 100644
--- a/src-ui/app/editor-impl/HasEditor.cpp
+++ b/src-ui/app/editor-impl/HasEditor.cpp
@@ -31,4 +31,5 @@
 namespace scxt::ui::app
 {
 HasEditor::HasEditor(SCXTEditor *e) : editor(e) {}
+HasEditor::HasEditor(HasEditor *e) : editor(e->editor) {}
 } // namespace scxt::ui::app
\ No newline at end of file
diff --git a/src-ui/app/editor-impl/SCXTEditorResponseHandlers.cpp b/src-ui/app/editor-impl/SCXTEditorResponseHandlers.cpp
index 91ca7f1d..cfcb5e9e 100644
--- a/src-ui/app/editor-impl/SCXTEditorResponseHandlers.cpp
+++ b/src-ui/app/editor-impl/SCXTEditorResponseHandlers.cpp
@@ -447,6 +447,6 @@ void SCXTEditor::onMissingResolutionWorkItemList(
 
 void SCXTEditor::onGroupTriggerConditions(scxt::engine::GroupTriggerConditions const &g)
 {
-    SCLOG_ONCE("Implement: On Group Trigger Conditions");
+    editScreen->partSidebar->groupTriggerConditionChanged(g);
 }
 } // namespace scxt::ui::app
\ No newline at end of file
diff --git a/src/engine/engine.h b/src/engine/engine.h
index f199f080..7772d0e8 100644
--- a/src/engine/engine.h
+++ b/src/engine/engine.h
@@ -147,6 +147,9 @@ struct Engine : MoveableOnly<Engine>, SampleRateSupport
             {
                 for (const auto &[gidx, group] : sst::cpputils::enumerate(*part))
                 {
+                    if (!group->triggerConditions.value(*this, *group))
+                        continue;
+
                     for (const auto &[zidx, zone] : sst::cpputils::enumerate(*group))
                     {
                         if (zone->mapping.keyboardRange.includes(key) &&
diff --git a/src/engine/group_triggers.cpp b/src/engine/group_triggers.cpp
index 270fc455..66614322 100644
--- a/src/engine/group_triggers.cpp
+++ b/src/engine/group_triggers.cpp
@@ -26,6 +26,9 @@
  */
 
 #include "group_triggers.h"
+
+#include "group.h"
+#include "part.h"
 #include "utils.h"
 
 namespace scxt::engine
@@ -33,6 +36,14 @@ namespace scxt::engine
 
 std::string toStringGroupTriggerID(const GroupTriggerID &p)
 {
+    if (p >= GroupTriggerID::MACRO && (int)p <= (int)GroupTriggerID::MACRO + scxt::macrosPerPart)
+    {
+        return fmt::format("macro{}", (int)p - (int)GroupTriggerID::MACRO);
+    }
+    if (p >= GroupTriggerID::MIDICC && p <= GroupTriggerID::LAST_MIDICC)
+    {
+        return fmt::format("midcc{}", (int)p - (int)GroupTriggerID::MIDICC);
+    }
     switch (p)
     {
         // lesson learned earlier was long names are not that handy debugging now the infra works
@@ -40,19 +51,24 @@ std::string toStringGroupTriggerID(const GroupTriggerID &p)
     case GroupTriggerID::NONE:
         return "n";
     case GroupTriggerID::MACRO:
-        return "mcr";
     case GroupTriggerID::MIDICC:
-        return "mcc";
+    case GroupTriggerID::LAST_MIDICC:
+    {
+        assert(false);
+    }
+    break;
     }
     return "n";
 }
 GroupTriggerID fromStringGroupTriggerID(const std::string &s)
 {
     static auto inverse = makeEnumInverse<GroupTriggerID, toStringGroupTriggerID>(
-        GroupTriggerID::NONE, GroupTriggerID::MIDICC);
+        GroupTriggerID::NONE, GroupTriggerID::LAST_MIDICC);
     auto p = inverse.find(s);
     if (p == inverse.end())
+    {
         return GroupTriggerID::NONE;
+    }
     return p->second;
 }
 
@@ -86,51 +102,107 @@ GroupTriggerConditions::fromStringConditionsConjunction(const std::string &s)
 
 struct GTMacro : GroupTrigger
 {
-    GTMacro(GroupTriggerInstrumentState &onState, GroupTriggerStorage &onStorage)
-        : GroupTrigger(onState, onStorage)
+    int macro{0};
+    float lb{0}, ub{1};
+    GTMacro(GroupTriggerID id, GroupTriggerInstrumentState &onState, GroupTriggerStorage &onStorage,
+            int macro)
+        : macro(macro), GroupTrigger(id, onState, onStorage)
     {
     }
 
-    bool value(const std::unique_ptr<Engine> &, const std::unique_ptr<Group> &) const override
+    bool value(const Engine &, const Group &g) const override
     {
-        return true;
+        auto mv = g.parentPart->macros[macro].value;
+        return mv >= lb && mv <= ub;
+    }
+
+    void storageAdjusted() override
+    {
+        lb = storage.args[0];
+        ub = storage.args[1];
+        if (lb > ub)
+            std::swap(lb, ub);
     }
-    datamodel::pmd argMetadata(int argNo) const override { return {}; }
 };
+
 struct GTMIDI1CC : GroupTrigger
 {
-    GTMIDI1CC(GroupTriggerInstrumentState &onState, GroupTriggerStorage &onStorage)
-        : GroupTrigger(onState, onStorage)
+    int cc;
+    float lb{0}, ub{1};
+    GTMIDI1CC(GroupTriggerID id, GroupTriggerInstrumentState &onState,
+              GroupTriggerStorage &onStorage, int cc)
+        : cc(cc), GroupTrigger(id, onState, onStorage)
     {
     }
 
-    bool value(const std::unique_ptr<Engine> &, const std::unique_ptr<Group> &) const override
+    bool value(const Engine &, const Group &g) const override
     {
-        return true;
+        assert(g.parentPart);
+        auto ccv = g.parentPart->midiCCValues[cc];
+        return ccv >= lb && ccv <= ub;
+    }
+
+    void storageAdjusted() override
+    {
+        auto flb = storage.args[0];
+        auto fub = storage.args[1];
+        if (flb > fub)
+            std::swap(flb, fub);
+        lb = std::floor(flb) / 127.0;
+        ub = std::ceil(fub) / 127.0;
     }
-    datamodel::pmd argMetadata(int argNo) const override { return {}; }
 };
 
 GroupTrigger *makeGroupTrigger(GroupTriggerID id, GroupTriggerInstrumentState &gis,
                                GroupTriggerStorage &st, GroupTriggerBuffer &bf)
 {
+    if (id >= GroupTriggerID::MACRO && (int)id <= (int)GroupTriggerID::MACRO + scxt::macrosPerPart)
+    {
+        static_assert(sizeof(GTMacro) < sizeof(GroupTriggerBuffer));
+        return new (bf) GTMacro(id, gis, st, (int)id - (int)GroupTriggerID::MACRO);
+    }
+    if (id >= GroupTriggerID::MIDICC && id <= GroupTriggerID::LAST_MIDICC)
+    {
+        static_assert(sizeof(GTMIDI1CC) < sizeof(GroupTriggerBuffer));
+        return new (bf) GTMIDI1CC(id, gis, st, (int)id - (int)GroupTriggerID::MIDICC);
+    }
+
 #define CS(id, tp)                                                                                 \
     static_assert(sizeof(tp) < sizeof(GroupTriggerBuffer));                                        \
     case id:                                                                                       \
-        return new (bf) tp(gis, st);
+        return new (bf) tp(id, gis, st);
     switch (id)
     {
-        CS(GroupTriggerID::MIDICC, GTMIDI1CC);
-        CS(GroupTriggerID::MACRO, GTMacro);
-    case GroupTriggerID::NONE:
+    default:
         return nullptr;
     }
     return nullptr;
 }
 
-// THIS NEEDS ARGUMENTS of the state and so on
+std::string getGroupTriggerDisplayName(GroupTriggerID id)
+{
+    if (id >= GroupTriggerID::MACRO && (int)id <= (int)GroupTriggerID::MACRO + scxt::macrosPerPart)
+    {
+        return fmt::format("MACRO {}", (int)id - (int)GroupTriggerID::MACRO + 1);
+    }
+    if (id >= GroupTriggerID::MIDICC && id <= GroupTriggerID::LAST_MIDICC)
+    {
+        return fmt::format("MIDICC {}", (int)id - (int)GroupTriggerID::MIDICC);
+    }
+
+    switch (id)
+    {
+    case GroupTriggerID::NONE:
+        return "NONE";
+    default:
+        return "ERROR";
+    }
+    return "ERROR";
+}
+
 void GroupTriggerConditions::setupOnUnstream(GroupTriggerInstrumentState &gis)
 {
+    bool allNone{true};
     for (int i = 0; i < triggerConditionsPerGroup; ++i)
     {
         auto &s = storage[i];
@@ -142,9 +214,32 @@ void GroupTriggerConditions::setupOnUnstream(GroupTriggerInstrumentState &gis)
         }
         else
         {
-            conditions[i] = makeGroupTrigger(s.id, gis, s, conditionBuffers[i]);
+            allNone = false;
+
+            if (!conditions[i] || conditions[i]->getID() != s.id)
+            {
+                conditions[i] = makeGroupTrigger(s.id, gis, s, conditionBuffers[i]);
+            }
         }
+        if (conditions[i])
+            conditions[i]->storageAdjusted();
+    }
+    alwaysReturnsTrue = allNone;
+}
+
+bool GroupTriggerConditions::value(const Engine &e, const Group &g) const
+{
+    if (alwaysReturnsTrue)
+        return true;
+
+    auto v = true;
+    for (int i = 0; i < triggerConditionsPerGroup; ++i)
+    {
+        // FIXME - conjunctions
+        if (active[i] && conditions[i])
+            v = v & conditions[i]->value(e, g);
     }
+    return v;
 }
 
 } // namespace scxt::engine
\ No newline at end of file
diff --git a/src/engine/group_triggers.h b/src/engine/group_triggers.h
index 937405d1..d99fbc10 100644
--- a/src/engine/group_triggers.h
+++ b/src/engine/group_triggers.h
@@ -55,8 +55,10 @@ enum struct GroupTriggerID : int32_t
 {
     NONE,
 
+    // Leave these at the end please
     MACRO,
-    MIDICC // if MIDICC is no longer last, adjust fromStringGfroupTRiggerID iteration
+    MIDICC = MACRO + scxt::macrosPerPart,
+    LAST_MIDICC = MIDICC + 128 // Leave this at the end please
 };
 
 std::string toStringGroupTriggerID(const GroupTriggerID &p);
@@ -65,7 +67,7 @@ GroupTriggerID fromStringGroupTriggerID(const std::string &p);
 struct GroupTriggerStorage
 {
     GroupTriggerID id{GroupTriggerID::NONE};
-    using argInterface_t = int32_t; // for now and just use ms for miliseconds and scale
+    using argInterface_t = float;
 
     static constexpr int32_t numArgs{2};
     std::array<argInterface_t, numArgs> args{0, 0};
@@ -77,13 +79,26 @@ struct GroupTrigger
 {
     GroupTriggerInstrumentState &state;
     GroupTriggerStorage &storage;
-    GroupTrigger(GroupTriggerInstrumentState &onState, GroupTriggerStorage &onStorage)
-        : state(onState), storage(onStorage)
+    GroupTriggerID id;
+    GroupTrigger(GroupTriggerID id, GroupTriggerInstrumentState &onState,
+                 GroupTriggerStorage &onStorage)
+        : state(onState), storage(onStorage), id(id)
     {
     }
+    GroupTriggerID getID() const { return id; }
     virtual ~GroupTrigger() = default;
-    virtual bool value(const std::unique_ptr<Engine> &, const std::unique_ptr<Group> &) const = 0;
-    virtual datamodel::pmd argMetadata(int argNo) const = 0;
+    virtual bool value(const Engine &, const Group &) const = 0;
+    virtual void storageAdjusted() = 0;
+
+    /*
+     * At one point I had considered making these self describing but our
+     * trigger types are small enough I just went with an int32 as each data
+     * element and then set up based on type in the UI in GroupTriggersCard.cpp,
+     * since I don't think adding trigger types will be anything like adding
+     * modulator or processor types in cadence. If I'm wrong add something like
+     * this again.
+     */
+    // virtual datamodel::pmd argMetadata(int argNo) const = 0;
 };
 
 // FIXME - make this sized more intelligently
@@ -98,11 +113,13 @@ struct GroupTriggerConditions
     GroupTriggerConditions()
     {
         std::fill(conditions.begin(), conditions.end(), nullptr);
-        std::fill(active.begin(), active.end(), false);
+        std::fill(active.begin(), active.end(), true);
     }
     std::array<GroupTriggerStorage, scxt::triggerConditionsPerGroup> storage{};
     std::array<bool, scxt::triggerConditionsPerGroup> active{};
 
+    bool alwaysReturnsTrue{true};
+
     enum struct Conjunction : int32_t
     {
         AND,
@@ -118,6 +135,8 @@ struct GroupTriggerConditions
 
     void setupOnUnstream(GroupTriggerInstrumentState &);
 
+    bool value(const Engine &, const Group &) const;
+
   protected:
     std::array<GroupTriggerBuffer, scxt::triggerConditionsPerGroup> conditionBuffers;
     std::array<GroupTrigger *, scxt::triggerConditionsPerGroup> conditions{};
diff --git a/src/json/engine_traits.h b/src/json/engine_traits.h
index 125c6495..9ecdc80f 100644
--- a/src/json/engine_traits.h
+++ b/src/json/engine_traits.h
@@ -298,6 +298,7 @@ SC_STREAMDEF(scxt::engine::Group, SC_FROM({
                  findIf(v, "outputInfo", group.outputInfo);
                  findIf(v, "processorStorage", group.processorStorage);
                  findIf(v, "routingTable", group.routingTable);
+                 findIf(v, "triggerConditions", group.triggerConditions);
                  findIfArray(v, "modulatorStorage", group.modulatorStorage);
                  group.clearZones();
 
diff --git a/src/messaging/client/client_serial.h b/src/messaging/client/client_serial.h
index d0f149ec..8b996a8a 100644
--- a/src/messaging/client/client_serial.h
+++ b/src/messaging/client/client_serial.h
@@ -103,6 +103,8 @@ enum ClientToSerializationMessagesIds
     c2s_update_group_output_int16_t_value,
     c2s_update_group_output_bool_value,
 
+    c2s_update_group_trigger_conditions,
+
     // #1141 done up until here. Below this point the name rubric above isn't confirmed in place
     c2s_request_pgz_structure, // ?
 
diff --git a/src/messaging/client/group_messages.h b/src/messaging/client/group_messages.h
index 86a92dc3..73ca34d5 100644
--- a/src/messaging/client/group_messages.h
+++ b/src/messaging/client/group_messages.h
@@ -58,5 +58,24 @@ CLIENT_TO_SERIAL_CONSTRAINED(UpdateGroupOutputBoolValue, c2s_update_group_output
                              detail::updateGroupMemberValue(&engine::Group::outputInfo, payload,
                                                             engine, cont));
 
+inline void doUpdateGroupTriggerConditions(const engine::GroupTriggerConditions &payload,
+                                           const engine::Engine &engine, MessageController &cont)
+{
+    auto ga = engine.getSelectionManager()->currentLeadGroup(engine);
+    if (ga.has_value())
+    {
+        cont.scheduleAudioThreadCallback([p = payload, g = *ga](auto &eng) {
+            auto &grp = eng.getPatch()->getPart(g.part)->getGroup(g.group);
+            grp->triggerConditions = p;
+            grp->triggerConditions.setupOnUnstream(
+                eng.getPatch()->getPart(g.part)->groupTriggerInstrumentState);
+        });
+    }
+}
+
+CLIENT_TO_SERIAL(UpdateGroupTriggerConditions, c2s_update_group_trigger_conditions,
+                 scxt::engine::GroupTriggerConditions,
+                 doUpdateGroupTriggerConditions(payload, engine, cont));
+
 } // namespace scxt::messaging::client
 #endif // SHORTCIRCUIT_GROUP_MESSAGES_H