Your day, in the menu bar.
A small native macOS app that shows your Google Calendar agenda without opening a browser tab, a calendar app, or anything else.
A day passing. The line marks now, finished events dim behind it, and the menu bar counts down to whatever is next.
- Today's agenda in one click — times, titles, calendar colours, and a line marking where you are in the day. Finished events stay on screen, dimmed, because "nothing left today" is accurate and useless at 23:45.
- Your next commitment, always visible — the menu bar counts down to it, so most of the time you never open the panel at all.
- This week in a small window — seven days, multi-day events repeated on every day they span, empty days kept so it reads as a calendar rather than a list.
- Join the call from the menu bar — Meet, Teams, Zoom, Webex, Whereby and Jitsi links are pulled out of the invite, so the meeting is one click from the countdown.
- Only the calendars you want — hide the ones you do not, per calendar, across every account macOS has.
Deliberately not a calendar client. No editing, no month grid, no inbox.
| Light | Dark | |
|---|---|---|
| Today | ![]() |
![]() |
| This week | ![]() |
![]() |
| Settings | ![]() |
![]() |
Every image above is rendered from a synthetic fixture, never from a real account —
Glance's UI is someone's appointments, so publishing a screenshot of it would publish
their day. See DemoData.swift.
Important
Glance is signed with a self-signed certificate, not notarized. macOS blocks a downloaded build outright on first launch, usually claiming the app is damaged. It is not — that is what an un-notarized app looks like to Gatekeeper.
Download Glance-<version>.zip from Releases, unzip, move to
/Applications, then run this before first launch:
xattr -dr com.apple.quarantine /Applications/Glance.app
open /Applications/Glance.appRight-click → Open also works on some macOS versions, but recent ones have stopped offering it for unsigned apps, so the command above is the reliable route.
Building from source avoids all of this — a locally built bundle never gets a quarantine attribute, so Gatekeeper never engages. It is the recommended path until there is a Developer ID to notarize with.
Three one-time steps. Full detail in docs/SETUP.md.
1. Connect Google to macOS. System Settings → Internet Accounts → Google → enable Calendars. macOS runs the sync; Glance reads the result.
2. Tick your calendars at syncselect, then restart the sync agent.
killall CalendarAgent # relaunches on demand; safeGoogle's CalDAV bridge only sends calendars selected on that page, and it defaults to your primary one. The second half matters as much as the first: macOS picks up new events within minutes but does not promptly re-read the list of calendars, so without the restart the fix looks like it did nothing. This cost a full day to work out.
3. Create a self-signed code-signing certificate — otherwise the calendar permission prompt returns after every rebuild, because macOS keys the grant to the signature.
Check what actually arrived:
open -n Glance.app --args --scan-dump && sleep 6 && head -20 "$TMPDIR/glance-scan.log"No Xcode required — Command Line Tools are enough. The build never calls xcodebuild.
./build.sh # release build → Glance.app
./build.sh --dist # + dist/Glance-<version>.zip
./run.sh --verify # launch and confirm it started
./Tools/make-icon.sh # regenerate AppIcon.icns from IconView.swiftGlance is menu-bar-only (LSUIElement), so there is no Dock icon and no window.
Look in the menu bar; pgrep -x Glance is the only programmatic proof it is running.
An app with no window is hard to test by looking at it, so it explains itself instead:
./Glance.app/Contents/MacOS/Glance --selftest # 83 checks, no permissions
open -n Glance.app --args --scan-dump # real calendar read
open -n Glance.app --args --scan-dump --probe-links # where conference links live
.build/release/Glance --render-ui out/ # UI in all four appearances--selftest covers countdown formatting, day grouping, midnight and timezone rollover,
calendar filtering — and the contrast budget: every foreground/background pairing the
UI renders is checked against WCAG AA in light, dark, and both Increase Contrast
variants. A colour change that fails contrast fails the build.
--scan-dump must go through open. macOS attributes a permission request to the
responsible process, so a binary run from a terminal is judged against Terminal's
grant: access reads notDetermined and the request returns instantly without prompting
or erroring.
Glance reads calendars through EventKit, so macOS runs the Google sync and Glance
renders the result. No OAuth, no tokens, no polling loop, no Google Cloud project — and
events update live through EKEventStoreChanged.
CalendarService ──► GlanceStore ──► views
(actor) (@MainActor)
CalendarService is the only file that imports EventKit, and it is an actor because
EKEventStore is not Sendable. Everything crossing out of it is a Sendable value
type, so no EventKit object ever reaches a view. Source is flat in Sources/Glance/.
The trade is that EventKit cannot see some Google-specific extras: no per-event colours,
and RSVP is permanently read-only. Conference links turned out not to be among them —
a probe found them sitting in notes on 52 of 148 events, which retired a planned week
of OAuth work and made the Join button a NSDataDetector pass instead. Glance extracts
the link at the service edge and discards the notes there; meeting agendas and dial-in
PINs never travel further into the app. That story is in docs/PLAN.md.
Glance sends nothing anywhere. There is no network code in it at all — no analytics, no crash reporting, no update check. It reads the calendar database macOS already maintains, and that is the whole of its I/O.
The one thing it writes is ~/Library/Preferences/space.godev.Glance.plist: which
calendars you have hidden, and whether past events stay on screen.
All eight phases shipped. It reads every calendar, survives midnight and timezone changes, adapts to light and dark, is accessible and localizable, and events are actionable — join the call, open it in Calendar.app, copy the link.
Current state in docs/STATUS.md, what is next in docs/TODO.md, and why any of it is the way it is in docs/PLAN.md — including the decisions not to build notifications, quick-add, or a Google API integration.
See CONTRIBUTING.md. The short version: no dependencies without
discussion, keep EventKit inside CalendarService, colours only from Palette, and
--selftest has to pass.
MIT © 2026 Alessio Occhipinti (@LasaleFamine)





