Skip to main content

Command Palette

Search for a command to run...

I Built a Timeline in My Menu Bar Because I Kept Losing Track of Time

Updated
6 min readView as Markdown
D

I'm a full stack software Engineer with over a decade of combined experience building apps in web 2 and web 3.

It was 9:47pm, and I was still at my desk. I’d spent the entire afternoon doing work that felt productive but completely ignored my actual allocations for the day.

That happens at an agency. You're juggling four or five clients, executing a schedule someone else planned and patching the gaps as you go. So when nobody's telling you what's next, you answer Slack. You fix yesterday's minor bug. You tackle the interesting problem instead of the necessary one. Then it's dark out, and you've lost the afternoon.

The real problem was that my schedule was buried. It lived in harvest ( a separate tool), a calendar app, and my head. Checking it required opening tabs and context-switching, which I naturally avoided doing when busy. I needed a tool that answered a single question: Given my allocations and meetings today, am I working on the right thing right now?

The Requirements

Once I treated this as a design problem, the requirements got very crisp:

  • Visual pattern recognition: I wanted to read my day like a weather strip, grasping my progress and upcoming blocks at a glance.

  • Zero distractions: No notifications, no dashboards, no pop-ups. It had to provide ambient awareness, exactly like a clock on the wall.

  • Omnipresent on all screens: I work across multiple monitors. The timeline needed to be visible everywhere, regardless of what app I had running full-screen.

  • Complete daily context: It had to show project allocations, meeting overlaps, my lunch break, and a clear marker for right now.

The Idea: A Timeline at the Edge of Your Vision

I look at the macOS menu bar constantly to check the time or battery. It's peripheral and never in the way. I decided to turn that wasted space into a continuous timeline running across the top edge of every display.

Project blocks, meetings, breaks, and a current-time marker, all compressed into one thin strip. Glance up, know where you are, and get back to the right work. That became EdgeLight.

How I Built EdgeLight

The Stack: Native AppKit

I built EdgeLight as a native macOS app using Swift and AppKit. A web wrapper would waste resources and feel clunky. I needed a low-footprint background process, transparent overlay windows pinned above full-screen apps, and custom drawing for a 6-pixel high timeline. AppKit handles all of this natively. The app runs as an .accessory so it stays out of the dock entirely.

Three Layers of Truth

Everything flows into a TimelineBuilder that merges three data sources into a single visual model (spanning 9:00–17:30 by default):

Layer Source What it tells you
Projects 3SC Scheduler API How your day is allocated across clients
Meetings Google Calendar What's actually booked on top of that plan
Break Local preferences Where you're supposed to stop

Projects dictate the base layer. If I have 6 hours on Project A and 1.5 hours on Project B, the bar splits proportionally based on those allocations.

Meetings and breaks draw directly on top of those project blocks. In an agency, a client call eats into your allocated project time; the timeline reflects that reality.

I set a strict draw order:

  1. Base colors for projects.

  2. Grey stacked stripes for meetings.

  3. A crosshatch for lunch.

  4. A white marker for the current time.

Hovering reveals a native tooltip with exact hours and details of each project or meeting.

Every Screen, Every Space

The system menu bar is too restrictive for custom rendering across multiple monitors. Instead, EdgeLight creates a borderless, transparent panel on each display, pinned just below the menu bar at CGShieldingWindowLevel.

The EdgeOverlayController detects monitor connections and rebuilds these windows automatically. It joins all macOS Spaces, ensuring the timeline stays visible even when Figma or Xcode takes over an entire screen.

Wiring Up the Data

EdgeLight pulls allocations directly from the 3SC Scheduler API. One GET request with the date and email returns the whole day's plan. If the API fails, the app falls back to manual local allocations so the bar never goes blank.

For calendar events, I implemented a local loopback server for Google OAuth with PKCE. It stores the refresh token in the macOS Keychain and prefetches events. Conveniently, authenticating with Google provides the email address needed for the 3SC API. You sign in once, and both data sources know who you are.

The Bugs That Shipped With Real Data

Testing with live schedules surfaced immediate rendering and state bugs:

  • Async dropping: The schedule fetch callback failed because the provider object deallocated too early. I had to retain it explicitly for the lifetime of the fetch.

  • Wrong payload: I passed an internal person ID to the API instead of an email address, resulting in hours of debugging empty schedules.

  • Draw order conflicts: All-day calendar events initially rendered over the lunch break crosshatch, hiding it completely. I hardcoded the break to always draw last.

  • Keychain loops: Stale development signatures caused macOS to bombard me with security dialogs. I moved to silent reads and pruned old entries on startup to fix the colleague onboarding experience.

Shipping It to Colleagues

I packaged the app for my team by embedding the dashboard API key and Google OAuth credentials directly into the binary. A colleague downloads the DMG, clicks "Sign in with Google," and the app configures itself.

My ./build_dmg.sh script compiles the Swift package and builds the disk image. It's ad-hoc signed for now, meaning colleagues right-click and open it the first time to bypass Gatekeeper. Proper Developer ID signing is on the backlog.

What It Looks Like in Practice

Picture a typical Tuesday. The strip across the top of both monitors shows:

  • A wide blue block — 5 hours on Client A.

  • A narrower green block — 2.5 hours on Client B.

  • Grey stripes at 10:00 and 14:30 — standups and a client call, stacked so you can see both.

  • cream crosshatch at 12:30 — lunch, visible even under the 14:30 meeting's tail.

  • A thin white line creeping rightward — where you are right now.

You alt-tab into Slack. The bar is still there. You go fullscreen in your editor. Still there. You glance up during a long compile. You know you're in the green block, the standup is in 20 minutes, and you haven't taken lunch yet.

https://youtu.be/1cdfXONV1Lk

The Meta-Lesson

The hardest part wasn't the code. It was deciding what to leave out. I stripped away task management, Pomodoro timers, and Slack integrations. EdgeLight exists to do one thing: show me the shape of my day while I'm inside it. Now the plan is always there, right at the edge of my vision.

J

this is one of those “simple idea, hard execution” tools. turning menu bar space into a live schedule strip actually makes sense for focus work.

D

Thank you