diff --git a/devices/wim/config/ags/config.js b/devices/wim/config/ags/config.js
index 48267d9d..0818ae69 100644
--- a/devices/wim/config/ags/config.js
+++ b/devices/wim/config/ags/config.js
@@ -21,6 +21,7 @@ exec(`sassc ${scss} ${css}`);
 Setup();
 
 
+// TODO: get rid of 'properties' and 'binds' prop
 export default {
     style: css,
     notificationPopupTimeout: 5000,
diff --git a/devices/wim/config/ags/js/applauncher/main.js b/devices/wim/config/ags/js/applauncher/main.js
index cf6ef326..41be888c 100644
--- a/devices/wim/config/ags/js/applauncher/main.js
+++ b/devices/wim/config/ags/js/applauncher/main.js
@@ -101,6 +101,23 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => {
         className: 'applauncher',
         vertical: true,
 
+        setup: (self) => {
+            self.hook(App, (_, name, visible) => {
+                if (name !== window_name) {
+                    return;
+                }
+
+                entry.text = '';
+
+                if (visible) {
+                    entry.grab_focus();
+                }
+                else {
+                    makeNewChildren();
+                }
+            });
+        },
+
         children: [
             Box({
                 className: 'header',
@@ -119,21 +136,6 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => {
                 }),
             }),
         ],
-
-        connections: [[App, (_, name, visible) => {
-            if (name !== window_name) {
-                return;
-            }
-
-            entry.text = '';
-
-            if (visible) {
-                entry.grab_focus();
-            }
-            else {
-                makeNewChildren();
-            }
-        }]],
     });
 };
 
diff --git a/devices/wim/config/ags/js/bar/buttons/audio.js b/devices/wim/config/ags/js/bar/buttons/audio.js
index 9dd249ae..643146d8 100644
--- a/devices/wim/config/ags/js/bar/buttons/audio.js
+++ b/devices/wim/config/ags/js/bar/buttons/audio.js
@@ -13,11 +13,13 @@ const SpeakerIndicator = (props) => Icon({
 
 const SpeakerPercentLabel = (props) => Label({
     ...props,
-    connections: [[Audio, (label) => {
-        if (Audio.speaker) {
-            label.label = `${Math.round(Audio.speaker.volume * 100)}%`;
-        }
-    }, 'speaker-changed']],
+    setup: (self) => {
+        self.hook(Audio, (label) => {
+            if (Audio.speaker) {
+                label.label = `${Math.round(Audio.speaker.volume * 100)}%`;
+            }
+        }, 'speaker-changed');
+    },
 });
 
 const SPACING = 5;
diff --git a/devices/wim/config/ags/js/bar/buttons/battery.js b/devices/wim/config/ags/js/bar/buttons/battery.js
index e0f81e6b..4a7aafdc 100644
--- a/devices/wim/config/ags/js/bar/buttons/battery.js
+++ b/devices/wim/config/ags/js/bar/buttons/battery.js
@@ -12,20 +12,24 @@ const Indicator = () => Icon({
 
     binds: [['icon', Battery, 'icon-name']],
 
-    connections: [[Battery, (self) => {
-        self.toggleClassName('charging', Battery.charging);
-        self.toggleClassName('charged', Battery.charged);
-        self.toggleClassName('low', Battery.percent < LOW_BATT);
-    }]],
+    setup: (self) => {
+        self.hook(Battery, () => {
+            self.toggleClassName('charging', Battery.charging);
+            self.toggleClassName('charged', Battery.charged);
+            self.toggleClassName('low', Battery.percent < LOW_BATT);
+        });
+    },
 });
 
 const LevelLabel = (props) => Label({
     ...props,
     className: 'label',
 
-    connections: [[Battery, (self) => {
-        self.label = `${Battery.percent}%`;
-    }]],
+    setup: (self) => {
+        self.hook(Battery, () => {
+            self.label = `${Battery.percent}%`;
+        });
+    },
 });
 
 const SPACING = 5;
diff --git a/devices/wim/config/ags/js/bar/buttons/bluetooth.js b/devices/wim/config/ags/js/bar/buttons/bluetooth.js
index c9c19512..4ee86436 100644
--- a/devices/wim/config/ags/js/bar/buttons/bluetooth.js
+++ b/devices/wim/config/ags/js/bar/buttons/bluetooth.js
@@ -7,25 +7,29 @@ import Separator from '../../misc/separator.js';
 
 const Indicator = (props) => Icon({
     ...props,
-    connections: [[Bluetooth, (self) => {
-        if (Bluetooth.enabled) {
-            self.icon = Bluetooth.connectedDevices[0] ?
-                Bluetooth.connectedDevices[0].iconName :
-                'bluetooth-active-symbolic';
-        }
-        else {
-            self.icon = 'bluetooth-disabled-symbolic';
-        }
-    }]],
+    setup: (self) => {
+        self.hook(Bluetooth, () => {
+            if (Bluetooth.enabled) {
+                self.icon = Bluetooth.connectedDevices[0] ?
+                    Bluetooth.connectedDevices[0].iconName :
+                    'bluetooth-active-symbolic';
+            }
+            else {
+                self.icon = 'bluetooth-disabled-symbolic';
+            }
+        });
+    },
 });
 
 const ConnectedLabel = (props) => Label({
     ...props,
-    connections: [[Bluetooth, (self) => {
-        self.label = Bluetooth.connectedDevices[0] ?
-            `${Bluetooth.connectedDevices[0]}` :
-            'Disconnected';
-    }, 'notify::connected-devices']],
+    setup: (self) => {
+        self.hook(Bluetooth, () => {
+            self.label = Bluetooth.connectedDevices[0] ?
+                `${Bluetooth.connectedDevices[0]}` :
+                'Disconnected';
+        }, 'notify::connected-devices');
+    },
 });
 
 const SPACING = 5;
diff --git a/devices/wim/config/ags/js/bar/buttons/brightness.js b/devices/wim/config/ags/js/bar/buttons/brightness.js
index 20d3d4c1..3d9b4272 100644
--- a/devices/wim/config/ags/js/bar/buttons/brightness.js
+++ b/devices/wim/config/ags/js/bar/buttons/brightness.js
@@ -13,9 +13,11 @@ const Indicator = (props) => Icon({
 
 const BrightnessPercentLabel = (props) => Label({
     ...props,
-    connections: [[Brightness, (self) => {
-        self.label = `${Math.round(Brightness.screen * 100)}%`;
-    }, 'screen']],
+    setup: (self) => {
+        self.hook(Brightness, () => {
+            self.label = `${Math.round(Brightness.screen * 100)}%`;
+        }, 'screen');
+    },
 });
 
 export default () => {
diff --git a/devices/wim/config/ags/js/bar/buttons/clock.js b/devices/wim/config/ags/js/bar/buttons/clock.js
index a9394041..18107665 100644
--- a/devices/wim/config/ags/js/bar/buttons/clock.js
+++ b/devices/wim/config/ags/js/bar/buttons/clock.js
@@ -8,36 +8,32 @@ const { DateTime } = GLib;
 import EventBox from '../../misc/cursorbox.js';
 
 
-const ClockModule = ({
-    interval = 1000,
-    ...props
-} = {}) => {
-    return Label({
-        ...props,
-        className: 'clock',
+const ClockModule = () => Label({
+    className: 'clock',
 
-        connections: [[interval, (self) => {
+    setup: (self) => {
+        self.poll(1000, () => {
             const time = DateTime.new_now_local();
 
             self.label = time.format('%a. ') +
                 time.get_day_of_month() +
                 time.format(' %b. %H:%M');
-        }]],
-    });
-};
+        });
+    },
+});
 
 export default () => EventBox({
     className: 'toggle-off',
 
     onPrimaryClickRelease: () => App.toggleWindow('calendar'),
 
-    connections: [
-        [App, (self, windowName, visible) => {
+    setup: (self) => {
+        self.hook(App, (_, windowName, visible) => {
             if (windowName === 'calendar') {
                 self.toggleClassName('toggle-on', visible);
             }
-        }],
-    ],
+        });
+    },
 
     child: ClockModule(),
 });
diff --git a/devices/wim/config/ags/js/bar/buttons/current-window.js b/devices/wim/config/ags/js/bar/buttons/current-window.js
index 2ae4b7c1..49b045a8 100644
--- a/devices/wim/config/ags/js/bar/buttons/current-window.js
+++ b/devices/wim/config/ags/js/bar/buttons/current-window.js
@@ -13,14 +13,17 @@ export default () => Box({
 
         Icon({
             size: 30,
-            connections: [[Hyprland.active.client, (self) => {
-                const app = Applications.query(Hyprland.active.client.class)[0];
+            setup: (self) => {
+                self.hook(Hyprland.active.client, () => {
+                    const app = Applications
+                        .query(Hyprland.active.client.class)[0];
 
-                if (app) {
-                    self.icon = app.iconName;
-                    self.visible = Hyprland.active.client.title !== '';
-                }
-            }]],
+                    if (app) {
+                        self.icon = app.iconName;
+                        self.visible = Hyprland.active.client.title !== '';
+                    }
+                });
+            },
         }),
 
         Separator(SPACING),
diff --git a/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js b/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js
index d0ab3568..c02c763c 100644
--- a/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js
+++ b/devices/wim/config/ags/js/bar/buttons/keyboard-layout.js
@@ -8,40 +8,44 @@ const DEFAULT_KB = 'at-translated-set-2-keyboard';
 const SPACING = 4;
 
 
+const Indicator = () => Label({
+    css: 'font-size: 20px;',
+    setup: (self) => {
+        self.hook(Hyprland, (_, _n, layout) => {
+            if (layout) {
+                if (layout === 'error') {
+                    return;
+                }
+
+                const shortName = layout.match(/\(([A-Za-z]+)\)/);
+
+                self.label = shortName ? shortName[1] : layout;
+            }
+            else {
+                // At launch, kb layout is undefined
+                Hyprland.sendMessage('j/devices').then((obj) => {
+                    const kb = JSON.parse(obj).keyboards
+                        .find((val) => val.name === DEFAULT_KB);
+
+                    layout = kb['active_keymap'];
+
+                    const shortName = layout
+                        .match(/\(([A-Za-z]+)\)/);
+
+                    self.label = shortName ? shortName[1] : layout;
+                }).catch(print);
+            }
+        }, 'keyboard-layout');
+    },
+});
+
 export default () => {
     const rev = Revealer({
         transition: 'slide_right',
         child: Box({
             children: [
                 Separator(SPACING),
-                Label({
-                    css: 'font-size: 20px;',
-                    connections: [[Hyprland, (self, _n, layout) => {
-                        if (layout) {
-                            if (layout === 'error') {
-                                return;
-                            }
-
-                            const shortName = layout.match(/\(([A-Za-z]+)\)/);
-
-                            self.label = shortName ? shortName[1] : layout;
-                        }
-                        else {
-                        // At launch, kb layout is undefined
-                            Hyprland.sendMessage('j/devices').then((obj) => {
-                                const kb = JSON.parse(obj).keyboards
-                                    .find((val) => val.name === DEFAULT_KB);
-
-                                layout = kb['active_keymap'];
-
-                                const shortName = layout
-                                    .match(/\(([A-Za-z]+)\)/);
-
-                                self.label = shortName ? shortName[1] : layout;
-                            }).catch(print);
-                        }
-                    }, 'keyboard-layout']],
-                }),
+                Indicator(),
             ],
         }),
     });
diff --git a/devices/wim/config/ags/js/bar/buttons/network.js b/devices/wim/config/ags/js/bar/buttons/network.js
index 872edf05..c2b5f2bb 100644
--- a/devices/wim/config/ags/js/bar/buttons/network.js
+++ b/devices/wim/config/ags/js/bar/buttons/network.js
@@ -7,36 +7,40 @@ import Separator from '../../misc/separator.js';
 
 const Indicator = (props) => Icon({
     ...props,
-    connections: [[Network, (self) => {
-        if (Network.wifi.internet === 'connected' ||
-            Network.wifi.internet === 'connecting') {
-            self.icon = Network.wifi.iconName;
-        }
-        else if (Network.wired.internet === 'connected' ||
-                 Network.wired.internet === 'connecting') {
-            self.icon = Network.wired.iconName;
-        }
-        else {
-            self.icon = Network.wifi.iconName;
-        }
-    }]],
+    setup: (self) => {
+        self.hook(Network, () => {
+            if (Network.wifi.internet === 'connected' ||
+                Network.wifi.internet === 'connecting') {
+                self.icon = Network.wifi.iconName;
+            }
+            else if (Network.wired.internet === 'connected' ||
+                Network.wired.internet === 'connecting') {
+                self.icon = Network.wired.iconName;
+            }
+            else {
+                self.icon = Network.wifi.iconName;
+            }
+        });
+    },
 });
 
 const APLabel = (props) => Label({
     ...props,
-    connections: [[Network, (self) => {
-        if (Network.wifi.internet === 'connected' ||
-            Network.wifi.internet === 'connecting') {
-            self.label = Network.wifi.ssid;
-        }
-        else if (Network.wired.internet === 'connected' ||
-                 Network.wired.internet === 'connecting') {
-            self.label = 'Connected';
-        }
-        else {
-            self.label = 'Disconnected';
-        }
-    }]],
+    setup: (self) => {
+        self.hook(Network, () => {
+            if (Network.wifi.internet === 'connected' ||
+                Network.wifi.internet === 'connecting') {
+                self.label = Network.wifi.ssid;
+            }
+            else if (Network.wired.internet === 'connected' ||
+                Network.wired.internet === 'connecting') {
+                self.label = 'Connected';
+            }
+            else {
+                self.label = 'Disconnected';
+            }
+        });
+    },
 });
 
 const SPACING = 5;
diff --git a/devices/wim/config/ags/js/bar/buttons/notif-button.js b/devices/wim/config/ags/js/bar/buttons/notif-button.js
index de52082e..2038bf4f 100644
--- a/devices/wim/config/ags/js/bar/buttons/notif-button.js
+++ b/devices/wim/config/ags/js/bar/buttons/notif-button.js
@@ -19,11 +19,13 @@ export default () => EventBox({
         App.toggleWindow('notification-center');
     },
 
-    connections: [[App, (self, windowName, visible) => {
-        if (windowName === 'notification-center') {
-            self.toggleClassName('toggle-on', visible);
-        }
-    }]],
+    setup: (self) => {
+        self.hook(App, (_, windowName, visible) => {
+            if (windowName === 'notification-center') {
+                self.toggleClassName('toggle-on', visible);
+            }
+        });
+    },
 
     child: CenterBox({
         className: 'notif-panel',
@@ -31,17 +33,19 @@ export default () => EventBox({
         center_widget: Box({
             children: [
                 Icon({
-                    connections: [[Notifications, (self) => {
-                        if (Notifications.dnd) {
-                            self.icon = 'notification-disabled-symbolic';
-                        }
-                        else if (Notifications.notifications.length > 0) {
-                            self.icon = 'notification-new-symbolic';
-                        }
-                        else {
-                            self.icon = 'notification-symbolic';
-                        }
-                    }]],
+                    setup: (self) => {
+                        self.hook(Notifications, () => {
+                            if (Notifications.dnd) {
+                                self.icon = 'notification-disabled-symbolic';
+                            }
+                            else if (Notifications.notifications.length > 0) {
+                                self.icon = 'notification-new-symbolic';
+                            }
+                            else {
+                                self.icon = 'notification-symbolic';
+                            }
+                        });
+                    },
                 }),
 
                 Separator(SPACING),
diff --git a/devices/wim/config/ags/js/bar/buttons/osk-toggle.js b/devices/wim/config/ags/js/bar/buttons/osk-toggle.js
index 7670a3bc..bda505b9 100644
--- a/devices/wim/config/ags/js/bar/buttons/osk-toggle.js
+++ b/devices/wim/config/ags/js/bar/buttons/osk-toggle.js
@@ -9,9 +9,11 @@ export default () => EventBox({
 
     onPrimaryClickRelease: () => Tablet.toggleOsk(),
 
-    connections: [[Tablet, (self) => {
-        self.toggleClassName('toggle-on', Tablet.oskState);
-    }, 'osk-toggled']],
+    setup: (self) => {
+        self.hook(Tablet, () => {
+            self.toggleClassName('toggle-on', Tablet.oskState);
+        }, 'osk-toggled');
+    },
 
     child: Box({
         className: 'osk-toggle',
diff --git a/devices/wim/config/ags/js/bar/buttons/quick-settings.js b/devices/wim/config/ags/js/bar/buttons/quick-settings.js
index a914ad70..e7e566e8 100644
--- a/devices/wim/config/ags/js/bar/buttons/quick-settings.js
+++ b/devices/wim/config/ags/js/bar/buttons/quick-settings.js
@@ -26,11 +26,13 @@ export default () => EventBox({
         App.toggleWindow('quick-settings');
     },
 
-    connections: [[App, (self, windowName, visible) => {
-        if (windowName === 'quick-settings') {
-            self.toggleClassName('toggle-on', visible);
-        }
-    }]],
+    setup: (self) => {
+        self.hook(App, (_, windowName, visible) => {
+            if (windowName === 'quick-settings') {
+                self.toggleClassName('toggle-on', visible);
+            }
+        });
+    },
 
     child: Box({
         className: 'quick-settings-toggle',
diff --git a/devices/wim/config/ags/js/bar/buttons/systray.js b/devices/wim/config/ags/js/bar/buttons/systray.js
index cb68f60b..1c18d0f0 100644
--- a/devices/wim/config/ags/js/bar/buttons/systray.js
+++ b/devices/wim/config/ags/js/bar/buttons/systray.js
@@ -33,42 +33,39 @@ const SysTray = () => MenuBar({
     setup: (self) => {
         self.items = new Map();
 
-        self.onAdded = (id) => {
-            const item = SystemTray.getItem(id);
+        self
+            .hook(SystemTray, (_, id) => {
+                const item = SystemTray.getItem(id);
 
-            if (self.items.has(id) || !item) {
-                return;
-            }
+                if (self.items.has(id) || !item) {
+                    return;
+                }
 
-            const w = SysTrayItem(item);
+                const w = SysTrayItem(item);
 
-            // Early return if item is in blocklist
-            if (!w) {
-                return;
-            }
+                // Early return if item is in blocklist
+                if (!w) {
+                    return;
+                }
 
-            self.items.set(id, w);
-            self.add(w);
-            self.show_all();
-            w.child.revealChild = true;
-        };
+                self.items.set(id, w);
+                self.add(w);
+                self.show_all();
+                w.child.revealChild = true;
+            }, 'added')
 
-        self.onRemoved = (id) => {
-            if (!self.items.has(id)) {
-                return;
-            }
+            .hook(SystemTray, (_, id) => {
+                if (!self.items.has(id)) {
+                    return;
+                }
 
-            self.items.get(id).child.revealChild = false;
-            timeout(REVEAL_DURATION, () => {
-                self.items.get(id).destroy();
-                self.items.delete(id);
-            });
-        };
+                self.items.get(id).child.revealChild = false;
+                timeout(REVEAL_DURATION, () => {
+                    self.items.get(id).destroy();
+                    self.items.delete(id);
+                });
+            }, 'removed');
     },
-    connections: [
-        [SystemTray, (self, id) => self.onAdded(id), 'added'],
-        [SystemTray, (self, id) => self.onRemoved(id), 'removed'],
-    ],
 });
 
 export default () => {
@@ -77,9 +74,11 @@ export default () => {
     return Revealer({
         transition: 'slide_right',
 
-        connections: [[SystemTray, (rev) => {
-            rev.revealChild = systray.get_children().length > 0;
-        }]],
+        setup: (self) => {
+            self.hook(SystemTray, () => {
+                self.revealChild = systray.get_children().length > 0;
+            });
+        },
 
         child: Box({
             children: [
diff --git a/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js b/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js
index 85bc9c77..3b5240f1 100644
--- a/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js
+++ b/devices/wim/config/ags/js/bar/buttons/tablet-toggle.js
@@ -9,9 +9,11 @@ export default () => EventBox({
 
     onPrimaryClickRelease: () => Tablet.toggleMode(),
 
-    connections: [[Tablet, (self) => {
-        self.toggleClassName('toggle-on', Tablet.tabletMode);
-    }, 'mode-toggled']],
+    setup: (self) => {
+        self.hook(Tablet, () => {
+            self.toggleClassName('toggle-on', Tablet.tabletMode);
+        }, 'mode-toggled');
+    },
 
     child: Box({
         className: 'tablet-toggle',
diff --git a/devices/wim/config/ags/js/bar/buttons/workspaces.js b/devices/wim/config/ags/js/bar/buttons/workspaces.js
index 697a8550..5ff53cea 100644
--- a/devices/wim/config/ags/js/bar/buttons/workspaces.js
+++ b/devices/wim/config/ags/js/bar/buttons/workspaces.js
@@ -43,19 +43,19 @@ const Workspace = ({ i } = {}) => {
                             }
                         }
                     };
+
+                    self
+                        .hook(Hyprland, () => self.update())
+                        // Deal with urgent windows
+                        .hook(Hyprland, (_, a) => {
+                            self.update(a);
+                        }, 'urgent-window')
+                        .hook(Hyprland.active.workspace, () => {
+                            if (Hyprland.active.workspace.id === i) {
+                                self.toggleClassName('urgent', false);
+                            }
+                        });
                 },
-
-                connections: [
-                    [Hyprland, (self) => self.update()],
-
-                    // Deal with urgent windows
-                    [Hyprland, (self, a) => self.update(a), 'urgent-window'],
-                    [Hyprland.active.workspace, (self) => {
-                        if (Hyprland.active.workspace.id === i) {
-                            self.toggleClassName('urgent', false);
-                        }
-                    }],
-                ],
             }),
         }),
     });
@@ -81,7 +81,9 @@ export default () => {
         vpack: 'center',
         hpack: 'start',
         className: 'button active',
-        connections: [[Hyprland.active.workspace, updateHighlight]],
+        setup: (self) => {
+            self.hook(Hyprland.active.workspace, updateHighlight);
+        },
     });
 
     const widget = Overlay({
@@ -122,21 +124,23 @@ export default () => {
                     }],
                 ],
 
-                connections: [[Hyprland, (self) => {
-                    self._workspaces = self.children.filter((ch) => {
-                        return Hyprland.workspaces.find((ws) => {
-                            return ws.id === ch._id;
-                        });
-                    }).sort((a, b) => a._id - b._id);
+                setup: (self) => {
+                    self.hook(Hyprland, () => {
+                        self._workspaces = self.children.filter((ch) => {
+                            return Hyprland.workspaces.find((ws) => {
+                                return ws.id === ch._id;
+                            });
+                        }).sort((a, b) => a._id - b._id);
 
-                    self._updateWorkspaces(self);
-                    self._refresh(self);
+                        self._updateWorkspaces(self);
+                        self._refresh(self);
 
-                    // Make sure the highlight doesn't go too far
-                    const TEMP_TIMEOUT = 10;
+                        // Make sure the highlight doesn't go too far
+                        const TEMP_TIMEOUT = 10;
 
-                    timeout(TEMP_TIMEOUT, () => updateHighlight(highlight));
-                }]],
+                        timeout(TEMP_TIMEOUT, () => updateHighlight(highlight));
+                    });
+                },
             }),
         }),
     });
diff --git a/devices/wim/config/ags/js/bar/fullscreen.js b/devices/wim/config/ags/js/bar/fullscreen.js
index 4eb3fa4f..75ecbf4c 100644
--- a/devices/wim/config/ags/js/bar/fullscreen.js
+++ b/devices/wim/config/ags/js/bar/fullscreen.js
@@ -31,19 +31,20 @@ export default (props) => {
         hexpand: true,
         vertical: true,
 
-        connections: [
-            [Hyprland.active, () => {
-                const workspace = Hyprland.getWorkspace(
-                    Hyprland.active.workspace.id,
-                );
+        setup: (self) => {
+            self
+                .hook(Hyprland.active, () => {
+                    const workspace = Hyprland.getWorkspace(
+                        Hyprland.active.workspace.id,
+                    );
 
-                Revealed.value = !workspace?.hasfullscreen;
-            }],
+                    Revealed.value = !workspace?.hasfullscreen;
+                })
 
-            [Hyprland, (_, fullscreen) => {
-                Revealed.value = !fullscreen;
-            }, 'fullscreen'],
-        ],
+                .hook(Hyprland, (_, fullscreen) => {
+                    Revealed.value = !fullscreen;
+                }, 'fullscreen');
+        },
 
         children: [
             Revealer({
diff --git a/devices/wim/config/ags/js/date.js b/devices/wim/config/ags/js/date.js
index 82df7413..42c00333 100644
--- a/devices/wim/config/ags/js/date.js
+++ b/devices/wim/config/ags/js/date.js
@@ -25,9 +25,11 @@ const Time = () => Box({
                 Label({
                     className: 'content',
                     label: 'hour',
-                    connections: [[1000, (self) => {
-                        self.label = DateTime.new_now_local().format('%H');
-                    }]],
+                    setup: (self) => {
+                        self.poll(1000, () => {
+                            self.label = DateTime.new_now_local().format('%H');
+                        });
+                    },
                 }),
 
                 Divider(),
@@ -35,9 +37,11 @@ const Time = () => Box({
                 Label({
                     className: 'content',
                     label: 'minute',
-                    connections: [[1000, (self) => {
-                        self.label = DateTime.new_now_local().format('%M');
-                    }]],
+                    setup: (self) => {
+                        self.poll(1000, () => {
+                            self.label = DateTime.new_now_local().format('%M');
+                        });
+                    },
                 }),
 
             ],
@@ -49,13 +53,15 @@ const Time = () => Box({
             child: Label({
                 css: 'font-size: 20px',
                 label: 'complete date',
-                connections: [[1000, (self) => {
-                    const time = DateTime.new_now_local();
+                setup: (self) => {
+                    self.poll(1000, () => {
+                        const time = DateTime.new_now_local();
 
-                    self.label = time.format('%A, %B ') +
-                                 time.get_day_of_month() +
-                                 time.format(', %Y');
-                }]],
+                        self.label = time.format('%A, %B ') +
+                            time.get_day_of_month() +
+                            time.format(', %Y');
+                    });
+                },
             }),
         }),
 
diff --git a/devices/wim/config/ags/js/media-player/gesture.js b/devices/wim/config/ags/js/media-player/gesture.js
index ac1bd622..87e477fc 100644
--- a/devices/wim/config/ags/js/media-player/gesture.js
+++ b/devices/wim/config/ags/js/media-player/gesture.js
@@ -12,7 +12,7 @@ const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease,
 
 export default ({
     properties,
-    connections,
+    setup = () => {/**/},
     props,
 } = {}) => {
     const widget = EventBox();
@@ -33,101 +33,103 @@ export default ({
 
         child: emptyPlayer,
 
-        connections: [
-            ...connections,
+        setup: (self) => {
+            setup(self);
 
-            [gesture, (overlay, realGesture) => {
-                if (realGesture) {
-                    overlay.list().forEach((over) => {
-                        over.visible = true;
-                    });
-                }
-                else {
-                    overlay.showTopOnly();
-                }
+            self
+                .hook(gesture, (overlay, realGesture) => {
+                    if (realGesture) {
+                        overlay.list().forEach((over) => {
+                            over.visible = true;
+                        });
+                    }
+                    else {
+                        overlay.showTopOnly();
+                    }
 
-                // Don't allow gesture when only one player
-                if (overlay.list().length <= 1) {
-                    return;
-                }
+                    // Don't allow gesture when only one player
+                    if (overlay.list().length <= 1) {
+                        return;
+                    }
 
-                overlay._dragging = true;
-                let offset = gesture.get_offset()[1];
+                    overlay._dragging = true;
+                    let offset = gesture.get_offset()[1];
 
-                const playerBox = overlay.list().at(-1);
+                    const playerBox = overlay.list().at(-1);
 
-                // Slide right
-                if (offset >= 0) {
-                    playerBox.setCss(`
-                        margin-left:   ${offset}px;
-                        margin-right: -${offset}px;
-                        ${playerBox._bgStyle}
-                    `);
-                }
-
-                // Slide left
-                else {
-                    offset = Math.abs(offset);
-                    playerBox.setCss(`
-                        margin-left: -${offset}px;
-                        margin-right: ${offset}px;
-                        ${playerBox._bgStyle}
-                    `);
-                }
-            }, 'drag-update'],
-
-            [gesture, (overlay) => {
-                // Don't allow gesture when only one player
-                if (overlay.list().length <= 1) {
-                    return;
-                }
-
-                overlay._dragging = false;
-                const offset = gesture.get_offset()[1];
-
-                const playerBox = overlay.list().at(-1);
-
-                // If crosses threshold after letting go, slide away
-                if (Math.abs(offset) > MAX_OFFSET) {
-                    // Disable inputs during animation
-                    widget.sensitive = false;
-
-                    // Slide away right
+                    // Slide right
                     if (offset >= 0) {
                         playerBox.setCss(`
-                            ${TRANSITION}
-                            margin-left:   ${OFFSCREEN}px;
-                            margin-right: -${OFFSCREEN}px;
-                            opacity: 0.7; ${playerBox._bgStyle}
+                            margin-left:   ${offset}px;
+                            margin-right: -${offset}px;
+                            ${playerBox._bgStyle}
                         `);
                     }
 
-                    // Slide away left
+                    // Slide left
                     else {
+                        offset = Math.abs(offset);
                         playerBox.setCss(`
-                            ${TRANSITION}
-                            margin-left: -${OFFSCREEN}px;
-                            margin-right: ${OFFSCREEN}px;
-                            opacity: 0.7; ${playerBox._bgStyle}`);
+                            margin-left: -${offset}px;
+                            margin-right: ${offset}px;
+                            ${playerBox._bgStyle}
+                        `);
+                    }
+                }, 'drag-update')
+
+                .hook(gesture, (overlay) => {
+                    // Don't allow gesture when only one player
+                    if (overlay.list().length <= 1) {
+                        return;
                     }
 
-                    timeout(ANIM_DURATION, () => {
-                        // Put the player in the back after anim
-                        overlay.reorder_overlay(playerBox, 0);
-                        // Recenter player
-                        playerBox.setCss(playerBox._bgStyle);
+                    overlay._dragging = false;
+                    const offset = gesture.get_offset()[1];
 
-                        widget.sensitive = true;
+                    const playerBox = overlay.list().at(-1);
 
-                        overlay.showTopOnly();
-                    });
-                }
-                else {
-                    // Recenter with transition for animation
-                    playerBox.setCss(`${TRANSITION} ${playerBox._bgStyle}`);
-                }
-            }, 'drag-end'],
-        ],
+                    // If crosses threshold after letting go, slide away
+                    if (Math.abs(offset) > MAX_OFFSET) {
+                        // Disable inputs during animation
+                        widget.sensitive = false;
+
+                        // Slide away right
+                        if (offset >= 0) {
+                            playerBox.setCss(`
+                                ${TRANSITION}
+                                margin-left:   ${OFFSCREEN}px;
+                                margin-right: -${OFFSCREEN}px;
+                                opacity: 0.7; ${playerBox._bgStyle}
+                            `);
+                        }
+
+                        // Slide away left
+                        else {
+                            playerBox.setCss(`
+                                ${TRANSITION}
+                                margin-left: -${OFFSCREEN}px;
+                                margin-right: ${OFFSCREEN}px;
+                                opacity: 0.7; ${playerBox._bgStyle}
+                            `);
+                        }
+
+                        timeout(ANIM_DURATION, () => {
+                            // Put the player in the back after anim
+                            overlay.reorder_overlay(playerBox, 0);
+                            // Recenter player
+                            playerBox.setCss(playerBox._bgStyle);
+
+                            widget.sensitive = true;
+
+                            overlay.showTopOnly();
+                        });
+                    }
+                    else {
+                        // Recenter with transition for animation
+                        playerBox.setCss(`${TRANSITION} ${playerBox._bgStyle}`);
+                    }
+                }, 'drag-end');
+        },
     });
 
     widget.add(content);
diff --git a/devices/wim/config/ags/js/media-player/mpris.js b/devices/wim/config/ags/js/media-player/mpris.js
index 7aa12a3c..8a97d16b 100644
--- a/devices/wim/config/ags/js/media-player/mpris.js
+++ b/devices/wim/config/ags/js/media-player/mpris.js
@@ -60,19 +60,18 @@ export const CoverArt = (player, props) => CenterBox({
                 self.setCss(self._bgStyle);
             }
         });
-    },
 
-    connections: [[player, (self) => {
-        execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] &&
+        self.hook(player, () => {
+            execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] &&
                   coloryou "${player.coverPath}" | grep -v Warning`])
-            .then((out) => {
-                if (!Mpris.players.find((p) => player === p)) {
-                    return;
-                }
+                .then((out) => {
+                    if (!Mpris.players.find((p) => player === p)) {
+                        return;
+                    }
 
-                player.colors.value = JSON.parse(out);
+                    player.colors.value = JSON.parse(out);
 
-                self._bgStyle = `
+                    self._bgStyle = `
                     background: radial-gradient(circle,
                                 rgba(0, 0, 0, 0.4) 30%,
                                 ${player.colors.value.imageAccent}),
@@ -81,15 +80,16 @@ export const CoverArt = (player, props) => CenterBox({
                     background-position: center;
                 `;
 
-                if (!self.get_parent()._dragging) {
-                    self.setCss(self._bgStyle);
-                }
-            }).catch((err) => {
-                if (err !== '') {
-                    print(err);
-                }
-            });
-    }]],
+                    if (!self.get_parent()._dragging) {
+                        self.setCss(self._bgStyle);
+                    }
+                }).catch((err) => {
+                    if (err !== '') {
+                        print(err);
+                    }
+                });
+        });
+    },
 });
 
 export const TitleLabel = (player, props) => Label({
@@ -127,27 +127,31 @@ export const PlayerIcon = (player, overlay, props) => {
             className: widget ? 'position-indicator' : 'player-icon',
             size: widget ? '' : ICON_SIZE,
 
-            connections: [[p, (self) => {
-                self.icon = lookUpIcon(p.entry) ?
-                    p.entry :
-                    icons.mpris.fallback;
-            }]],
+            setup: (self) => {
+                self.hook(p, () => {
+                    self.icon = lookUpIcon(p.entry) ?
+                        p.entry :
+                        icons.mpris.fallback;
+                });
+            },
         }),
     });
 
     return Box({
-        connections: [[Mpris, (self) => {
-            const thisIndex = overlay.list()
-                .indexOf(self.get_parent().get_parent());
+        setup: (self) => {
+            self.hook(Mpris, () => {
+                const thisIndex = overlay.list()
+                    .indexOf(self.get_parent().get_parent());
 
-            self.children = overlay.list().map((over, i) => {
-                self.children.push(Separator(2));
+                self.children = overlay.list().map((over, i) => {
+                    self.children.push(Separator(2));
 
-                return i === thisIndex ?
-                    playerIcon(player) :
-                    playerIcon(over._player, overlay, over);
-            }).reverse();
-        }]],
+                    return i === thisIndex ?
+                        playerIcon(player) :
+                        playerIcon(over._player, overlay, over);
+                }).reverse();
+            });
+        },
     });
 };
 
@@ -163,38 +167,38 @@ export const PositionSlider = (player, props) => Slider({
         player.position = player.length * value;
     },
 
-    properties: [['update', (slider) => {
-        if (!slider.dragging) {
-            slider.visible = player.length > 0;
-            if (player.length > 0) {
-                slider.value = player.position / player.length;
+    setup: (self) => {
+        const update = () => {
+            if (!self.dragging) {
+                self.visible = player.length > 0;
+                if (player.length > 0) {
+                    self.value = player.position / player.length;
+                }
             }
-        }
-    }]],
+        };
 
-    connections: [
-        [1000, (s) => s._update(s)],
-        [player, (s) => s._update(s), 'position'],
-        [player.colors, (s) => {
-            if (player.colors.value) {
-                const c = player.colors.value;
+        self
+            .poll(1000, () => update())
+            .hook(player, () => update(), 'position')
+            .hook(player.colors, () => {
+                if (player.colors.value) {
+                    const c = player.colors.value;
 
-                s.setCss(`
-                    highlight    { background-color: ${c.buttonAccent}; }
-                    slider       { background-color: ${c.buttonAccent}; }
-                    slider:hover { background-color: ${c.hoverAccent}; }
-                    trough       { background-color: ${c.buttonText}; }
-                `);
-            }
-        }],
-
-        ['button-press-event', (s) => {
-            s.cursor = 'grabbing';
-        }],
-        ['button-release-event', (s) => {
-            s.cursor = 'pointer';
-        }],
-    ],
+                    self.setCss(`
+                        highlight    { background-color: ${c.buttonAccent}; }
+                        slider       { background-color: ${c.buttonAccent}; }
+                        slider:hover { background-color: ${c.hoverAccent}; }
+                        trough       { background-color: ${c.buttonText}; }
+                    `);
+                }
+            })
+            .on('button-press-event', (s) => {
+                s.cursor = 'grabbing';
+            })
+            .on('button-release-event', (s) => {
+                s.cursor = 'pointer';
+            });
+    },
 });
 
 const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
@@ -212,13 +216,13 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
 
                 items.forEach((item) => {
                     item[1].setCss(`
-                    background-color: ${c.hoverAccent};
-                    color: ${c.buttonText};
-                    min-height: 40px;
-                    min-width: 36px;
-                    margin-bottom: 1px;
-                    margin-right: 1px;
-                `);
+                        background-color: ${c.hoverAccent};
+                        color: ${c.buttonText};
+                        min-height: 40px;
+                        min-width: 36px;
+                        margin-bottom: 1px;
+                        margin-right: 1px;
+                    `);
                 });
             }
         },
@@ -230,60 +234,61 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
 
                 items.forEach((item) => {
                     item[1].setCss(`
-                    background-color: ${c.buttonAccent};
-                    color: ${c.buttonText};
-                    min-height: 42px;
-                    min-width: 38px;
-                `);
+                        background-color: ${c.buttonAccent};
+                        color: ${c.buttonText};
+                        min-height: 42px;
+                        min-width: 38px;
+                    `);
                 });
             }
         },
 
-        connections: [
-            [player, (button) => {
-                button.child.shown = `${player[prop]}`;
-            }],
+        setup: (self) => {
+            self
+                .hook(player, () => {
+                    self.child.shown = `${player[prop]}`;
+                })
+                .hook(player.colors, () => {
+                    if (!Mpris.players.find((p) => player === p)) {
+                        return;
+                    }
 
-            [player.colors, (button) => {
-                if (!Mpris.players.find((p) => player === p)) {
-                    return;
-                }
+                    if (player.colors.value) {
+                        const c = player.colors.value;
 
-                if (player.colors.value) {
-                    const c = player.colors.value;
-
-                    if (prop === 'playBackStatus') {
-                        if (button._hovered) {
-                            items.forEach((item) => {
-                                item[1].setCss(`
-                                background-color: ${c.hoverAccent};
-                                color: ${c.buttonText};
-                                min-height: 40px;
-                                min-width: 36px;
-                                margin-bottom: 1px;
-                                margin-right: 1px;
-                            `);
-                            });
+                        if (prop === 'playBackStatus') {
+                            if (self._hovered) {
+                                items.forEach((item) => {
+                                    item[1].setCss(`
+                                        background-color: ${c.hoverAccent};
+                                        color: ${c.buttonText};
+                                        min-height: 40px;
+                                        min-width: 36px;
+                                        margin-bottom: 1px;
+                                        margin-right: 1px;
+                                    `);
+                                });
+                            }
+                            else {
+                                items.forEach((item) => {
+                                    item[1].setCss(`
+                                        background-color: ${c.buttonAccent};
+                                        color: ${c.buttonText};
+                                        min-height: 42px;
+                                        min-width: 38px;
+                                    `);
+                                });
+                            }
                         }
                         else {
-                            items.forEach((item) => {
-                                item[1].setCss(`
-                                background-color: ${c.buttonAccent};
-                                color: ${c.buttonText};
-                                min-height: 42px;
-                                min-width: 38px;`);
-                            });
+                            self.setCss(`
+                                *       { color: ${c.buttonAccent}; }
+                                *:hover { color: ${c.hoverAccent}; }
+                            `);
                         }
                     }
-                    else {
-                        button.setCss(`
-                        *       { color: ${c.buttonAccent}; }
-                        *:hover { color: ${c.hoverAccent}; }
-                    `);
-                    }
-                }
-            }],
-        ],
+                });
+        },
     }),
 });
 
diff --git a/devices/wim/config/ags/js/media-player/player.js b/devices/wim/config/ags/js/media-player/player.js
index 3f0f6cda..9cba5a4c 100644
--- a/devices/wim/config/ags/js/media-player/player.js
+++ b/devices/wim/config/ags/js/media-player/player.js
@@ -107,72 +107,72 @@ export default () => {
             ['setup', false],
         ],
 
-        connections: [
-            [Mpris, (overlay, busName) => {
-                if (overlay._players.has(busName)) {
-                    return;
-                }
-
-                // Sometimes the signal doesn't give the busName
-                if (!busName) {
-                    const player = Mpris.players.find((p) => {
-                        return !overlay._players.has(p.busName);
-                    });
-
-                    if (player) {
-                        busName = player.busName;
-                    }
-                    else {
+        setup: (self) => {
+            self
+                .hook(Mpris, (overlay, busName) => {
+                    if (overlay._players.has(busName)) {
                         return;
                     }
-                }
 
-                // Get the one on top so we can move it up later
-                const previousFirst = overlay.list().at(-1);
+                    // Sometimes the signal doesn't give the busName
+                    if (!busName) {
+                        const player = Mpris.players.find((p) => {
+                            return !overlay._players.has(p.busName);
+                        });
 
-                // Make the new player
-                const player = Mpris.getPlayer(busName);
+                        if (player) {
+                            busName = player.busName;
+                        }
+                        else {
+                            return;
+                        }
+                    }
 
-                player.colors = Variable();
-                overlay._players.set(
-                    busName,
-                    PlayerBox(player, content.getOverlay()),
-                );
-                overlay.overlays = Array.from(overlay._players.values())
-                    .map((widget) => widget);
+                    // Get the one on top so we can move it up later
+                    const previousFirst = overlay.list().at(-1);
 
-                // Select favorite player at startup
-                if (!overlay._setup && overlay._players.has(FAVE_PLAYER)) {
-                    overlay.moveToTop(overlay._players.get(FAVE_PLAYER));
-                    overlay._setup = true;
-                }
+                    // Make the new player
+                    const player = Mpris.getPlayer(busName);
 
-                // Move previousFirst on top again
-                else if (overlay.includesWidget(previousFirst)) {
-                    overlay.moveToTop(previousFirst);
-                }
-            }, 'player-added'],
+                    player.colors = Variable();
+                    overlay._players.set(
+                        busName,
+                        PlayerBox(player, content.getOverlay()),
+                    );
+                    overlay.overlays = Array.from(overlay._players.values())
+                        .map((widget) => widget);
 
+                    // Select favorite player at startup
+                    if (!overlay._setup && overlay._players.has(FAVE_PLAYER)) {
+                        overlay.moveToTop(overlay._players.get(FAVE_PLAYER));
+                        overlay._setup = true;
+                    }
 
-            [Mpris, (overlay, busName) => {
-                if (!busName || !overlay._players.has(busName)) {
-                    return;
-                }
+                    // Move previousFirst on top again
+                    else if (overlay.includesWidget(previousFirst)) {
+                        overlay.moveToTop(previousFirst);
+                    }
+                }, 'player-added')
 
-                // Get the one on top so we can move it up later
-                const previousFirst = overlay.list().at(-1);
+                .hook(Mpris, (overlay, busName) => {
+                    if (!busName || !overlay._players.has(busName)) {
+                        return;
+                    }
 
-                // Remake overlays without deleted one
-                overlay._players.delete(busName);
-                overlay.overlays = Array.from(overlay._players.values())
-                    .map((widget) => widget);
+                    // Get the one on top so we can move it up later
+                    const previousFirst = overlay.list().at(-1);
 
-                // Move previousFirst on top again
-                if (overlay.includesWidget(previousFirst)) {
-                    overlay.moveToTop(previousFirst);
-                }
-            }, 'player-closed'],
-        ],
+                    // Remake overlays without deleted one
+                    overlay._players.delete(busName);
+                    overlay.overlays = Array.from(overlay._players.values())
+                        .map((widget) => widget);
+
+                    // Move previousFirst on top again
+                    if (overlay.includesWidget(previousFirst)) {
+                        overlay.moveToTop(previousFirst);
+                    }
+                }, 'player-closed');
+        },
     });
 
     return Box({
diff --git a/devices/wim/config/ags/js/misc/cursorbox.js b/devices/wim/config/ags/js/misc/cursorbox.js
index 21dd3cae..6413f842 100644
--- a/devices/wim/config/ags/js/misc/cursorbox.js
+++ b/devices/wim/config/ags/js/misc/cursorbox.js
@@ -42,7 +42,7 @@ export default ({
 
     const gesture = Gtk.GestureLongPress.new(widget);
 
-    widget.connectTo(gesture, () => {
+    widget.hook(gesture, () => {
         const pointer = gesture.get_point(null);
         const x = pointer[1];
         const y = pointer[2];
diff --git a/devices/wim/config/ags/js/misc/popup.js b/devices/wim/config/ags/js/misc/popup.js
index 3734594a..85f31669 100644
--- a/devices/wim/config/ags/js/misc/popup.js
+++ b/devices/wim/config/ags/js/misc/popup.js
@@ -58,20 +58,22 @@ export default ({
                 transition,
                 transitionDuration,
 
-                connections: [[App, (rev, currentName, isOpen) => {
-                    if (currentName === name) {
-                        rev.revealChild = isOpen;
+                setup: (self) => {
+                    self.hook(App, (_, currentName, isOpen) => {
+                        if (currentName === name) {
+                            self.revealChild = isOpen;
 
-                        if (isOpen) {
-                            onOpen(window);
+                            if (isOpen) {
+                                onOpen(window);
+                            }
+                            else {
+                                timeout(transitionDuration, () => {
+                                    onClose(window);
+                                });
+                            }
                         }
-                        else {
-                            timeout(transitionDuration, () => {
-                                onClose(window);
-                            });
-                        }
-                    }
-                }]],
+                    });
+                },
 
                 child: child || Box(),
             }),
diff --git a/devices/wim/config/ags/js/notifications/center.js b/devices/wim/config/ags/js/notifications/center.js
index d05585c2..e9288f7f 100644
--- a/devices/wim/config/ags/js/notifications/center.js
+++ b/devices/wim/config/ags/js/notifications/center.js
@@ -28,28 +28,30 @@ const NotificationList = () => Box({
     vexpand: true,
     vpack: 'start',
     binds: [['visible', HasNotifs]],
-    connections: [
-        [Notifications, (box, id) => {
-            // Handle cached notifs
-            if (box.children.length === 0) {
-                Notifications.notifications.forEach((n) => {
-                    addNotif(box, n);
-                });
-            }
 
-            else if (id) {
-                addNotif(box, Notifications.getNotification(id));
-            }
-        }, 'notified'],
+    setup: (self) => {
+        self
+            .hook(Notifications, (box, id) => {
+                // Handle cached notifs
+                if (box.children.length === 0) {
+                    Notifications.notifications.forEach((n) => {
+                        addNotif(box, n);
+                    });
+                }
 
-        [Notifications, (box, id) => {
-            const notif = box.children.find((ch) => ch._id === id);
+                else if (id) {
+                    addNotif(box, Notifications.getNotification(id));
+                }
+            }, 'notified')
 
-            if (notif?.sensitive) {
-                notif.slideAway('Right');
-            }
-        }, 'closed'],
-    ],
+            .hook(Notifications, (box, id) => {
+                const notif = box.children.find((ch) => ch._id === id);
+
+                if (notif?.sensitive) {
+                    notif.slideAway('Right');
+                }
+            }, 'closed');
+    },
 });
 
 // Needs to be wrapped to still have onHover when disabled
@@ -64,11 +66,13 @@ const ClearButton = () => EventBox({
             children: [
                 Label('Clear '),
                 Icon({
-                    connections: [[Notifications, (self) => {
-                        self.icon = Notifications.notifications.length > 0 ?
-                            'user-trash-full-symbolic' :
-                            'user-trash-symbolic';
-                    }]],
+                    setup: (self) => {
+                        self.hook(Notifications, () => {
+                            self.icon = Notifications.notifications.length > 0 ?
+                                'user-trash-full-symbolic' :
+                                'user-trash-symbolic';
+                        });
+                    },
                 }),
             ],
         }),
diff --git a/devices/wim/config/ags/js/notifications/gesture.js b/devices/wim/config/ags/js/notifications/gesture.js
index 6daaf211..94c72098 100644
--- a/devices/wim/config/ags/js/notifications/gesture.js
+++ b/devices/wim/config/ags/js/notifications/gesture.js
@@ -91,80 +91,80 @@ export default ({
 
     widget.add(Box({
         css: squeezeLeft,
-        connections: [
+        setup: (self) => {
+            self
+                // When dragging
+                .hook(gesture, () => {
+                    let offset = gesture.get_offset()[1];
 
-            // When dragging
-            [gesture, (self) => {
-                let offset = gesture.get_offset()[1];
+                    if (offset === 0) {
+                        return;
+                    }
 
-                if (offset === 0) {
-                    return;
-                }
-
-                // Slide right
-                if (offset > 0) {
-                    self.setCss(`
+                    // Slide right
+                    if (offset > 0) {
+                        self.setCss(`
                         margin-top: 0px; margin-bottom: 0px;
                         opacity: 1; transition: none;
                         margin-left:   ${offset}px;
                         margin-right: -${offset}px;
                     `);
-                }
+                    }
 
-                // Slide left
-                else {
-                    offset = Math.abs(offset);
-                    self.setCss(`
+                    // Slide left
+                    else {
+                        offset = Math.abs(offset);
+                        self.setCss(`
                         margin-top: 0px; margin-bottom: 0px;
                         opacity: 1; transition: none;
                         margin-right: ${offset}px;
                         margin-left: -${offset}px;
                     `);
-                }
+                    }
 
-                // Put a threshold on if a click is actually dragging
-                widget._dragging = Math.abs(offset) > SLIDE_MIN_THRESHOLD;
-                widget.cursor = 'grabbing';
-            }, 'drag-update'],
+                    // Put a threshold on if a click is actually dragging
+                    widget._dragging = Math.abs(offset) > SLIDE_MIN_THRESHOLD;
+                    widget.cursor = 'grabbing';
+                }, 'drag-update')
 
+                // On drag end
+                .hook(gesture, () => {
+                    // Make it slide in on init
+                    if (!widget.ready) {
+                        // Reverse of slideAway, so it started at squeeze, then we go to slide
+                        self.setCss(slideIn === 'Left' ?
+                            slideLeft :
+                            slideRight);
 
-            // On drag end
-            [gesture, (self) => {
-                // Make it slide in on init
-                if (!widget.ready) {
-                    // Reverse of slideAway, so it started at squeeze, then we go to slide
-                    self.setCss(slideIn === 'Left' ? slideLeft : slideRight);
-
-                    timeout(ANIM_DURATION, () => {
-                        // Then we go to center
-                        self.setCss(defaultStyle);
                         timeout(ANIM_DURATION, () => {
-                            widget.ready = true;
+                            // Then we go to center
+                            self.setCss(defaultStyle);
+                            timeout(ANIM_DURATION, () => {
+                                widget.ready = true;
+                            });
                         });
-                    });
 
-                    return;
-                }
+                        return;
+                    }
 
-                const offset = gesture.get_offset()[1];
+                    const offset = gesture.get_offset()[1];
 
-                // If crosses threshold after letting go, slide away
-                if (Math.abs(offset) > MAX_OFFSET) {
-                    if (offset > 0) {
-                        widget.slideAway('Right');
+                    // If crosses threshold after letting go, slide away
+                    if (Math.abs(offset) > MAX_OFFSET) {
+                        if (offset > 0) {
+                            widget.slideAway('Right');
+                        }
+                        else {
+                            widget.slideAway('Left');
+                        }
                     }
                     else {
-                        widget.slideAway('Left');
+                        self.setCss(defaultStyle);
+                        widget.cursor = 'grab';
+                        widget._dragging = false;
                     }
-                }
-                else {
-                    self.setCss(defaultStyle);
-                    widget.cursor = 'grab';
-                    widget._dragging = false;
-                }
-            }, 'drag-end'],
-
-        ],
+                }, 'drag-end');
+        },
     }));
 
     return widget;
diff --git a/devices/wim/config/ags/js/notifications/popup.js b/devices/wim/config/ags/js/notifications/popup.js
index 2baaebe2..00e8af70 100644
--- a/devices/wim/config/ags/js/notifications/popup.js
+++ b/devices/wim/config/ags/js/notifications/popup.js
@@ -10,56 +10,57 @@ import { Notification } from './base.js';
 const DELAY = 2000;
 
 
-const addPopup = (box, id) => {
-    if (!id) {
-        return;
-    }
-
-    const notif = Notifications.getNotification(id);
-
-    const NewNotif = Notification({
-        notif,
-        command: () => notif.dismiss(),
-    });
-
-    if (NewNotif) {
-        // Use this instead of add to put it at the top
-        box.pack_end(NewNotif, false, false, 0);
-        box.show_all();
-    }
-};
-
-const handleDismiss = (box, id, force = false) => {
-    const notif = box.children.find((ch) => ch._id === id);
-
-    if (!notif) {
-        return;
-    }
-
-    // If notif isn't hovered or was closed, slide away
-    if (!notif._hovered || force) {
-        notif.slideAway('Left');
-    }
-
-    // If notif is hovered, delay close
-    else if (notif._hovered) {
-        notif.interval = interval(DELAY, () => {
-            if (!notif._hovered && notif.interval) {
-                notif.slideAway('Left');
-
-                GLib.source_remove(notif.interval);
-                notif.interval = null;
-            }
-        });
-    }
-};
-
 export default () => Box({
     vertical: true,
 
-    connections: [
-        [Notifications, (s, id) => addPopup(s, id), 'notified'],
-        [Notifications, (s, id) => handleDismiss(s, id), 'dismissed'],
-        [Notifications, (s, id) => handleDismiss(s, id, true), 'closed'],
-    ],
+    setup: (self) => {
+        const addPopup = (id) => {
+            if (!id) {
+                return;
+            }
+
+            const notif = Notifications.getNotification(id);
+
+            const NewNotif = Notification({
+                notif,
+                command: () => notif.dismiss(),
+            });
+
+            if (NewNotif) {
+                // Use this instead of add to put it at the top
+                self.pack_end(NewNotif, false, false, 0);
+                self.show_all();
+            }
+        };
+
+        const handleDismiss = (id, force = false) => {
+            const notif = self.children.find((ch) => ch._id === id);
+
+            if (!notif) {
+                return;
+            }
+
+            // If notif isn't hovered or was closed, slide away
+            if (!notif._hovered || force) {
+                notif.slideAway('Left');
+            }
+
+            // If notif is hovered, delay close
+            else if (notif._hovered) {
+                notif.interval = interval(DELAY, () => {
+                    if (!notif._hovered && notif.interval) {
+                        notif.slideAway('Left');
+
+                        GLib.source_remove(notif.interval);
+                        notif.interval = null;
+                    }
+                });
+            }
+        };
+
+        self
+            .hook(Notifications, (_, id) => addPopup(id), 'notified')
+            .hook(Notifications, (_, id) => handleDismiss(id), 'dismissed')
+            .hook(Notifications, (_, id) => handleDismiss(id, true), 'closed');
+    },
 });
diff --git a/devices/wim/config/ags/js/on-screen-keyboard/keyboard.js b/devices/wim/config/ags/js/on-screen-keyboard/keyboard.js
index ddbdbb1f..21591b7e 100644
--- a/devices/wim/config/ags/js/on-screen-keyboard/keyboard.js
+++ b/devices/wim/config/ags/js/on-screen-keyboard/keyboard.js
@@ -37,15 +37,17 @@ export default (window) => Box({
                             active: true,
                             vpack: 'center',
 
-                            connections: [['toggled', (self) => {
-                                self.toggleClassName(
-                                    'toggled',
-                                    self.get_active(),
-                                );
-                                window.exclusivity = self.get_active() ?
-                                    'exclusive' :
-                                    'normal';
-                            }]],
+                            setup: (self) => {
+                                self.on('toggled', () => {
+                                    self.toggleClassName(
+                                        'toggled',
+                                        self.get_active(),
+                                    );
+                                    window.exclusivity = self.get_active() ?
+                                        'exclusive' :
+                                        'normal';
+                                });
+                            },
 
                             child: Label('Exclusive'),
                         }),
diff --git a/devices/wim/config/ags/js/on-screen-keyboard/keys.js b/devices/wim/config/ags/js/on-screen-keyboard/keys.js
index fd96d535..1cf7d87e 100644
--- a/devices/wim/config/ags/js/on-screen-keyboard/keys.js
+++ b/devices/wim/config/ags/js/on-screen-keyboard/keys.js
@@ -86,11 +86,13 @@ const ModKey = (key) => {
             self.child.toggleClassName('active', !Mod.value);
             Mod.value = !Mod.value;
         },
-        connections: [[NormalClick, (self) => {
-            Mod.value = false;
-            self.child.toggleClassName('active', false);
-            execAsync(`ydotool key ${key.keycode}:0`);
-        }]],
+        setup: (self) => {
+            self.hook(NormalClick, () => {
+                Mod.value = false;
+                self.child.toggleClassName('active', false);
+                execAsync(`ydotool key ${key.keycode}:0`);
+            });
+        },
         child: Label({
             class_name: `mod ${key.label}`,
             label: key.label,
@@ -114,40 +116,41 @@ const RegularKey = (key) => {
             class_name: `normal ${key.label}`,
             label: key.label,
 
-            connections: [
-                [Shift, (self) => {
-                    if (!key.labelShift) {
-                        return;
-                    }
+            setup: (self) => {
+                self
+                    .hook(Shift, () => {
+                        if (!key.labelShift) {
+                            return;
+                        }
 
-                    self.label = Shift.value ? key.labelShift : key.label;
-                }],
+                        self.label = Shift.value ? key.labelShift : key.label;
+                    })
+                    .hook(Caps, () => {
+                        if (key.label === 'Caps') {
+                            self.toggleClassName('active', Caps.value);
 
-                [Caps, (self) => {
-                    if (key.label === 'Caps') {
-                        self.toggleClassName('active', Caps.value);
+                            return;
+                        }
 
-                        return;
-                    }
+                        if (!key.labelShift) {
+                            return;
+                        }
 
-                    if (!key.labelShift) {
-                        return;
-                    }
+                        if (key.label.match(/[A-Za-z]/)) {
+                            self.label = Caps.value ?
+                                key.labelShift :
+                                key.label;
+                        }
+                    })
+                    .hook(AltGr, () => {
+                        if (!key.labelAltGr) {
+                            return;
+                        }
 
-                    if (key.label.match(/[A-Za-z]/)) {
-                        self.label = Caps.value ? key.labelShift : key.label;
-                    }
-                }],
-
-                [AltGr, (self) => {
-                    if (!key.labelAltGr) {
-                        return;
-                    }
-
-                    self.toggleClassName('altgr', AltGr.value);
-                    self.label = AltGr.value ? key.labelAltGr : key.label;
-                }],
-            ],
+                        self.toggleClassName('altgr', AltGr.value);
+                        self.label = AltGr.value ? key.labelAltGr : key.label;
+                    });
+            },
         }),
     });
 
@@ -156,7 +159,7 @@ const RegularKey = (key) => {
     gesture.delay_factor = 1.0;
 
     // Long press
-    widget.connectTo(gesture, () => {
+    widget.hook(gesture, () => {
         const pointer = gesture.get_point(null);
         const x = pointer[1];
         const y = pointer[2];
@@ -171,7 +174,7 @@ const RegularKey = (key) => {
     }, 'pressed');
 
     // OnPrimaryClickRelease
-    widget.connectTo(gesture, () => {
+    widget.hook(gesture, () => {
         const pointer = gesture.get_point(null);
         const x = pointer[1];
         const y = pointer[2];
diff --git a/devices/wim/config/ags/js/on-screen-keyboard/main.js b/devices/wim/config/ags/js/on-screen-keyboard/main.js
index 2d1950e5..d095da97 100644
--- a/devices/wim/config/ags/js/on-screen-keyboard/main.js
+++ b/devices/wim/config/ags/js/on-screen-keyboard/main.js
@@ -16,17 +16,18 @@ export default () => {
         visible: false,
         anchor: ['left', 'bottom', 'right'],
 
-        connections: [
-            [Tablet, (self, state) => {
-                self.setVisible(state);
-            }, 'osk-toggled'],
+        setup: (self) => {
+            self
+                .hook(Tablet, (_, state) => {
+                    self.setVisible(state);
+                }, 'osk-toggled')
 
-            [Tablet, () => {
-                if (!Tablet.tabletMode && !Tablet.oskState) {
-                    window.visible = false;
-                }
-            }, 'mode-toggled'],
-        ],
+                .hook(Tablet, () => {
+                    if (!Tablet.tabletMode && !Tablet.oskState) {
+                        window.visible = false;
+                    }
+                }, 'mode-toggled');
+        },
     });
 
     window.child = Keyboard(window);
diff --git a/devices/wim/config/ags/js/osd/ctor.js b/devices/wim/config/ags/js/osd/ctor.js
index 44bfc43c..1fe1c463 100644
--- a/devices/wim/config/ags/js/osd/ctor.js
+++ b/devices/wim/config/ags/js/osd/ctor.js
@@ -36,7 +36,7 @@ export default ({ stack, icon, info } = {}) => {
         connectFunc = () => stack.popup(osd);
     }
 
-    osd.children[0].children[1].connectTo(info.mod, connectFunc, info.signal);
+    osd.children[0].children[1].hook(info.mod, connectFunc, info.signal);
 
     return osd;
 };
diff --git a/devices/wim/config/ags/js/overview/dragndrop.js b/devices/wim/config/ags/js/overview/dragndrop.js
index 5537d4a6..3240a613 100644
--- a/devices/wim/config/ags/js/overview/dragndrop.js
+++ b/devices/wim/config/ags/js/overview/dragndrop.js
@@ -33,28 +33,28 @@ let hidden = 0;
 
 export const WorkspaceDrop = (props) => EventBox({
     ...props,
-    connections: [['drag-data-received', (self, _c, _x, _y, data) => {
-        let id = self.get_parent()._id;
-
-        if (id < -1) {
-            id = self.get_parent()._name;
-        }
-
-        else if (id === -1) {
-            id = `special:${++hidden}`;
-        }
-
-        else if (id === 1000) {
-            id = 'empty';
-        }
-
-        Hyprland.sendMessage('dispatch ' +
-            `movetoworkspacesilent ${id},address:${data.get_text()}`)
-            .catch(print);
-    }]],
-
     setup: (self) => {
         self.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
+
+        self.on('drag-data-received', (_, _c, _x, _y, data) => {
+            let id = self.get_parent()._id;
+
+            if (id < -1) {
+                id = self.get_parent()._name;
+            }
+
+            else if (id === -1) {
+                id = `special:${++hidden}`;
+            }
+
+            else if (id === 1000) {
+                id = 'empty';
+            }
+
+            Hyprland.sendMessage('dispatch ' +
+                `movetoworkspacesilent ${id},address:${data.get_text()}`)
+                .catch(print);
+        });
     },
 });
 
@@ -69,16 +69,16 @@ export const WindowButton = ({ address, mainBox, ...props } = {}) => Button({
             Gdk.DragAction.COPY,
         );
 
-        self.connect('drag-data-get', (_w, _c, data) => {
+        self.on('drag-data-get', (_w, _c, data) => {
             data.set_text(address, address.length);
         });
 
-        self.connect('drag-begin', (_, context) => {
+        self.on('drag-begin', (_, context) => {
             Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(self));
             self.get_parent().revealChild = false;
         });
 
-        self.connect('drag-end', () => {
+        self.on('drag-end', () => {
             self.get_parent().destroy();
 
             updateClients(mainBox);
diff --git a/devices/wim/config/ags/js/overview/main.js b/devices/wim/config/ags/js/overview/main.js
index 531e56ab..6603cc35 100644
--- a/devices/wim/config/ags/js/overview/main.js
+++ b/devices/wim/config/ags/js/overview/main.js
@@ -40,13 +40,15 @@ export const Overview = () => {
             }),
         ],
 
-        connections: [[Hyprland, (self) => {
-            if (!App.getWindow('overview').visible) {
-                return;
-            }
+        setup: (self) => {
+            self.hook(Hyprland, () => {
+                if (!App.getWindow('overview').visible) {
+                    return;
+                }
 
-            self.update();
-        }]],
+                self.update();
+            });
+        },
 
         properties: [
             ['workspaces'],
@@ -72,15 +74,17 @@ export const Overview = () => {
         }),
 
         // TODO: throttle his?
-        connections: [['get-child-position', (self, ch) => {
-            if (ch === mainBox) {
-                self.child.setCss(`
-                    transition: min-height 0.2s ease, min-width 0.2s ease;
-                    min-height: ${mainBox.get_allocated_height()}px;
-                    min-width: ${mainBox.get_allocated_width()}px;
-                `);
-            }
-        }]],
+        setup: (self) => {
+            self.on('get-child-position', (_, ch) => {
+                if (ch === mainBox) {
+                    self.child.setCss(`
+                        transition: min-height 0.2s ease, min-width 0.2s ease;
+                        min-height: ${mainBox.get_allocated_height()}px;
+                        min-width: ${mainBox.get_allocated_width()}px;
+                    `);
+                }
+            });
+        },
     });
 
     widget.getChild = () => mainBox;
diff --git a/devices/wim/config/ags/js/overview/workspaces.js b/devices/wim/config/ags/js/overview/workspaces.js
index 255a34c5..e6e93497 100644
--- a/devices/wim/config/ags/js/overview/workspaces.js
+++ b/devices/wim/config/ags/js/overview/workspaces.js
@@ -31,17 +31,17 @@ const Workspace = (id, name, normal = true) => {
         transition: 'slide_right',
         transitionDuration: 500,
 
-        connections: normal ?
+        setup: (self) => {
+            if (normal) {
+                self.hook(Hyprland, () => {
+                    const activeId = Hyprland.active.workspace.id;
+                    const active = activeId === self._id;
 
-            [[Hyprland, (box) => {
-                const activeId = Hyprland.active.workspace.id;
-                const active = activeId === box._id;
-
-                box.revealChild = Hyprland.getWorkspace(box._id)
-                    ?.windows > 0 || active;
-            }]] :
-
-            [],
+                    self.revealChild = Hyprland.getWorkspace(self._id)
+                        ?.windows > 0 || active;
+                });
+            }
+        },
 
         child: WorkspaceDrop({
             child: Box({
@@ -88,38 +88,42 @@ export const WorkspaceRow = (className, i) => {
         transition: 'slide_down',
         hpack: className === 'special' ? '' : 'start',
 
-        connections: [[Hyprland, (rev) => {
-            const minId = i * VARS.WORKSPACE_PER_ROW;
-            const activeId = Hyprland.active.workspace.id;
+        setup: (self) => {
+            self.hook(Hyprland, (rev) => {
+                const minId = i * VARS.WORKSPACE_PER_ROW;
+                const activeId = Hyprland.active.workspace.id;
 
-            const rowExists = Hyprland.workspaces.some((ws) => {
-                const isInRow = ws.id > minId;
-                const hasClients = ws.windows > 0;
-                const isActive = ws.id === activeId;
+                const rowExists = Hyprland.workspaces.some((ws) => {
+                    const isInRow = ws.id > minId;
+                    const hasClients = ws.windows > 0;
+                    const isActive = ws.id === activeId;
 
-                return isInRow && (hasClients || isActive);
+                    return isInRow && (hasClients || isActive);
+                });
+
+                rev.revealChild = rowExists;
             });
-
-            rev.revealChild = rowExists;
-        }]],
+        },
 
         child: CenterBox({
             children: [null, EventBox({
-                connections: [[Hyprland, () => {
-                    const maxId = (i + 1) * VARS.WORKSPACE_PER_ROW;
-                    const activeId = Hyprland.active.workspace.id;
+                setup: (self) => {
+                    self.hook(Hyprland, () => {
+                        const maxId = (i + 1) * VARS.WORKSPACE_PER_ROW;
+                        const activeId = Hyprland.active.workspace.id;
 
-                    const isSpecial = className === 'special';
-                    const nextRowExists = Hyprland.workspaces.some((ws) => {
-                        const isInNextRow = ws.id > maxId;
-                        const hasClients = ws.windows > 0;
-                        const isActive = ws.id === activeId;
+                        const isSpecial = className === 'special';
+                        const nextRowExists = Hyprland.workspaces.some((ws) => {
+                            const isInNextRow = ws.id > maxId;
+                            const hasClients = ws.windows > 0;
+                            const isActive = ws.id === activeId;
 
-                        return isInNextRow && (hasClients || isActive);
+                            return isInNextRow && (hasClients || isActive);
+                        });
+
+                        addWorkspace.revealChild = isSpecial || !nextRowExists;
                     });
-
-                    addWorkspace.revealChild = isSpecial || !nextRowExists;
-                }]],
+                },
 
                 child: Box({
                     className,
diff --git a/devices/wim/config/ags/js/quick-settings/bluetooth.js b/devices/wim/config/ags/js/quick-settings/bluetooth.js
index 1135532b..dbff22af 100644
--- a/devices/wim/config/ags/js/quick-settings/bluetooth.js
+++ b/devices/wim/config/ags/js/quick-settings/bluetooth.js
@@ -5,8 +5,8 @@ import { Box, Icon, Label, ListBox, Overlay, Revealer, Scrollable } from 'resour
 
 import EventBox from '../misc/cursorbox.js';
 
-const SCROLL_THRESHOLD_H = 200;
-const SCROLL_THRESHOLD_N = 7;
+const SCROLL_THRESH_H = 200;
+const SCROLL_THRESH_N = 7;
 
 
 const BluetoothDevice = (dev) => {
@@ -29,9 +29,11 @@ const BluetoothDevice = (dev) => {
                 icon: 'object-select-symbolic',
                 hexpand: true,
                 hpack: 'end',
-                connections: [[dev, (self) => {
-                    self.setCss(`opacity: ${dev.paired ? '1' : '0'};`);
-                }]],
+                setup: (self) => {
+                    self.hook(dev, () => {
+                        self.setCss(`opacity: ${dev.paired ? '1' : '0'};`);
+                    });
+                },
             }),
         ],
     });
@@ -95,17 +97,19 @@ export const BluetoothMenu = () => {
                 hscroll: 'never',
                 vscroll: 'never',
 
-                connections: [['edge-reached', (_, pos) => {
-                    // Manage scroll indicators
-                    if (pos === 2) {
-                        topArrow.revealChild = false;
-                        bottomArrow.revealChild = true;
-                    }
-                    else if (pos === 3) {
-                        topArrow.revealChild = true;
-                        bottomArrow.revealChild = false;
-                    }
-                }]],
+                setup: (self) => {
+                    self.on('edge-reached', (_, pos) => {
+                        // Manage scroll indicators
+                        if (pos === 2) {
+                            topArrow.revealChild = false;
+                            bottomArrow.revealChild = true;
+                        }
+                        else if (pos === 3) {
+                            topArrow.revealChild = true;
+                            bottomArrow.revealChild = false;
+                        }
+                    });
+                },
 
                 child: ListBox({
                     setup: (self) => {
@@ -113,78 +117,79 @@ export const BluetoothMenu = () => {
                             return b.get_children()[0].dev.paired -
                                 a.get_children()[0].dev.paired;
                         });
-                    },
 
-                    connections: [[Bluetooth, (box) => {
-                        // Get all devices
-                        const Devices = [].concat(
-                            Bluetooth.devices,
-                            Bluetooth.connectedDevices,
-                        );
+                        self.hook(Bluetooth, () => {
+                            // Get all devices
+                            const Devices = [].concat(
+                                Bluetooth.devices,
+                                Bluetooth.connectedDevices,
+                            );
 
-                        // Add missing devices
-                        Devices.forEach((dev) => {
-                            if (!DevList.has(dev) && dev.name) {
-                                DevList.set(dev, BluetoothDevice(dev));
+                            // Add missing devices
+                            Devices.forEach((dev) => {
+                                if (!DevList.has(dev) && dev.name) {
+                                    DevList.set(dev, BluetoothDevice(dev));
 
-                                box.add(DevList.get(dev));
-                                box.show_all();
-                            }
-                        });
+                                    self.add(DevList.get(dev));
+                                    self.show_all();
+                                }
+                            });
 
-                        // Delete ones that don't exist anymore
-                        const difference = Array.from(DevList.keys())
-                            .filter((dev) => !Devices
-                                .find((d) => dev === d) &&
+                            // Delete ones that don't exist anymore
+                            const difference = Array.from(DevList.keys())
+                                .filter((dev) => !Devices
+                                    .find((d) => dev === d) &&
                                     dev.name);
 
-                        difference.forEach((dev) => {
-                            const devWidget = DevList.get(dev);
+                            difference.forEach((dev) => {
+                                const devWidget = DevList.get(dev);
 
-                            if (devWidget) {
-                                if (devWidget.toDestroy) {
-                                    devWidget.get_parent().destroy();
-                                    DevList.delete(dev);
+                                if (devWidget) {
+                                    if (devWidget.toDestroy) {
+                                        devWidget.get_parent().destroy();
+                                        DevList.delete(dev);
+                                    }
+                                    else {
+                                        devWidget.children[0]
+                                            .revealChild = false;
+                                        devWidget.toDestroy = true;
+                                    }
                                 }
-                                else {
-                                    devWidget.children[0].revealChild = false;
-                                    devWidget.toDestroy = true;
+                            });
+
+                            // Start scrolling after a specified height
+                            // is reached by the children
+                            const height = Math.max(
+                                self.get_parent().get_allocated_height(),
+                                SCROLL_THRESH_H,
+                            );
+
+                            const scroll = self.get_parent().get_parent();
+
+                            if (self.get_children().length > SCROLL_THRESH_N) {
+                                scroll.vscroll = 'always';
+                                scroll.setCss(`min-height: ${height}px;`);
+
+                                // Make bottom scroll indicator appear only
+                                // when first getting overflowing children
+                                if (!(bottomArrow.revealChild === true ||
+                                    topArrow.revealChild === true)) {
+                                    bottomArrow.revealChild = true;
                                 }
                             }
-                        });
-
-                        // Start scrolling after a specified height
-                        // is reached by the children
-                        const height = Math.max(
-                            box.get_parent().get_allocated_height(),
-                            SCROLL_THRESHOLD_H,
-                        );
-
-                        const scroll = box.get_parent().get_parent();
-
-                        if (box.get_children().length > SCROLL_THRESHOLD_N) {
-                            scroll.vscroll = 'always';
-                            scroll.setCss(`min-height: ${height}px;`);
-
-                            // Make bottom scroll indicator appear only
-                            // when first getting overflowing children
-                            if (!(bottomArrow.revealChild === true ||
-                                topArrow.revealChild === true)) {
-                                bottomArrow.revealChild = true;
+                            else {
+                                scroll.vscroll = 'never';
+                                scroll.setCss('');
+                                topArrow.revealChild = false;
+                                bottomArrow.revealChild = false;
                             }
-                        }
-                        else {
-                            scroll.vscroll = 'never';
-                            scroll.setCss('');
-                            topArrow.revealChild = false;
-                            bottomArrow.revealChild = false;
-                        }
 
-                        // Trigger sort_func
-                        box.get_children().forEach((ch) => {
-                            ch.changed();
+                            // Trigger sort_func
+                            self.get_children().forEach((ch) => {
+                                ch.changed();
+                            });
                         });
-                    }]],
+                    },
                 }),
             }),
         }),
diff --git a/devices/wim/config/ags/js/quick-settings/button-grid.js b/devices/wim/config/ags/js/quick-settings/button-grid.js
index 0f884dce..9d60194f 100644
--- a/devices/wim/config/ags/js/quick-settings/button-grid.js
+++ b/devices/wim/config/ags/js/quick-settings/button-grid.js
@@ -34,24 +34,27 @@ const GridButton = ({
         icon = Icon({
             className: 'grid-label',
             icon,
-            connections: [[Activated, (self) => {
-                self.setCss(`color: ${Activated.value ?
-                    'rgba(189, 147, 249, 0.8)' :
-                    'unset'};`);
-            }]],
+            setup: (self) => {
+                self.hook(Activated, () => {
+                    self.setCss(`color: ${Activated.value ?
+                        'rgba(189, 147, 249, 0.8)' :
+                        'unset'};`);
+                });
+            },
         });
     }
     else {
         icon = Icon({
             className: 'grid-label',
-            connections: [
-                icon,
-                [Activated, (self) => {
-                    self.setCss(`color: ${Activated.value ?
-                        'rgba(189, 147, 249, 0.8)' :
-                        'unset'};`);
-                }],
-            ],
+            setup: (self) => {
+                self
+                    .hook(...icon)
+                    .hook(Activated, () => {
+                        self.setCss(`color: ${Activated.value ?
+                            'rgba(189, 147, 249, 0.8)' :
+                            'unset'};`);
+                    });
+            },
         });
     }
 
@@ -61,7 +64,9 @@ const GridButton = ({
             justification: 'left',
             truncate: 'end',
             maxWidthChars: 12,
-            connections: [indicator],
+            setup: (self) => {
+                self.hook(...indicator);
+            },
         });
     }
 
@@ -126,17 +131,19 @@ const GridButton = ({
                             icon: `${App.configDir }/icons/down-large.svg`,
                             className: 'grid-chev',
 
-                            connections: [[Activated, (self) => {
-                                let deg = 270;
+                            setup: (self) => {
+                                self.hook(Activated, () => {
+                                    let deg = 270;
 
-                                if (Activated.value) {
-                                    deg = menu ? 360 : 450;
-                                    onOpen(menu);
-                                }
-                                self.setCss(`
-                                    -gtk-icon-transform: rotate(${deg}deg);
-                                `);
-                            }]],
+                                    if (Activated.value) {
+                                        deg = menu ? 360 : 450;
+                                        onOpen(menu);
+                                    }
+                                    self.setCss(`
+                                        -gtk-icon-transform: rotate(${deg}deg);
+                                    `);
+                                });
+                            },
                         }),
                     }),
 
diff --git a/devices/wim/config/ags/js/quick-settings/network.js b/devices/wim/config/ags/js/quick-settings/network.js
index b6e5fc44..5d5182e7 100644
--- a/devices/wim/config/ags/js/quick-settings/network.js
+++ b/devices/wim/config/ags/js/quick-settings/network.js
@@ -7,8 +7,8 @@ import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
 
 import EventBox from '../misc/cursorbox.js';
 
-const SCROLL_THRESHOLD_H = 200;
-const SCROLL_THRESHOLD_N = 7;
+const SCROLL_THRESH_H = 200;
+const SCROLL_THRESH_N = 7;
 
 
 const AccessPoint = (ap) => {
@@ -22,28 +22,37 @@ const AccessPoint = (ap) => {
         hexpand: true,
         children: [
             Icon({
-                connections: [[widget.ap, (self) => {
-                    self.icon = widget.ap.value.iconName;
-                }]],
+                setup: (self) => {
+                    self.hook(widget.ap, () => {
+                        self.icon = widget.ap.value.iconName;
+                    });
+                },
             }),
 
             Label({
-                connections: [[widget.ap, (self) => {
-                    self.label = widget.ap.value.ssid || '';
-                }]],
+                setup: (self) => {
+                    self.hook(widget.ap, () => {
+                        self.label = widget.ap.value.ssid || '';
+                    });
+                },
             }),
 
             Icon({
                 icon: 'object-select-symbolic',
                 hexpand: true,
                 hpack: 'end',
-                connections: [[Network, (self) => {
-                    self.setCss(`opacity: ${
-                        widget.ap.value.ssid === Network.wifi.ssid ?
-                            '1' :
-                            '0'
-                    };`);
-                }]],
+                setup: (self) => {
+                    self.hook(Network, () => {
+                        self.setCss(
+                            `opacity: ${
+                                widget.ap.value.ssid === Network.wifi.ssid ?
+                                    '1' :
+                                    '0'
+                            };
+                        `,
+                        );
+                    });
+                },
             }),
         ],
     });
@@ -109,17 +118,19 @@ export const NetworkMenu = () => {
                 hscroll: 'never',
                 vscroll: 'never',
 
-                connections: [['edge-reached', (_, pos) => {
-                    // Manage scroll indicators
-                    if (pos === 2) {
-                        topArrow.revealChild = false;
-                        bottomArrow.revealChild = true;
-                    }
-                    else if (pos === 3) {
-                        topArrow.revealChild = true;
-                        bottomArrow.revealChild = false;
-                    }
-                }]],
+                setup: (self) => {
+                    self.on('edge-reached', (_, pos) => {
+                        // Manage scroll indicators
+                        if (pos === 2) {
+                            topArrow.revealChild = false;
+                            bottomArrow.revealChild = true;
+                        }
+                        else if (pos === 3) {
+                            topArrow.revealChild = true;
+                            bottomArrow.revealChild = false;
+                        }
+                    });
+                },
 
                 child: ListBox({
                     setup: (self) => {
@@ -127,82 +138,83 @@ export const NetworkMenu = () => {
                             return b.get_children()[0].ap.value.strength -
                                 a.get_children()[0].ap.value.strength;
                         });
-                    },
 
-                    connections: [[Network, (box) => {
-                        // Add missing APs
-                        Network.wifi?.access_points.forEach((ap) => {
-                            if (ap.ssid !== 'Unknown') {
-                                if (APList.has(ap.ssid)) {
-                                    const accesPoint = APList.get(ap.ssid)
-                                        .ap.value;
+                        self.hook(Network, () => {
+                            // Add missing APs
+                            Network.wifi?.access_points.forEach((ap) => {
+                                if (ap.ssid !== 'Unknown') {
+                                    if (APList.has(ap.ssid)) {
+                                        const accesPoint = APList.get(ap.ssid)
+                                            .ap.value;
 
-                                    if (accesPoint.strength < ap.strength) {
-                                        APList.get(ap.ssid).ap.value = ap;
+                                        if (accesPoint.strength < ap.strength) {
+                                            APList.get(ap.ssid).ap.value = ap;
+                                        }
+                                    }
+                                    else {
+                                        APList.set(ap.ssid, AccessPoint(ap));
+
+                                        self.add(APList.get(ap.ssid));
+                                        self.show_all();
                                     }
                                 }
-                                else {
-                                    APList.set(ap.ssid, AccessPoint(ap));
+                            });
 
-                                    box.add(APList.get(ap.ssid));
-                                    box.show_all();
+                            // Delete ones that don't exist anymore
+                            const difference = Array.from(APList.keys())
+                                .filter((ssid) => !Network.wifi.access_points
+                                    .find((ap) => ap.ssid === ssid) &&
+                                    ssid !== 'Unknown');
+
+                            difference.forEach((ssid) => {
+                                const apWidget = APList.get(ssid);
+
+                                if (apWidget) {
+                                    if (apWidget.toDestroy) {
+                                        apWidget.get_parent().destroy();
+                                        APList.delete(ssid);
+                                    }
+                                    else {
+                                        apWidget.children[0]
+                                            .revealChild = false;
+                                        apWidget.toDestroy = true;
+                                    }
+                                }
+                            });
+
+                            // Start scrolling after a specified height
+                            // is reached by the children
+                            const height = Math.max(
+                                self.get_parent().get_allocated_height(),
+                                SCROLL_THRESH_H,
+                            );
+
+                            const scroll = self.get_parent().get_parent();
+
+                            if (self.get_children().length > SCROLL_THRESH_N) {
+                                scroll.vscroll = 'always';
+                                scroll.setCss(`min-height: ${height}px;`);
+
+                                // Make bottom scroll indicator appear only
+                                // when first getting overflowing children
+                                if (!(bottomArrow.revealChild === true ||
+                                    topArrow.revealChild === true)) {
+                                    bottomArrow.revealChild = true;
                                 }
                             }
-                        });
-
-                        // Delete ones that don't exist anymore
-                        const difference = Array.from(APList.keys())
-                            .filter((ssid) => !Network.wifi.access_points
-                                .find((ap) => ap.ssid === ssid) &&
-                                ssid !== 'Unknown');
-
-                        difference.forEach((ssid) => {
-                            const apWidget = APList.get(ssid);
-
-                            if (apWidget) {
-                                if (apWidget.toDestroy) {
-                                    apWidget.get_parent().destroy();
-                                    APList.delete(ssid);
-                                }
-                                else {
-                                    apWidget.children[0].revealChild = false;
-                                    apWidget.toDestroy = true;
-                                }
+                            else {
+                                scroll.vscroll = 'never';
+                                scroll.setCss('');
+                                topArrow.revealChild = false;
+                                bottomArrow.revealChild = false;
                             }
+
+                            // Trigger sort_func
+                            self.get_children().forEach((ch) => {
+                                ch.changed();
+                            });
                         });
-
-                        // Start scrolling after a specified height
-                        // is reached by the children
-                        const height = Math.max(
-                            box.get_parent().get_allocated_height(),
-                            SCROLL_THRESHOLD_H,
-                        );
-
-                        const scroll = box.get_parent().get_parent();
-
-                        if (box.get_children().length > SCROLL_THRESHOLD_N) {
-                            scroll.vscroll = 'always';
-                            scroll.setCss(`min-height: ${height}px;`);
-
-                            // Make bottom scroll indicator appear only
-                            // when first getting overflowing children
-                            if (!(bottomArrow.revealChild === true ||
-                                topArrow.revealChild === true)) {
-                                bottomArrow.revealChild = true;
-                            }
-                        }
-                        else {
-                            scroll.vscroll = 'never';
-                            scroll.setCss('');
-                            topArrow.revealChild = false;
-                            bottomArrow.revealChild = false;
-                        }
-
-                        // Trigger sort_func
-                        box.get_children().forEach((ch) => {
-                            ch.changed();
-                        });
-                    }]],
+                    },
                 }),
             }),
         }),
diff --git a/devices/wim/config/ags/js/quick-settings/slider-box.js b/devices/wim/config/ags/js/quick-settings/slider-box.js
index 596c089e..a1f55b93 100644
--- a/devices/wim/config/ags/js/quick-settings/slider-box.js
+++ b/devices/wim/config/ags/js/quick-settings/slider-box.js
@@ -34,18 +34,20 @@ export default () => Box({
                         Audio.speaker.volume = value;
                     },
 
-                    connections: [
-                        [Audio, (slider) => {
-                            slider.value = Audio.speaker?.volume;
-                        }, 'speaker-changed'],
+                    setup: (self) => {
+                        self
+                            .hook(Audio, () => {
+                                self.value = Audio.speaker?.volume;
+                            }, 'speaker-changed')
 
-                        ['button-press-event', (s) => {
-                            s.cursor = 'grabbing';
-                        }],
-                        ['button-release-event', (s) => {
-                            s.cursor = 'pointer';
-                        }],
-                    ],
+                            .on('button-press-event', () => {
+                                self.cursor = 'grabbing';
+                            })
+
+                            .on('button-release-event', () => {
+                                self.cursor = 'pointer';
+                            });
+                    },
                 }),
             ],
         }),
@@ -70,18 +72,20 @@ export default () => Box({
                         Brightness.screen = value;
                     },
 
-                    connections: [
-                        [Brightness, (slider) => {
-                            slider.value = Brightness.screen;
-                        }, 'screen'],
+                    setup: (self) => {
+                        self
+                            .hook(Brightness, () => {
+                                self.value = Brightness.screen;
+                            }, 'screen')
 
-                        ['button-press-event', (s) => {
-                            s.cursor = 'grabbing';
-                        }],
-                        ['button-release-event', (s) => {
-                            s.cursor = 'pointer';
-                        }],
-                    ],
+                            .on('button-press-event', () => {
+                                self.cursor = 'grabbing';
+                            })
+
+                            .on('button-release-event', () => {
+                                self.cursor = 'pointer';
+                            });
+                    },
                 }),
             ],
         }),
diff --git a/devices/wim/config/ags/js/quick-settings/toggle-button.js b/devices/wim/config/ags/js/quick-settings/toggle-button.js
index 7e913dc6..0d1611dd 100644
--- a/devices/wim/config/ags/js/quick-settings/toggle-button.js
+++ b/devices/wim/config/ags/js/quick-settings/toggle-button.js
@@ -14,20 +14,20 @@ export default (rev) => CenterBox({
                 self.set_active(Mpris.players.length > 0);
                 Mpris.disconnect(id);
             });
-        },
 
-        connections: [['toggled', (self) => {
-            if (self.get_active()) {
-                self.get_children()[0]
-                    .setCss('-gtk-icon-transform: rotate(0deg);');
-                rev.revealChild = true;
-            }
-            else {
-                self.get_children()[0]
-                    .setCss('-gtk-icon-transform: rotate(180deg);');
-                rev.revealChild = false;
-            }
-        }]],
+            self.on('toggled', () => {
+                if (self.get_active()) {
+                    self.get_children()[0]
+                        .setCss('-gtk-icon-transform: rotate(0deg);');
+                    rev.revealChild = true;
+                }
+                else {
+                    self.get_children()[0]
+                        .setCss('-gtk-icon-transform: rotate(180deg);');
+                    rev.revealChild = false;
+                }
+            });
+        },
 
         child: Icon({
             icon: `${App.configDir }/icons/down-large.svg`,
diff --git a/flake.lock b/flake.lock
index d2670cd1..caf4c159 100644
--- a/flake.lock
+++ b/flake.lock
@@ -7,11 +7,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1702637287,
-        "narHash": "sha256-HLXLYcHQfiOvsCF6/X9FY89URAOr/E62tOw8XWgMloU=",
+        "lastModified": 1702684413,
+        "narHash": "sha256-GdAkD8HJxsb3j9lOW7XFuRpG9S7OND/i1GjkNok4mj0=",
         "owner": "Aylur",
         "repo": "ags",
-        "rev": "6cfc5ace1077ffbf7f323e1f8c040e17dece6910",
+        "rev": "79d0f0555ba95c00c103aaa8ff3a14b49e2fe24f",
         "type": "github"
       },
       "original": {
@@ -314,11 +314,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1702538064,
-        "narHash": "sha256-At5GwJPu2tzvS9dllhBoZmqK6lkkh/sOp2YefWRlaL8=",
+        "lastModified": 1702735279,
+        "narHash": "sha256-SztEzDOE/6bDNnWWvnRbSHPVrgewLwdSei1sxoZFejM=",
         "owner": "nix-community",
         "repo": "home-manager",
-        "rev": "0e2e443ff24f9d75925e91b89d1da44b863734af",
+        "rev": "e9b9ecef4295a835ab073814f100498716b05a96",
         "type": "github"
       },
       "original": {
@@ -334,11 +334,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1702564334,
-        "narHash": "sha256-MwzG4YZhag4CFzM7JQJi+/QINCDZPJhue++TXr3Xxo4=",
+        "lastModified": 1702781283,
+        "narHash": "sha256-uR/vE/EGJwSEzJS40mEYATwybUODgMgH54l73xkjr0k=",
         "owner": "horriblename",
         "repo": "hyprgrass",
-        "rev": "f600c18e0c6672e1b053aa281c8175aef5eafebe",
+        "rev": "ebc604b2e0e1fba8949675bbd09426f8a5374ab0",
         "type": "github"
       },
       "original": {
@@ -358,11 +358,11 @@
         "xdph": "xdph"
       },
       "locked": {
-        "lastModified": 1702600743,
-        "narHash": "sha256-gxWffHkvtfarJ3ANJFRO3aohrIgwGVnDpG2WhKfLbf8=",
+        "lastModified": 1702759678,
+        "narHash": "sha256-+UGyDbOEvKewPkDD1tj1M3rcBTkX7UgE4zh8N1Hh2+o=",
         "owner": "hyprwm",
         "repo": "Hyprland",
-        "rev": "36fa33f7ca2f555cc8581a13c5899effed2bf84b",
+        "rev": "9ca0c7d814d6039450ff0341556faa1ce9b37e82",
         "type": "github"
       },
       "original": {
@@ -426,11 +426,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1702598962,
-        "narHash": "sha256-uh0Tw7og6xswrrQ9PxEFOCLQXcwQHUynO4bL1fkUJO8=",
+        "lastModified": 1702771628,
+        "narHash": "sha256-xU762QNKQZBWlbZ/b1S1a58txXoG+6sIcSCj1TqEn7c=",
         "owner": "nix-community",
         "repo": "neovim-nightly-overlay",
-        "rev": "21a2bc2a9f18c49d4c9ba811ab6c6746b8d62580",
+        "rev": "916b055a001b578567dfa4ccb82aaa210e2f3e20",
         "type": "github"
       },
       "original": {
@@ -449,11 +449,11 @@
       },
       "locked": {
         "dir": "contrib",
-        "lastModified": 1702595978,
-        "narHash": "sha256-PvcPk+f9ENeY5Jq1nvWpkL12KWeVQFhqQ2a8PLNfP/k=",
+        "lastModified": 1702769013,
+        "narHash": "sha256-Kaq//79n61r4e2p5N7g0MDDdUUPOj5+NnUhtLC0k23s=",
         "owner": "neovim",
         "repo": "neovim",
-        "rev": "f31f260f0c6449dba4c84071be6bfe91d3cb4993",
+        "rev": "8f08b1efbd096850c04c2e8e2890d993bd4d9f95",
         "type": "github"
       },
       "original": {
@@ -489,11 +489,11 @@
         "treefmt-nix": "treefmt-nix"
       },
       "locked": {
-        "lastModified": 1702254072,
-        "narHash": "sha256-48qPUGgw5CY3O1M2r6K5g9mJMyxvG1K9GaAb4tXxmSs=",
+        "lastModified": 1702743345,
+        "narHash": "sha256-xvRuGxFJLPbUPrLke4zf9UiqZadeLzV/gSfFTum0K4s=",
         "owner": "nix-community",
         "repo": "nix-eval-jobs",
-        "rev": "843dc25cfe93eb620f8dc8825b1de001e5b33e9f",
+        "rev": "4a1123c42dc9c4c07863a4bfa426f7fcc8f12b90",
         "type": "github"
       },
       "original": {
@@ -531,11 +531,11 @@
         "nixpkgs": "nixpkgs_2"
       },
       "locked": {
-        "lastModified": 1702516441,
-        "narHash": "sha256-bhC1cqytO99L9i4BZLjaIEgDP4CpuVf+yP9pIFaWHjE=",
+        "lastModified": 1702776069,
+        "narHash": "sha256-NCza/LTcFVu8YMzoo9XqQwzTaYjqb2Q4DMZxG6vWIX8=",
         "owner": "fufexan",
         "repo": "nix-gaming",
-        "rev": "dcd1a7c19691646e41a3328aa7d055ec43443b8d",
+        "rev": "b5bcd1163fff15a3fc9d97e7d148812736212b89",
         "type": "github"
       },
       "original": {
@@ -719,11 +719,11 @@
         "nixpkgs": "nixpkgs_6"
       },
       "locked": {
-        "lastModified": 1702649675,
-        "narHash": "sha256-sSGEuGpPnXhHWtR8e9XOlKm/OPy2ocfr2vaPDk1n0x4=",
+        "lastModified": 1702757751,
+        "narHash": "sha256-fDmMncKTNVVypMjv4Bv7F66MUdmMC3qZUyC+uVjsLs0=",
         "owner": "nix-community",
         "repo": "nixpkgs-wayland",
-        "rev": "8248ceefd6f2802d763aa9fc1b8682976ec8cc92",
+        "rev": "13140e9610b7de62b3f3e1e9d17f779c8d9fa21c",
         "type": "github"
       },
       "original": {
@@ -734,11 +734,11 @@
     },
     "nixpkgs_2": {
       "locked": {
-        "lastModified": 1702029940,
-        "narHash": "sha256-qM3Du0perpLesh5hr87mVPZ79McMUKIWUH7EQMh2kWo=",
+        "lastModified": 1702539185,
+        "narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "e9ef8a102c555da4f8f417fe5cf5bd539d8a38b7",
+        "rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447",
         "type": "github"
       },
       "original": {
@@ -782,11 +782,11 @@
     },
     "nixpkgs_5": {
       "locked": {
-        "lastModified": 1702249933,
-        "narHash": "sha256-OlqCC8A6OC5n0n4meCklk73V64JcyoJQ+EGa5arhvsI=",
+        "lastModified": 1702667777,
+        "narHash": "sha256-qpgZVpFrOEgW0DimJ24UXeFh63TI9fQFXxc58DPtG8Q=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "0838486e9183ce39398f01b221766e68b50f405c",
+        "rev": "203ecda835bcf69633df7183459283543dd4a874",
         "type": "github"
       },
       "original": {
@@ -878,11 +878,11 @@
     },
     "nur": {
       "locked": {
-        "lastModified": 1702640491,
-        "narHash": "sha256-+mKwkPnutEB1adKn51ykH2SbKXh2gcB4YWrIM9do7IU=",
+        "lastModified": 1702780981,
+        "narHash": "sha256-Ct9FHSS6VeFrAyA2/19bL1sq4JIzLfg+0neXvpDwOzY=",
         "owner": "nix-community",
         "repo": "NUR",
-        "rev": "476bb9eacb33dace8b614bf35bdc04129faeaae2",
+        "rev": "ddb4fe1f170116376c1c300883b239d4ce45f939",
         "type": "github"
       },
       "original": {
@@ -1116,11 +1116,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1702212301,
-        "narHash": "sha256-Rvl8BD7mnHZC1Qz6TxT120UyjsGUEdcsthHbEY+1vnU=",
+        "lastModified": 1702461037,
+        "narHash": "sha256-ssyGxfGHRuuLHuMex+vV6RMOt7nAo07nwufg9L5GkLg=",
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "afdd5e48a0869b389027307652a658051c0d2f96",
+        "rev": "d06b70e5163a903f19009c3f97770014787a080f",
         "type": "github"
       },
       "original": {