diff --git a/config/dot/quickshell/modules/bar/Bar.qml b/config/dot/quickshell/modules/bar/Bar.qml index be9d040..e61e527 100644 --- a/config/dot/quickshell/modules/bar/Bar.qml +++ b/config/dot/quickshell/modules/bar/Bar.qml @@ -104,6 +104,10 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: Theme.itemSpacing + CalendarIndicator { + anchors.verticalCenter: parent.verticalCenter + } + ActivityIndicator { anchors.verticalCenter: parent.verticalCenter } diff --git a/config/dot/quickshell/modules/bar/CalendarIndicator.qml b/config/dot/quickshell/modules/bar/CalendarIndicator.qml new file mode 100644 index 0000000..24695d9 --- /dev/null +++ b/config/dot/quickshell/modules/bar/CalendarIndicator.qml @@ -0,0 +1,53 @@ +// A deliberately quiet appointment reminder. It only enters the bar during +// the narrow window where an upcoming event is immediately useful. + +import QtQuick +import QtQuick.Controls +import qs.config +import qs.services +import qs.widgets + +Pill { + id: root + + visible: CalendarAgenda.capsuleVisible + opacity: visible ? 1 : 0 + horizontalPadding: 8 + onActivated: ShellState.openDateMenu("agenda") + + ToolTip.visible: root.hovered && root.visible + ToolTip.delay: 500 + ToolTip.text: CalendarAgenda.nextEvent?.summary ?? "Upcoming event" + + Behavior on opacity { + NumberAnimation { + duration: 120 + easing.type: Easing.OutCubic + } + } + + Behavior on implicitWidth { + NumberAnimation { + duration: 120 + easing.type: Easing.OutCubic + } + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: "\u{F00ED}" + color: Theme.accent + font.family: Theme.fontMono + font.pixelSize: 13 + } + + Text { + anchors.verticalCenter: parent.verticalCenter + text: CalendarAgenda.capsuleText + color: Theme.fg + font.family: Theme.fontFamily + font.features: Theme.tabularFigures + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.DemiBold + } +} diff --git a/config/dot/quickshell/services/CalendarAgenda.qml b/config/dot/quickshell/services/CalendarAgenda.qml index a5cc555..a9e334d 100644 --- a/config/dot/quickshell/services/CalendarAgenda.qml +++ b/config/dot/quickshell/services/CalendarAgenda.qml @@ -179,12 +179,37 @@ Singleton { } function applyFixture(name: string): void { - if (name !== "daybook") + if (name !== "daybook" && name !== "upcoming" && name !== "outside-window") return; root.watchWanted = false; root.fixtureMode = true; root.fixtureNow = Math.floor(new Date(2026, 7, 17, 11, 6).getTime() / 1000); + + if (name === "upcoming" || name === "outside-window") { + const offset = name === "upcoming" ? 12 * 60 : 20 * 60; + root.sources = [{ id: "fixture-work", name: "Fixture Work", color: "#82aaff" }]; + root.events = [ + { + id: "fixture-work:timed:1", sourceId: "fixture-work", uid: "fixture-timed", + summary: "Fixture appointment", start: root.fixtureNow + offset, + end: root.fixtureNow + offset + 30 * 60, allDay: false, + location: "", joinUrl: "" + }, + { + id: "fixture-work:all-day:1", sourceId: "fixture-work", uid: "fixture-all-day", + summary: "Fixture all day", start: Math.floor(new Date(2026, 7, 17).getTime() / 1000), + end: Math.floor(new Date(2026, 7, 18).getTime() / 1000), allDay: true, + location: "", joinUrl: "" + } + ]; + root.errors = []; + root.generatedAt = root.fixtureNow; + root.phase = "ready"; + root.selectIsoDate("2026-08-17"); + return; + } + root.sources = [ { id: "fixture-work", name: "Fixture Work", color: "#82aaff" }, { id: "fixture-cloud", name: "Fixture Cloud", color: "#c3e88d" }, diff --git a/docs/superpowers/plans/2026-08-17-calendar-agenda.md b/docs/superpowers/plans/2026-08-17-calendar-agenda.md index 00d2ef4..7acf590 100644 --- a/docs/superpowers/plans/2026-08-17-calendar-agenda.md +++ b/docs/superpowers/plans/2026-08-17-calendar-agenda.md @@ -660,7 +660,7 @@ git commit -m "Build the Prism calendar Daybook" - Produces: a right-side Pill showing calendar glyph plus `12m` or `Now`. - Produces: `applyFixture("upcoming")` and `applyFixture("outside-window")` for boundary tests. -- [ ] **Step 1: Add failing capsule boundary assertions** +- [x] **Step 1: Add failing capsule boundary assertions** Append: @@ -676,13 +676,13 @@ jq -e '.capsuleVisible == false' <<<"$(qs ipc call calendar-agenda status)" >/de Run and expect RED because those fixtures do not exist. -- [ ] **Step 2: Implement deterministic timing fixtures and capsule state** +- [x] **Step 2: Implement deterministic timing fixtures and capsule state** Add `fixtureNow` to CalendarAgenda. Production uses `Date.now() / 1000`; fixture modes use their explicit clock. `upcoming` creates one timed event exactly 12 minutes ahead. `outside-window` creates one 20 minutes ahead plus an all-day event. Verify all-day never wins `nextEvent`. Use a one-minute `Timer` only while events are loaded to update relative time. Midnight refresh remains separate. There is no sub-minute repaint. -- [ ] **Step 3: Build CalendarIndicator and mount it in Bar** +- [x] **Step 3: Build CalendarIndicator and mount it in Bar** Use `Pill` with `visible: CalendarAgenda.capsuleVisible`, horizontal padding 8, a calendar Nerd Font glyph, and tabular relative text. `onActivated` opens Agenda. Import `QtQuick.Controls` and use `ToolTip.visible: root.hovered && root.visible` with the next event's title; the title must not otherwise appear in the bar. @@ -690,7 +690,7 @@ Mount the indicator at the beginning of Bar's right row, before ActivityIndicato Use a single 120 ms opacity/width entrance tied to visibility. No looping animation. -- [ ] **Step 4: Run capsule and full shell contracts** +- [x] **Step 4: Run capsule and full shell contracts** ```bash tests/quickshell/calendar-agenda-contract.sh @@ -699,7 +699,7 @@ for test in tests/quickshell/*.sh; do "$test"; done Expected: calendar fixture boundaries PASS and every existing Quickshell contract remains green. -- [ ] **Step 5: Commit the capsule** +- [x] **Step 5: Commit the capsule** ```bash git add config/dot/quickshell/modules/bar/CalendarIndicator.qml \ diff --git a/tests/quickshell/calendar-agenda-contract.sh b/tests/quickshell/calendar-agenda-contract.sh index e540367..2677681 100755 --- a/tests/quickshell/calendar-agenda-contract.sh +++ b/tests/quickshell/calendar-agenda-contract.sh @@ -49,6 +49,16 @@ for component in AgendaPane AgendaEvent NotificationSummary; do || fail "$component is not registered" done +qs ipc call calendar-agenda fixture upcoming >/dev/null +jq -e '.capsuleVisible == true and .capsuleText == "12m"' \ + <<<"$(qs ipc call calendar-agenda status)" >/dev/null \ + || fail 'upcoming capsule window is wrong' + +qs ipc call calendar-agenda fixture outside-window >/dev/null +jq -e '.capsuleVisible == false' \ + <<<"$(qs ipc call calendar-agenda status)" >/dev/null \ + || fail 'capsule appears outside the quiet window' + qs ipc call calendar-agenda reset >/dev/null jq -e '.fixture == false' <<<"$(qs ipc call calendar-agenda status)" >/dev/null \ || fail 'fixture reset did not restore live mode'