From 8272c6d881baa83f82f95d95c628245e7de4d4c1 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Mon, 18 May 2026 02:33:43 -0400 Subject: [PATCH] Remove legacy bar widget modules --- .../omarchy-shell/plugins/bar/Bar.qml | 290 ------------------ .../plugins/bar/widgets/networkPanel.qml | 79 ++++- .../plugins/bar/widgets/weatherFlyout.qml | 30 +- .../plugins/settings/SettingsPanel.qml | 14 +- .../omarchy-shell/shell-defaults.json | 2 +- 5 files changed, 108 insertions(+), 307 deletions(-) diff --git a/default/quickshell/omarchy-shell/plugins/bar/Bar.qml b/default/quickshell/omarchy-shell/plugins/bar/Bar.qml index 040e78da..12383ed4 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/Bar.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/Bar.qml @@ -1,8 +1,6 @@ import Quickshell -import Quickshell.Bluetooth import Quickshell.Hyprland import Quickshell.Io -import Quickshell.Services.Pipewire import Quickshell.Services.SystemTray import Quickshell.Services.UPower import Quickshell.Wayland @@ -58,22 +56,13 @@ Item { Behavior on foreground { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } } Behavior on background { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } } Behavior on urgent { ColorAnimation { duration: 420; easing.type: Easing.InOutCubic } } - property string weatherText: "" - property string weatherClass: "" property bool updateAvailable: false property string voxtypeIcon: "" property string voxtypeClass: "" property string screenRecordingText: "" property string screenRecordingTooltip: "" - property string idleText: "" - property string idleTooltip: "" property string notificationSilencingText: "" property string notificationSilencingTooltip: "" - property int audioVolumePercent: -1 - property string networkKind: "disconnected" - property string networkLabel: "" - property int networkSignal: -1 - property string networkFrequency: "" property bool clockAlt: false property var tooltipTarget: null property string tooltipText: "" @@ -223,20 +212,13 @@ Item { case "omarchy": return omarchyModuleComponent case "workspaces": return workspacesModuleComponent case "clock": return clockModuleComponent - case "weather": return weatherModuleComponent case "update": return updateModuleComponent case "voxtype": return voxtypeModuleComponent case "screenRecording": case "screenrecording": return screenRecordingModuleComponent - case "idle": return idleModuleComponent case "notifications": case "notificationSilencing": return notificationsModuleComponent case "tray": return trayModuleComponent - case "bluetooth": return bluetoothModuleComponent - case "network": return networkModuleComponent - case "audio": - case "pulseaudio": return audioModuleComponent - case "cpu": return cpuModuleComponent case "battery": return batteryModuleComponent default: return null } @@ -338,37 +320,6 @@ Item { else finalize() } - function networkCommand() { - return [ - "device=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == \"dev\") { print $(i + 1); exit } }')", - "if [[ -z $device ]]; then", - " printf 'disconnected\\t\\t\\t\\n'", - " exit 0", - "fi", - "if [[ ! -d /sys/class/net/$device/wireless ]]; then", - " printf 'ethernet\\t%s\\t\\t\\n' \"$device\"", - " exit 0", - "fi", - "show=$(iwctl station \"$device\" show 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g')", - "state=$(awk '/^[[:space:]]*State[[:space:]]/ { sub(/.*State[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", - "ssid=$(awk '/^[[:space:]]*Connected network[[:space:]]/ { sub(/.*Connected network[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", - "freq=$(awk '/^[[:space:]]*Frequency[[:space:]]/ { sub(/.*Frequency[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", - "rssi=$(awk '/^[[:space:]]*RSSI[[:space:]]/ { sub(/.*RSSI[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", - "dbm=${rssi%% *}", - "signal=\"\"", - "if [[ -n $dbm ]]; then", - " if (( dbm >= -50 )); then signal=100", - " elif (( dbm <= -100 )); then signal=0", - " else signal=$(( 2 * (dbm + 100) )); fi", - "fi", - "if [[ -n $state && $state != connected ]]; then", - " printf 'disconnected\\t\\t\\t\\n'", - " exit 0", - "fi", - "printf 'wifi\\t%s\\t%s\\t%s\\n' \"${ssid:-$device}\" \"$signal\" \"$freq\"" - ].join("\n") - } - function run(command) { if (!command) return @@ -431,12 +382,6 @@ Item { } } - function updateWeather(raw) { - var data = parseModuleJson(raw) - weatherText = data.text || "" - weatherClass = data.class || "" - } - function updateIndicator(name, raw) { var data = parseModuleJson(raw) var text = data.text || "" @@ -445,9 +390,6 @@ Item { if (name === "screenRecording") { screenRecordingText = text screenRecordingTooltip = tooltip - } else if (name === "idle") { - idleText = text - idleTooltip = tooltip } else if (name === "notifications") { notificationSilencingText = text notificationSilencingTooltip = tooltip @@ -464,18 +406,6 @@ Item { else voxtypeIcon = "" } - function updateNetwork(raw) { - var parts = String(raw || "disconnected\t\t\t").replace(/\r?\n+$/, "").split("\t") - networkKind = parts[0] || "disconnected" - networkLabel = parts[1] || "" - networkSignal = parts[2] ? parseInt(parts[2], 10) : -1 - networkFrequency = parts[3] || "" - } - - function refreshWeather() { - runProcess(weatherProc) - } - function refreshUpdate() { runProcess(updateProc) } @@ -486,23 +416,9 @@ Item { function refreshIndicators() { refreshScreenRecording() - runProcess(idleProc) runProcess(notificationSilencingProc) } - function refreshNetwork() { - runProcess(networkProc) - } - - function updateAudioVolume(raw) { - var volume = parseInt(String(raw || "").trim(), 10) - audioVolumePercent = isNaN(volume) ? -1 : Math.max(0, volume) - } - - function refreshAudio() { - runProcess(audioProc) - } - function workspaceById(id) { var values = Hyprland.workspaces.values for (var i = 0; i < values.length; i++) { @@ -553,95 +469,6 @@ Item { return defaultIcons[index] } - function audioIcon() { - var sink = Pipewire.defaultAudioSink - if (!sink || !sink.audio) return "" - if (sink.audio.muted) return "" - - var props = sink.properties || {} - var sinkText = String([ - sink.name, - sink.description, - sink.nickname, - props["device.icon-name"], - props["device.product.name"], - props["node.name"] - ].join(" ")).toLowerCase() - - if (sinkText.indexOf("headphone") !== -1 || sinkText.indexOf("headset") !== -1) - return "" - - var volume = sink.audio.volume - if (!isFinite(volume) && audioVolumePercent >= 0) - volume = audioVolumePercent / 100 - if (!isFinite(volume)) return "" - if (volume < 0.34) return "" - if (volume < 0.67) return "" - return "" - } - - function bluetoothIcon() { - var adapter = Bluetooth.defaultAdapter - if (!adapter) return "" - if (!adapter.enabled) return "󰂲" - - var devices = Bluetooth.devices.values - for (var i = 0; i < devices.length; i++) { - if (devices[i].connected) - return "󰂱" - } - - return "" - } - - function networkIcon() { - if (networkKind === "wifi") { - var icons = ["󰤯", "󰤟", "󰤢", "󰤥", "󰤨"] - var index = Math.max(0, Math.min(4, Math.ceil(networkSignal / 20) - 1)) - return icons[index] - } - - if (networkKind === "ethernet") return "󰀂" - return "󰤮" - } - - function networkTooltip() { - if (networkKind === "wifi") { - var frequency = parseFloat(networkFrequency) - var frequencyText = frequency > 0 ? " (" + (frequency / 1000).toFixed(1) + " GHz)" : "" - return (networkLabel || "Wi-Fi") + frequencyText - } - - if (networkKind === "ethernet") return "Connected" - return "Disconnected" - } - - function audioTooltip() { - var sink = Pipewire.defaultAudioSink - if (sink && sink.audio) { - var volume = sink.audio.volume - if (isFinite(volume)) - return "Playing at " + Math.round(volume * 100) + "%" - } - - if (audioVolumePercent >= 0) - return "Playing at " + audioVolumePercent + "%" - - return "Audio" - } - - function bluetoothTooltip() { - var count = 0 - var devices = Bluetooth.devices.values - - for (var i = 0; i < devices.length; i++) { - if (devices[i].connected) - count++ - } - - return "Devices connected: " + count - } - function batteryTooltip() { var device = UPower.displayDevice if (!device || !device.isPresent) return "" @@ -720,23 +547,6 @@ Item { onFileChanged: barHiddenProbe.running = true } - Process { - id: weatherProc - command: ["bash", "-lc", root.commandWithOmarchyPath(root.shellQuote(root.omarchyPath + "/default/quickshell/omarchy-shell/scripts/weather.sh"))] - stdout: StdioCollector { - waitForEnd: true - onStreamFinished: root.updateWeather(text) - } - } - - Timer { - interval: 60000 - running: true - repeat: true - triggeredOnStart: true - onTriggered: root.refreshWeather() - } - Process { id: updateProc command: ["bash", "-lc", root.commandWithOmarchyPath("omarchy-update-available")] @@ -774,15 +584,6 @@ Item { } } - Process { - id: idleProc - command: ["bash", "-lc", root.commandWithOmarchyPath(root.shellQuote(root.omarchyPath + "/default/quickshell/omarchy-shell/scripts/indicators/idle.sh"))] - stdout: StdioCollector { - waitForEnd: true - onStreamFinished: root.updateIndicator("idle", text) - } - } - Process { id: notificationSilencingProc command: ["bash", "-lc", root.commandWithOmarchyPath(root.shellQuote(root.omarchyPath + "/default/quickshell/omarchy-shell/scripts/indicators/notification-silencing.sh"))] @@ -800,40 +601,6 @@ Item { onTriggered: root.refreshIndicators() } - Process { - id: networkProc - command: ["bash", "-lc", root.commandWithOmarchyPath(root.networkCommand())] - stdout: StdioCollector { - waitForEnd: true - onStreamFinished: root.updateNetwork(text) - } - } - - Timer { - interval: 3000 - running: true - repeat: true - triggeredOnStart: true - onTriggered: root.refreshNetwork() - } - - Process { - id: audioProc - command: ["bash", "-lc", "if command -v pamixer >/dev/null; then pamixer --get-volume; elif command -v wpctl >/dev/null; then wpctl get-volume @DEFAULT_AUDIO_SINK@ | awk '{ print int($2 * 100) }'; fi"] - stdout: StdioCollector { - waitForEnd: true - onStreamFinished: root.updateAudioVolume(text) - } - } - - Timer { - interval: 2000 - running: true - repeat: true - triggeredOnStart: true - onTriggered: root.refreshAudio() - } - IpcHandler { target: "bar" @@ -999,17 +766,11 @@ Item { Component { id: omarchyModuleComponent; OmarchyModule {} } Component { id: workspacesModuleComponent; WorkspacesModule {} } Component { id: clockModuleComponent; ClockModule {} } - Component { id: weatherModuleComponent; WeatherModule {} } Component { id: updateModuleComponent; UpdateModule {} } Component { id: voxtypeModuleComponent; VoxtypeModule {} } Component { id: screenRecordingModuleComponent; ScreenRecordingModule {} } - Component { id: idleModuleComponent; IdleModule {} } Component { id: notificationsModuleComponent; NotificationsModule {} } Component { id: trayModuleComponent; TrayModule {} } - Component { id: bluetoothModuleComponent; BluetoothModule {} } - Component { id: networkModuleComponent; NetworkModule {} } - Component { id: audioModuleComponent; AudioModule {} } - Component { id: cpuModuleComponent; CpuModule {} } Component { id: batteryModuleComponent; BatteryModule {} } function findCenterAnchorEntry() { @@ -1405,14 +1166,6 @@ Item { } } - component WeatherModule: ModuleButton { - text: root.weatherText - active: root.weatherClass === "active" - keepSpace: false - horizontalMargin: 7.5 - onPressed: function() { root.run("omarchy-notification-send \"$(omarchy-weather-status)\"") } - } - component UpdateModule: ModuleButton { text: root.updateAvailable ? "" : "" fontSize: 10 @@ -1437,13 +1190,6 @@ Item { onPressed: function() { root.run("omarchy-capture-screenrecording") } } - component IdleModule: IndicatorModule { - text: root.idleText - active: root.idleText !== "" - tooltipText: root.idleTooltip - onPressed: function() { root.run("omarchy-toggle-idle") } - } - component NotificationsModule: IndicatorModule { text: root.notificationSilencingText active: root.notificationSilencingText !== "" @@ -1876,42 +1622,6 @@ Item { } } - component BluetoothModule: ModuleButton { - text: root.bluetoothIcon() - horizontalMargin: 8.5 - visible: Bluetooth.defaultAdapter !== null - onPressed: function() { root.run("omarchy-launch-bluetooth") } - } - - component NetworkModule: ModuleButton { - text: root.networkIcon() - horizontalMargin: 6.5 - rightExtraMargin: 2 - tooltipText: root.networkTooltip() - onPressed: function() { root.run("OMARCHY_PATH=" + root.shellQuote(root.omarchyPath) + " PATH=" + root.shellQuote(root.omarchyPath + "/bin:" + (Quickshell.env("PATH") || "")) + " " + root.shellQuote(root.omarchyPath + "/bin/omarchy-launch-wifi")) } - } - - component AudioModule: ModuleButton { - text: root.audioIcon() - onPressed: function(button) { - if (button === Qt.RightButton) root.run("pamixer -t") - else root.run("omarchy-launch-audio") - } - onWheelMoved: function(delta) { - if (delta > 0) root.run("pamixer -i 5") - else if (delta < 0) root.run("pamixer -d 5") - root.refreshAudio() - } - } - - component CpuModule: ModuleButton { - text: "󰍛" - onPressed: function(button) { - if (button === Qt.RightButton) root.run("alacritty") - else root.run("omarchy-launch-or-focus-tui btop") - } - } - component BatteryModule: ModuleButton { property var device: UPower.displayDevice diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml index add0543d..fcb0b348 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/networkPanel.qml @@ -148,9 +148,61 @@ iwctl known-networks list 2>/dev/null \\ if (net && net.connected) forget(net.ssid) } - readonly property string kind: bar ? bar.networkKind : "disconnected" - readonly property string label: bar ? bar.networkLabel : "" - readonly property int signalStrength: bar ? bar.networkSignal : -1 + // Bar pill state. Polled locally so this panel is self-contained; + // populated by networkProc + networkTimer below. + property string kind: "disconnected" + property string label: "" + property int signalStrength: -1 + property string frequency: "" + + function updateNetwork(raw) { + var parts = String(raw || "disconnected\t\t\t").replace(/\r?\n+$/, "").split("\t") + kind = parts[0] || "disconnected" + label = parts[1] || "" + signalStrength = parts[2] ? parseInt(parts[2], 10) : -1 + frequency = parts[3] || "" + } + + function networkTooltip() { + if (kind === "wifi") { + var f = parseFloat(frequency) + var ftext = f > 0 ? " (" + (f / 1000).toFixed(1) + " GHz)" : "" + return (label || "Wi-Fi") + ftext + } + if (kind === "ethernet") return "Connected" + return "Disconnected" + } + + function networkCommand() { + return [ + "device=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == \"dev\") { print $(i + 1); exit } }')", + "if [[ -z $device ]]; then", + " printf 'disconnected\\t\\t\\t\\n'", + " exit 0", + "fi", + "if [[ ! -d /sys/class/net/$device/wireless ]]; then", + " printf 'ethernet\\t%s\\t\\t\\n' \"$device\"", + " exit 0", + "fi", + "show=$(iwctl station \"$device\" show 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g')", + "state=$(awk '/^[[:space:]]*State[[:space:]]/ { sub(/.*State[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", + "ssid=$(awk '/^[[:space:]]*Connected network[[:space:]]/ { sub(/.*Connected network[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", + "freq=$(awk '/^[[:space:]]*Frequency[[:space:]]/ { sub(/.*Frequency[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", + "rssi=$(awk '/^[[:space:]]*RSSI[[:space:]]/ { sub(/.*RSSI[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")", + "dbm=${rssi%% *}", + "signal=\"\"", + "if [[ -n $dbm ]]; then", + " if (( dbm >= -50 )); then signal=100", + " elif (( dbm <= -100 )); then signal=0", + " else signal=$(( 2 * (dbm + 100) )); fi", + "fi", + "if [[ -n $state && $state != connected ]]; then", + " printf 'disconnected\\t\\t\\t\\n'", + " exit 0", + "fi", + "printf 'wifi\\t%s\\t%s\\t%s\\n' \"${ssid:-$device}\" \"$signal\" \"$freq\"" + ].join("\n") + } readonly property string icon: { if (kind === "wifi") { @@ -525,7 +577,7 @@ iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\ text: root.icon horizontalMargin: 8.5 rightExtraMargin: 2 - tooltipText: bar ? bar.networkTooltip() : "" + tooltipText: root.networkTooltip() onPressed: function(b) { if (b === Qt.RightButton) root.launchImpala() @@ -1146,4 +1198,23 @@ iwctl known-networks list 2>/dev/null \\ } } } + + // Poll the wifi/ethernet pill state every 3s. Local to this panel so + // Bar.qml does not need to mirror network state. + Process { + id: networkProc + command: ["bash", "-lc", root.bar ? root.bar.commandWithOmarchyPath(root.networkCommand()) : ""] + stdout: StdioCollector { + waitForEnd: true + onStreamFinished: root.updateNetwork(text) + } + } + + Timer { + interval: 3000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: if (!networkProc.running) networkProc.running = true + } } diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml index 023eca48..1fd0d3ea 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml @@ -30,8 +30,16 @@ Item { property var dailyForecastReport: null property string wttrLocation: "" - readonly property string label: bar ? bar.weatherText : "" - readonly property string klass: bar ? bar.weatherClass : "" + // Bar pill state. Polled locally; populated by weatherProc below. + property string label: "" + property string klass: "" + + function updateWeather(raw) { + var data + try { data = JSON.parse(raw || "{}") } catch (e) { data = {} } + label = data.text || "" + klass = data.class || "" + } readonly property var current: report && report.current_condition && report.current_condition[0] ? report.current_condition[0] : null readonly property var areaInfo: report && report.nearest_area && report.nearest_area[0] ? report.nearest_area[0] : null @@ -533,4 +541,22 @@ Item { } } } + + // Poll the weather pill text/class every minute. Local to this widget. + Process { + id: weatherProc + command: ["bash", "-lc", root.bar ? root.bar.commandWithOmarchyPath(root.bar.shellQuote(root.bar.omarchyPath + "/default/quickshell/omarchy-shell/scripts/weather.sh")) : ""] + stdout: StdioCollector { + waitForEnd: true + onStreamFinished: root.updateWeather(text) + } + } + + Timer { + interval: 60000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: if (!weatherProc.running) weatherProc.running = true + } } diff --git a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml index 11c0ae79..ae9bc334 100644 --- a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml @@ -306,21 +306,15 @@ Item { } // ---------------- widget catalog ----------------------------------------- - readonly property var legacyWidgetMeta: ({ + readonly property var builtinWidgetMeta: ({ "omarchy": { name: "Omarchy menu", description: "Launches the Omarchy menu", category: "Compositor" }, "workspaces": { name: "Workspaces", description: "Workspace number indicators", category: "Compositor" }, "clock": { name: "Clock", description: "Date / time text", category: "Time" }, - "weather": { name: "Weather (legacy)", description: "Tiny weather pill", category: "Info" }, "update": { name: "Updates", description: "Indicates available system updates", category: "System" }, "voxtype": { name: "Voxtype", description: "Voxtype dictation state", category: "Status" }, "screenRecording": { name: "Screen recording", description: "Active recording indicator", category: "Status" }, - "idle": { name: "Idle (legacy)", description: "Inhibitor indicator", category: "Status" }, "notifications": { name: "DND", description: "Do-not-disturb indicator", category: "Status" }, "tray": { name: "System tray", description: "Status notifier items", category: "Status" }, - "bluetooth": { name: "Bluetooth (legacy)", description: "Bluetooth status icon", category: "Network" }, - "network": { name: "Network (legacy)", description: "Wi-Fi / ethernet status", category: "Network" }, - "audio": { name: "Volume (legacy)", description: "Speaker icon, scroll for volume", category: "Audio" }, - "cpu": { name: "CPU (legacy)", description: "btop launcher", category: "System" }, "battery": { name: "Battery", description: "Battery percent and ETA", category: "System" } }) @@ -344,7 +338,7 @@ Item { var key = String(id || "") if (root.barWidgetRegistry && root.barWidgetRegistry.has(key)) return root.barWidgetRegistry.metadataFor(key) || {} - if (legacyWidgetMeta[key]) return legacyWidgetMeta[key] + if (builtinWidgetMeta[key]) return builtinWidgetMeta[key] var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[key] : null if (manifest) { @@ -409,7 +403,7 @@ Item { ids[pid] = true } } - for (var key in legacyWidgetMeta) ids[key] = true + for (var key in builtinWidgetMeta) ids[key] = true return Object.keys(ids) } @@ -432,7 +426,7 @@ Item { var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null var manifestIsBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1 var isBarWidget = !!(meta && meta.source !== "plugin") || manifestIsBarWidget - if (!isBarWidget && !legacyWidgetMeta[id]) continue + if (!isBarWidget && !builtinWidgetMeta[id]) continue var inSection = sectionArray(section) var existsHere = false diff --git a/default/quickshell/omarchy-shell/shell-defaults.json b/default/quickshell/omarchy-shell/shell-defaults.json index 774610a2..f3280488 100644 --- a/default/quickshell/omarchy-shell/shell-defaults.json +++ b/default/quickshell/omarchy-shell/shell-defaults.json @@ -33,7 +33,7 @@ "id": "screenRecording" }, { - "id": "idle" + "id": "idleInhibitor" }, { "id": "notifications"