Add Ongoing to the Panama Daybook

This commit is contained in:
Gabriel Brown
2026-08-17 12:18:04 -04:00
parent 1ce5c732b7
commit a5450fdb6d
12 changed files with 483 additions and 47 deletions
+2 -1
View File
@@ -20,7 +20,8 @@ Last live audit: 2026-08-17, Fedora 44, Hyprland 0.56.2, Quickshell 0.3.0.
| Panel | Workspaces, CPU/RAM/GPU, clock, weather, quiet event capsule, media, clipboard, tray, status | Live | | Panel | Workspaces, CPU/RAM/GPU, clock, weather, quiet event capsule, media, clipboard, tray, status | Live |
| Clipboard history | Vicinae owns capture/storage; Quickshell provides the panel | Live | | Clipboard history | Vicinae owns capture/storage; Quickshell provides the panel | Live |
| Notifications | Quickshell server, toasts, grouped history, DND, clear actions | Live | | Notifications | Quickshell server, toasts, grouped history, DND, clear actions | Live |
| Date menu | Two-column Daybook with EDS agenda, notifications, month calendar, weather, and media | Live | | Date menu | Two-column Daybook with Agenda, Ongoing activity, Notifications, month calendar, weather, and media | Live |
| Ongoing activity | Focus, Caffeine, recording, screen sharing, camera, and microphone state with direct controls | Live |
| Calendar agenda | Existing GNOME, Google, iCloud, and Nextcloud sources through Evolution Data Server | Live | | Calendar agenda | Existing GNOME, Google, iCloud, and Nextcloud sources through Evolution Data Server | Live |
| Quick settings | Wi-Fi, Bluetooth, audio input/output, caffeine, night light, focus | Live | | Quick settings | Wi-Fi, Bluetooth, audio input/output, caffeine, night light, focus | Live |
| Focus sessions | Persistent workspace-bound timer, DND/caffeine coordination, Signal Glass controls | Live | | Focus sessions | Persistent workspace-bound timer, DND/caffeine coordination, Signal Glass controls | Live |
+10 -2
View File
@@ -191,8 +191,16 @@ Settings remains searchable in Vicinae for hardware and account panels.
Click the clock to open the two-column Daybook on **Agenda**. The month grid, Click the clock to open the two-column Daybook on **Agenda**. The month grid,
today's schedule, Up Next, weather, and conditional media controls stay visible today's schedule, Up Next, weather, and conditional media controls stay visible
together. `SUPER + B` opens **Notifications** in that same surface; returning to together. Its right pane has three first-class pages:
Agenda preserves the selected date and visible month.
- **Agenda** — the selected day's calendar.
- **Ongoing** — Focus, Caffeine, recording, screen sharing, camera, and
microphone activity, visible until each state ends.
- **Notifications** — grouped history, Do Not Disturb, and clear actions.
`SUPER + B` opens Notifications directly. Switching pages preserves the
selected date and visible month. Brief completed events such as screenshots and
device changes remain in Signal Glass instead of accumulating in Ongoing.
Calendar data comes from Evolution Data Server, so the Google, iCloud, Calendar data comes from Evolution Data Server, so the Google, iCloud,
Nextcloud, and local calendars already configured for GNOME remain the source Nextcloud, and local calendars already configured for GNOME remain the source
@@ -7,8 +7,6 @@ import qs.modules.quicksettings
Item { Item {
id: root id: root
signal requestNotifications
readonly property bool isToday: CalendarAgenda.selectedDateKey === CalendarAgenda.dateKey(clock.date) readonly property bool isToday: CalendarAgenda.selectedDateKey === CalendarAgenda.dateKey(clock.date)
readonly property var upNext: CalendarAgenda.nextEventForSelectedDate readonly property var upNext: CalendarAgenda.nextEventForSelectedDate
readonly property var listedEvents: CalendarAgenda.selectedEvents.filter(event => !root.upNext || event.id !== root.upNext.id) readonly property var listedEvents: CalendarAgenda.selectedEvents.filter(event => !root.upNext || event.id !== root.upNext.id)
@@ -238,10 +236,6 @@ Item {
} }
} }
NotificationSummary {
width: parent.width
onActivated: root.requestNotifications()
}
} }
function relativeStart(event: var): string { function relativeStart(event: var): string {
@@ -1,4 +1,4 @@
// GNOME's date/time menu: the calendar and the message tray in one panel, // GNOME's date/time menu: calendar, ongoing activity and messages in one panel,
// dropping from the top centre because that is where GNOME put it and the // dropping from the top centre because that is where GNOME put it and the
// muscle memory is the whole point. // muscle memory is the whole point.
// //
@@ -20,8 +20,10 @@ import qs.widgets
PanelWindow { PanelWindow {
id: root id: root
// "notifications" is the existing overlay key, so Super+B, the notification readonly property bool ongoingContentFits: panel.ongoingContentFits
// IPC target and the clock all land here.
// "notifications" remains the overlay key so existing keybinds keep their
// contract; the surface itself now has Agenda, Ongoing and Notifications.
visible: ShellState.notificationsOpen visible: ShellState.notificationsOpen
color: "transparent" color: "transparent"
@@ -1,5 +1,5 @@
// The approved A · Daybook: month context on the left, the selected day's // The approved A · Daybook: month context on the left, the selected day's
// schedule or grouped notification history on the right. // schedule, persistent activity or grouped notification history on the right.
import QtQuick import QtQuick
import Quickshell import Quickshell
@@ -46,7 +46,7 @@ Item {
Text { Text {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 4 anchors.leftMargin: 4
anchors.right: headerActions.left anchors.right: dateMenuTabs.left
anchors.rightMargin: Theme.itemSpacing anchors.rightMargin: Theme.itemSpacing
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: Qt.formatDateTime(clock.date, "dddd, MMMM d") text: Qt.formatDateTime(clock.date, "dddd, MMMM d")
@@ -59,35 +59,36 @@ Item {
} }
Row { Row {
id: headerActions id: dateMenuTabs
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: 6 spacing: 6
HeaderPill { HeaderPill {
visible: ShellState.dateMenuPage === "notifications" text: "Agenda"
text: "← Back to agenda" active: ShellState.dateMenuPage === "agenda"
onClicked: ShellState.dateMenuPage = "agenda" onClicked: ShellState.dateMenuPage = "agenda"
} }
HeaderPill { HeaderPill {
text: "Do Not Disturb" text: "Ongoing"
active: Notifs.doNotDisturb active: ShellState.dateMenuPage === "ongoing"
icon: "notifications-disabled-symbolic" badge: Ongoing.count
onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb onClicked: ShellState.dateMenuPage = "ongoing"
}
HeaderPill {
text: "Notifications"
active: ShellState.dateMenuPage === "notifications"
badge: Notifs.history.length
onClicked: ShellState.dateMenuPage = "notifications"
} }
HeaderPill { HeaderPill {
visible: ShellState.dateMenuPage === "agenda" visible: ShellState.dateMenuPage === "agenda"
text: "Open Calendar ↗" text: "Calendar ↗"
onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate) onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate)
} }
HeaderPill {
visible: ShellState.dateMenuPage === "notifications" && Notifs.hasNotifications
text: "Clear all"
onClicked: Notifs.dismissAll()
}
} }
} }
@@ -165,10 +166,16 @@ Item {
anchors.top: parent.top anchors.top: parent.top
anchors.margins: 14 anchors.margins: 14
visible: ShellState.dateMenuPage === "agenda" visible: ShellState.dateMenuPage === "agenda"
onRequestNotifications: { }
ShellState.dateMenuPage = "notifications";
Notifs.markAllRead(); OngoingPane {
} id: ongoingPane
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 14
maxHeight: body.height - 28
visible: ShellState.dateMenuPage === "ongoing"
} }
Column { Column {
@@ -186,6 +193,8 @@ Item {
Text { Text {
anchors.left: parent.left anchors.left: parent.left
anchors.right: notificationActions.left
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: "Notifications" text: "Notifications"
color: Theme.fg color: Theme.fg
@@ -194,14 +203,24 @@ Item {
font.weight: Font.DemiBold font.weight: Font.DemiBold
} }
Text { Row {
id: notificationActions
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: Notifs.history.length + (Notifs.history.length === 1 ? " item" : " items") spacing: 6
color: Theme.fgDim
font.family: Theme.fontFamily HeaderPill {
font.features: Theme.tabularFigures text: "Do Not Disturb"
font.pixelSize: Theme.fontSizeSmall active: Notifs.doNotDisturb
icon: "notifications-disabled-symbolic"
onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb
}
HeaderPill {
visible: Notifs.hasNotifications
text: "Clear all"
onClicked: Notifs.dismissAll()
}
} }
} }
@@ -215,9 +234,14 @@ Item {
} }
} }
readonly property real rightContentHeight: ShellState.dateMenuPage === "notifications" readonly property real rightContentHeight: {
? notificationPane.implicitHeight if (ShellState.dateMenuPage === "notifications")
: agenda.implicitHeight return notificationPane.implicitHeight;
if (ShellState.dateMenuPage === "ongoing")
return ongoingPane.implicitHeight;
return agenda.implicitHeight;
}
readonly property bool ongoingContentFits: ongoingPane.implicitHeight <= body.height - 28
component HeaderPill: Rectangle { component HeaderPill: Rectangle {
id: pill id: pill
@@ -225,6 +249,7 @@ Item {
required property string text required property string text
property string icon: "" property string icon: ""
property bool active: false property bool active: false
property int badge: -1
signal clicked signal clicked
width: pillRow.implicitWidth + 20 width: pillRow.implicitWidth + 20
@@ -260,6 +285,27 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
} }
Rectangle {
anchors.verticalCenter: parent.verticalCenter
visible: pill.badge > 0
width: visible ? badgeText.implicitWidth + 9 : 0
height: 17
radius: Theme.pillRadius
border.width: 0
color: Theme.alpha(pill.active ? Theme.accent : Theme.fg, 0.12)
Text {
id: badgeText
anchors.centerIn: parent
text: String(pill.badge)
color: pill.active ? Theme.accent : Theme.fgDim
font.family: Theme.fontFamily
font.features: Theme.tabularFigures
font.pixelSize: 9
font.weight: Font.DemiBold
}
}
} }
MouseArea { MouseArea {
@@ -0,0 +1,263 @@
// Persistent work and privacy activity inside the Daybook.
import QtQuick
import qs.config
import qs.services
import qs.modules.quicksettings
Item {
id: root
property real maxHeight: 500
implicitHeight: content.implicitHeight
Column {
id: content
width: parent.width
spacing: 12
Item {
width: parent.width
height: 38
Column {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: 2
Text {
text: "PERSISTENT ACTIVITY"
color: Theme.fgMuted
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
font.letterSpacing: 0.9
}
Text {
text: "Ongoing"
color: Theme.fg
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.DemiBold
}
}
Text {
anchors.right: parent.right
anchors.bottom: parent.bottom
text: Ongoing.count === 0 ? "Nothing active" : Ongoing.count + (Ongoing.count === 1 ? " activity" : " activities")
color: Theme.fgDim
font.family: Theme.fontFamily
font.features: Theme.tabularFigures
font.pixelSize: Theme.fontSizeSmall
}
}
Item {
width: parent.width
height: visible ? 190 : 0
visible: Ongoing.count === 0
Column {
anchors.centerIn: parent
spacing: 8
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "\u{F04A5}"
color: Theme.fgMuted
font.family: Theme.fontMono
font.pixelSize: 24
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "Nothing ongoing"
color: Theme.fg
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
font.weight: Font.DemiBold
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "Focus, Caffeine, recording and privacy activity appear here."
color: Theme.fgDim
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
}
}
}
ScrollColumn {
width: parent.width
// Header + footer + the two 12px Column gaps consume 116px.
// Keeping that outside the scroll budget prevents the pane from
// growing beyond the Daybook when every producer is active.
maxHeight: Math.max(0, root.maxHeight - 116)
spacing: 8
visible: Ongoing.count > 0
Repeater {
model: Ongoing.activities
Rectangle {
id: activityRow
required property var modelData
width: parent.width
height: 62
radius: Theme.cardRadius
border.width: 0
color: rowMouse.containsMouse
? Theme.alpha(Theme.fg, 0.10)
: Theme.alpha(Theme.fg, 0.055)
Behavior on color { ColorAnimation { duration: Theme.durFast } }
Text {
id: activityIcon
anchors.left: parent.left
anchors.leftMargin: 13
anchors.verticalCenter: parent.verticalCenter
text: activityRow.modelData.glyph
color: root.toneColor(activityRow.modelData.tone)
font.family: Theme.fontMono
font.pixelSize: 17
}
Column {
anchors.left: activityIcon.right
anchors.leftMargin: 11
anchors.right: activityAction.visible ? activityAction.left : activityState.left
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
spacing: 3
Text {
width: parent.width
text: activityRow.modelData.label
color: Theme.fg
elide: Text.ElideRight
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
font.weight: Font.DemiBold
}
Text {
width: parent.width
text: activityRow.modelData.detail
color: Theme.fgDim
elide: Text.ElideRight
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
}
}
Rectangle {
id: activityState
anchors.right: activityAction.visible ? activityAction.left : parent.right
anchors.rightMargin: activityAction.visible ? 7 : 12
anchors.verticalCenter: parent.verticalCenter
width: stateLabel.implicitWidth + 16
height: 25
radius: Theme.pillRadius
border.width: 0
color: Theme.alpha(root.toneColor(activityRow.modelData.tone), 0.12)
Text {
id: stateLabel
anchors.centerIn: parent
text: activityRow.modelData.state
color: root.toneColor(activityRow.modelData.tone)
font.family: Theme.fontFamily
font.features: Theme.tabularFigures
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
}
}
Rectangle {
id: activityAction
z: 2
anchors.right: parent.right
anchors.rightMargin: 9
anchors.verticalCenter: parent.verticalCenter
visible: activityRow.modelData.action !== ""
width: visible ? actionLabel.implicitWidth + 18 : 0
height: 28
radius: Theme.pillRadius
border.width: 0
color: actionMouse.containsMouse
? Theme.alpha(activityRow.modelData.tone === "danger" ? Theme.danger : Theme.fg, 0.20)
: Theme.alpha(activityRow.modelData.tone === "danger" ? Theme.danger : Theme.fg, 0.08)
Text {
id: actionLabel
anchors.centerIn: parent
text: activityRow.modelData.action
color: activityRow.modelData.tone === "danger" ? Theme.danger : Theme.fg
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
}
MouseArea {
id: actionMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: Ongoing.invoke(activityRow.modelData.kind)
}
}
MouseArea {
id: rowMouse
anchors.fill: parent
z: 1
enabled: activityRow.modelData.kind !== "caffeine"
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: Ongoing.activate(activityRow.modelData.kind)
}
}
}
}
Rectangle {
width: parent.width
height: 54
radius: Theme.cardRadius
border.width: 0
visible: Ongoing.count > 0
color: Theme.alpha(Theme.fg, 0.035)
Column {
anchors.left: parent.left
anchors.leftMargin: 13
anchors.verticalCenter: parent.verticalCenter
spacing: 3
Text {
text: "Visible until it ends"
color: Theme.fg
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
}
Text {
text: "Completed screenshots, devices and other brief events stay in Signal Glass."
color: Theme.fgDim
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
}
}
}
}
function toneColor(tone: string): color {
if (tone === "danger")
return Theme.danger;
if (tone === "warn")
return Theme.warn;
if (tone === "ok")
return Theme.ok;
return Theme.accent;
}
}
@@ -9,4 +9,5 @@ MonthArrow 1.0 MonthArrow.qml
NotificationGroup 1.0 NotificationGroup.qml NotificationGroup 1.0 NotificationGroup.qml
NotificationList 1.0 NotificationList.qml NotificationList 1.0 NotificationList.qml
NotificationSummary 1.0 NotificationSummary.qml NotificationSummary 1.0 NotificationSummary.qml
OngoingPane 1.0 OngoingPane.qml
WeatherCard 1.0 WeatherCard.qml WeatherCard 1.0 WeatherCard.qml
@@ -0,0 +1,98 @@
pragma Singleton
// One presentation model for state that remains active until it is explicitly
// stopped or naturally completes. Transient confirmations stay in Signal Glass.
import Quickshell
import QtQuick
Singleton {
id: root
readonly property var activities: {
const result = [];
if (FocusSession.active) {
result.push({
kind: "focus",
glyph: "\u{F051F}",
label: "Focus session",
detail: (FocusSession.workspaceLabel || "Focused workspace") + " · " + FocusSession.statusText,
state: FocusSession.remainingText,
tone: FocusSession.paused ? "warn" : "accent",
action: FocusSession.paused ? "Resume" : "Pause"
});
}
if (Caffeine.enabled) {
result.push({
kind: "caffeine",
glyph: "\u{F0F2E}",
label: "Caffeine",
detail: "Automatic sleep is blocked",
state: "On",
tone: "ok",
action: "Turn off"
});
}
if (PrivacyState.recordingActive) {
result.push({
kind: "recording",
glyph: "\u{F044A}",
label: "Screen recording",
detail: "Panama · " + root.recordingElapsed,
state: "Live",
tone: "danger",
action: "Stop"
});
}
if (PrivacyState.screenSharingActive)
result.push(root.privacyActivity("screen", "\u{F0379}", "Screen sharing", PrivacyState.screenSharingApp));
if (PrivacyState.cameraActive)
result.push(root.privacyActivity("camera", "\u{F0100}", "Camera", PrivacyState.cameraApp));
if (PrivacyState.microphoneActive)
result.push(root.privacyActivity("microphone", "\u{F036C}", "Microphone", PrivacyState.microphoneApp));
return result;
}
readonly property int count: activities.length
readonly property string recordingElapsed: {
const total = Capture.recordingSeconds;
const seconds = String(total % 60).padStart(2, "0");
const minutes = Math.floor(total / 60) % 60;
const hours = Math.floor(total / 3600);
return hours > 0
? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}`
: `${minutes}:${seconds}`;
}
function privacyActivity(kind: string, glyph: string, label: string, app: string): var {
return {
kind,
glyph,
label,
detail: app || "Managed by the application",
state: "In use",
tone: "warn",
action: ""
};
}
function activate(kind: string): void {
if (kind === "focus")
FocusSession.activateWorkspace();
else if (["recording", "screen", "camera", "microphone"].indexOf(kind) >= 0)
ShellState.open("activity");
}
function invoke(kind: string): void {
if (kind === "focus")
FocusSession.pauseOrResume();
else if (kind === "caffeine")
Caffeine.toggle();
else if (kind === "recording")
Capture.stopRecording();
}
}
@@ -138,6 +138,8 @@ Singleton {
root.applyStates(false, true, false, "", "Contract fixture", "", true); root.applyStates(false, true, false, "", "Contract fixture", "", true);
else if (name === "screen-on") else if (name === "screen-on")
root.applyStates(false, false, true, "", "", "Contract fixture", true); root.applyStates(false, false, true, "", "", "Contract fixture", true);
else if (name === "all-on")
root.applyStates(true, true, true, "Contract fixture", "Contract fixture", "Contract fixture", true);
else if (name === "all-off") else if (name === "all-off")
root.applyStates(false, false, false, "", "", "", true); root.applyStates(false, false, false, "", "", "", true);
} }
@@ -35,8 +35,8 @@ Singleton {
readonly property bool anyOverlayOpen: activeOverlay !== "" readonly property bool anyOverlayOpen: activeOverlay !== ""
// The date menu is one surface with two deliberate entry points. The clock // The date menu keeps calendar context fixed while its right pane switches
// opens the Daybook; Super+B and notification actions open message history. // between the schedule, persistent activity and message history.
property string dateMenuPage: "agenda" property string dateMenuPage: "agenda"
// 0 means "use the currently focused workspace". A positive value lets a // 0 means "use the currently focused workspace". A positive value lets a
@@ -53,12 +53,12 @@ Singleton {
} }
function openDateMenu(page: string): void { function openDateMenu(page: string): void {
root.dateMenuPage = page === "notifications" ? "notifications" : "agenda"; root.dateMenuPage = root.normalizedDateMenuPage(page);
root.activeOverlay = "notifications"; root.activeOverlay = "notifications";
} }
function toggleDateMenu(page: string): void { function toggleDateMenu(page: string): void {
const target = page === "notifications" ? "notifications" : "agenda"; const target = root.normalizedDateMenuPage(page);
if (root.activeOverlay === "notifications" && root.dateMenuPage === target) { if (root.activeOverlay === "notifications" && root.dateMenuPage === target) {
root.activeOverlay = ""; root.activeOverlay = "";
return; return;
@@ -67,6 +67,10 @@ Singleton {
root.activeOverlay = "notifications"; root.activeOverlay = "notifications";
} }
function normalizedDateMenuPage(page: string): string {
return ["agenda", "ongoing", "notifications"].indexOf(page) >= 0 ? page : "agenda";
}
function openOverview(workspaceId: int): void { function openOverview(workspaceId: int): void {
root.overviewWorkspaceId = Math.max(0, workspaceId); root.overviewWorkspaceId = Math.max(0, workspaceId);
root.overviewQuery = ""; root.overviewQuery = "";
+5 -2
View File
@@ -79,7 +79,7 @@ ShellRoot {
Overview {} Overview {}
// QuickSettings is the window; QuickSettingsPanel is its content. // QuickSettings is the window; QuickSettingsPanel is its content.
QuickSettings {} QuickSettings {}
DateMenu {} DateMenu { id: dateMenu }
ClipboardPanel {} ClipboardPanel {}
CaptureOverlay {} CaptureOverlay {}
IntelligenceResult {} IntelligenceResult {}
@@ -268,6 +268,7 @@ ShellRoot {
function reset(): void { CalendarAgenda.clearFixture(); } function reset(): void { CalendarAgenda.clearFixture(); }
function refresh(): void { CalendarAgenda.refresh(); } function refresh(): void { CalendarAgenda.refresh(); }
function open(): void { ShellState.openDateMenu("agenda"); } function open(): void { ShellState.openDateMenu("agenda"); }
function page(name: string): void { ShellState.openDateMenu(name); }
function close(): void { ShellState.close(); } function close(): void { ShellState.close(); }
function select(date: string): void { CalendarAgenda.selectIsoDate(date); } function select(date: string): void { CalendarAgenda.selectIsoDate(date); }
function status(): string { function status(): string {
@@ -281,7 +282,9 @@ ShellRoot {
selectedDate: CalendarAgenda.selectedDateKey, selectedDate: CalendarAgenda.selectedDateKey,
selectedEventCount: CalendarAgenda.selectedEvents.length, selectedEventCount: CalendarAgenda.selectedEvents.length,
capsuleVisible: CalendarAgenda.capsuleVisible, capsuleVisible: CalendarAgenda.capsuleVisible,
capsuleText: CalendarAgenda.capsuleText capsuleText: CalendarAgenda.capsuleText,
ongoingCount: Ongoing.count,
ongoingFits: dateMenu.ongoingContentFits
}); });
} }
} }
@@ -8,6 +8,8 @@ fail() {
} }
cleanup() { cleanup() {
qs ipc call focus end >/dev/null 2>&1 || true
qs ipc call activity clear >/dev/null 2>&1 || true
qs ipc call calendar-agenda reset >/dev/null 2>&1 || true qs ipc call calendar-agenda reset >/dev/null 2>&1 || true
qs ipc call calendar-agenda close >/dev/null 2>&1 || true qs ipc call calendar-agenda close >/dev/null 2>&1 || true
} }
@@ -40,6 +42,18 @@ jq -e '.open == true and .page == "notifications"' \
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \ <<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|| fail 'notification open did not route to Notifications' || fail 'notification open did not route to Notifications'
qs ipc call activity fixture microphone-on >/dev/null
qs ipc call calendar-agenda page ongoing >/dev/null
jq -e '.open == true and .page == "ongoing" and .ongoingCount >= 1' \
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|| fail 'persistent activity did not route to Ongoing'
qs ipc call activity fixture all-on >/dev/null
qs ipc call focus start >/dev/null
jq -e '.ongoingCount == 5 and .ongoingFits == true' \
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|| fail 'crowded Ongoing activity does not fit the Daybook'
hyprctl layers | rg -q 'namespace: qs-popover-datemenu' \ hyprctl layers | rg -q 'namespace: qs-popover-datemenu' \
|| fail 'Daybook layer did not map' || fail 'Daybook layer did not map'
rg -q 'implicitWidth: 760' config/dot/quickshell/modules/datemenu/DateMenu.qml \ rg -q 'implicitWidth: 760' config/dot/quickshell/modules/datemenu/DateMenu.qml \