Add the quiet calendar capsule
This commit is contained in:
@@ -104,6 +104,10 @@ PanelWindow {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.itemSpacing
|
spacing: Theme.itemSpacing
|
||||||
|
|
||||||
|
CalendarIndicator {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
ActivityIndicator {
|
ActivityIndicator {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -179,12 +179,37 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyFixture(name: string): void {
|
function applyFixture(name: string): void {
|
||||||
if (name !== "daybook")
|
if (name !== "daybook" && name !== "upcoming" && name !== "outside-window")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
root.watchWanted = false;
|
root.watchWanted = false;
|
||||||
root.fixtureMode = true;
|
root.fixtureMode = true;
|
||||||
root.fixtureNow = Math.floor(new Date(2026, 7, 17, 11, 6).getTime() / 1000);
|
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 = [
|
root.sources = [
|
||||||
{ id: "fixture-work", name: "Fixture Work", color: "#82aaff" },
|
{ id: "fixture-work", name: "Fixture Work", color: "#82aaff" },
|
||||||
{ id: "fixture-cloud", name: "Fixture Cloud", color: "#c3e88d" },
|
{ id: "fixture-cloud", name: "Fixture Cloud", color: "#c3e88d" },
|
||||||
|
|||||||
@@ -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: a right-side Pill showing calendar glyph plus `12m` or `Now`.
|
||||||
- Produces: `applyFixture("upcoming")` and `applyFixture("outside-window")` for boundary tests.
|
- 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:
|
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.
|
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`.
|
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.
|
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.
|
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.
|
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
|
```bash
|
||||||
tests/quickshell/calendar-agenda-contract.sh
|
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.
|
Expected: calendar fixture boundaries PASS and every existing Quickshell contract remains green.
|
||||||
|
|
||||||
- [ ] **Step 5: Commit the capsule**
|
- [x] **Step 5: Commit the capsule**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add config/dot/quickshell/modules/bar/CalendarIndicator.qml \
|
git add config/dot/quickshell/modules/bar/CalendarIndicator.qml \
|
||||||
|
|||||||
@@ -49,6 +49,16 @@ for component in AgendaPane AgendaEvent NotificationSummary; do
|
|||||||
|| fail "$component is not registered"
|
|| fail "$component is not registered"
|
||||||
done
|
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
|
qs ipc call calendar-agenda reset >/dev/null
|
||||||
jq -e '.fixture == false' <<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
jq -e '.fixture == false' <<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||||
|| fail 'fixture reset did not restore live mode'
|
|| fail 'fixture reset did not restore live mode'
|
||||||
|
|||||||
Reference in New Issue
Block a user