Skip to content

About

A macOS-native LeetCode coaching companion that lives in your menu bar — streak, weak-tag radar, next-problem recommender, and contest rating ETA. GraphQL only, zero dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

Repository files navigation

🔥 Grind

A macOS-native LeetCode coaching surface — not a dashboard.

Grind lives in your menu bar and on your desktop, and answers the only three questions that actually change behaviour:

Am I safe today? · Where am I bleeding? · What do I solve next?


Platform Swift UI Data Deps Status Tests


Grind menu bar popover

Why it's different

The usual "LeetCode tracker" is a Python/Tk window that shows a solved count and a heatmap. That's a dashboard — nobody changes behaviour because of a dashboard.

Grind is a coaching surface. It escalates urgency as your streak comes under threat, finds the tags you're actually weak in (measured against yourself, not a leaderboard), and hands you one concrete unsolved problem to do next — all without opening a browser.

Grind does Grind refuses to
GraphQL only, one plausible-header request path ❌ Scrape HTML
Native MenuBarExtra + WidgetKit + Swift Charts ❌ Electron / Python / web views
Keychain-only optional session cookie ❌ Store credentials in plaintext
Compare you to your own solved distribution ❌ Fabricate confidence from thin data
Zero third-party dependencies ❌ Pull in a framework to draw a grid

Features

🔥 Streak, with teeth

The menu bar icon is the state. It shifts from flame → flame.fill → warning triangle as local midnight approaches, and the notification copy is honest about stakes: "you're about to drop a 47-day streak," not "reminder."

🗓️ A heatmap that's actually correct

7×N grid, quantile-based color buckets (readable whether you do 5/day or 50/day), month labels placed by the week that contains the 1st — never 4.33-weeks-per-month math. UTC→local day mapping is unit-tested against leap years, DST weeks, and Asia/Kolkata offsets.

🎯 Weak-tag radar

Coverage ratio + z-score per tag, computed against your own mean. Surfaces your bottom 5 tags — so it doesn't punish you for a category you deliberately skipped.

➡️ Next up

One recommended problem: weighted-random over your weakest tags, difficulty adapted to your solve rate, guaranteed unsolved and (by default) non-premium. One click opens it.

📈 Rating trajectory — honestly

Linear regression over your last 10 contests with an ETA to the next threshold. If there isn't enough signal (< 6 contests or R² < 0.3) it says "not enough signal" instead of lying.

🧩 Widgets

Small / Medium / Large / Notification-Center families, all reading a shared SwiftData cache. The timeline never touches the network and flips streak state exactly at local midnight.


Architecture

┌──────────────────────────────────────────────────────────────┐
│  GrindApp  (LSUIElement · no dock icon)                        │
│  MenuBarExtra(.window) · Settings · onboarding                 │
└───────────────┬───────────────────────────────┬──────────────┘
                │ embeds                         │ writes cache
        ┌───────▼────────┐              ┌────────▼─────────┐
        │    GrindKit     │             │   App Group      │
        │  (framework)    │◀────reads───│  SwiftData store │
        │                 │             └────────▲─────────┘
        │  Networking ─── GraphQLClient (URLSession, retry, ETag)
        │  Models ─────── Codable DTOs + @Model                   │
        │  Store ──────── ProfileStore (actor) refresh orchestr.  │
        │  Domain ─────── StreakEngine · TagAnalyzer ·            │
        │                 Recommender · RatingProjector           │
        │  UI ─────────── HeatmapView (Canvas) · RingView         │
        └─────────────────────────────┬──────────────────────────┘
                                       │ embeds (reads cache only)
                               ┌───────▼────────┐
                               │  GrindWidget   │
                               │  (WidgetKit)   │
                               └────────────────┘
  • GrindKit — the shared framework, embedded in both the app and the widget. All domain logic is pure and testable with zero network in tests (recorded JSON fixtures).
  • XcodeGen owns the project. project.yml is the single source of truth; the .pbxproj is generated and git-ignored. Never hand-edited.

Getting started

Requirements

  • macOS 14.0 (Sonoma) or later — desktop widgets need it
  • Full Xcode.app (the Command Line Tools alone can't build app/widget targets)
  • XcodeGen — brew install xcodegen

Build & run

make gen      # generate Grind.xcodeproj from project.yml
make build    # build the app (Debug)
make run      # launch it — a flame appears in your menu bar
make test     # run the GrindKit suite (no network)

Run make help to see every target.

Note

If make build reports xcodebuild: error: ... requires Xcode, you only have the Command Line Tools installed. Install Xcode from the App Store, then:

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

Important

Widgets + App Group without a paid team. The widget reads the app's SwiftData cache through the App Group group.com.grind.shared. Adding that entitlement normally makes Xcode demand a provisioning profile — so the checked-in build entitlements omit it, and make build re-signs the app + widget ad-hoc with the group entitlement afterwards (make sign-groups). This works on macOS with no signing team. With a real DEVELOPMENT_TEAM, point CODE_SIGN_ENTITLEMENTS at the *.groups.entitlements files and delete the re-sign step.

Note: the accessory widget families (accessoryRectangular, …) are iOS/watchOS-only — on macOS the three system sizes also serve Notification Center, so that's what ships.


Screenshots

Grind menu bar popover

The menu bar popover — streak header, solved rings, today's daily, a tag-targeted Next-up recommendation, weak tags, a 26-week heatmap, and the contest rating ETA. Live data, real account.



Grind Settings — Appearance

Settings — Account · Appearance · Alerts · Advanced (theme, heatmap range/color, launch-at-login, notifications, session cookie, reset cache).



Grind popover — LeetCode heatmap style

LeetCode heatmap style (Settings → Appearance → Heatmap style) — the exact leetcode.com layout: each month is its own block (the 1st placed at its weekday, partial weeks padded, a gap between months), fixed count-based green levels, and a horizontal slider across the year.

Widgets (systemSmall / systemMedium / systemLarge) read the same shared cache — add them via right-click desktop → Edit Widgets → search "Grind".


Roadmap

Milestones ship in order; each must build and test green (see SPEC.md §11).

  • M0 — project.yml, Makefile, three targets, menu bar app scaffold
  • M1 — GraphQLClient + DTOs + 7 real fixtures + decode tests
  • M2 — StreakEngine + HeatmapView + full timezone/leap/DST suite
  • M3 — popover wired to live data + username onboarding
  • M4 — SwiftData App Group cache + widget families (3 macOS system sizes)
  • M5 — TagAnalyzer + Recommender + Next-up card
  • M6 — contest module + RatingProjector + honest ETA
  • M7 — notifications, Settings, launch-at-login, Keychain cookie

Shipping it (for later)

Local dev uses ad-hoc signing so the app builds without a paid team. To hand the app to someone else, sign + notarize:

# 1. Sign with your Developer ID and the hardened runtime
codesign --force --deep --options runtime \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  build/Build/Products/Release/Grind.app

# 2. Notarize (requires an App Store Connect API key / app-specific password)
xcrun notarytool submit Grind.zip \
  --apple-id you@example.com --team-id TEAMID --wait

# 3. Staple the ticket
xcrun stapler staple build/Build/Products/Release/Grind.app

Notarization is a manual, ship-time step — never run it in CI.


Project layout

Grind/
├── project.yml          # XcodeGen manifest — the source of truth
├── Makefile             # gen / build / test / run / lint / clean
├── SPEC.md              # the full product + engineering spec
├── Sources/
│   ├── GrindKit/        # shared framework (Networking · Models · Store · Domain · UI)
│   ├── GrindApp/        # MenuBarExtra + Settings + onboarding
│   └── GrindWidget/     # WidgetKit extension
└── Tests/
    ├── GrindKitTests/   # pure unit tests, zero network
    └── Fixtures/        # recorded GraphQL JSON responses

Built for macOS. GraphQL only. Zero dependencies. Your username is the identity.

About

A macOS-native LeetCode coaching companion that lives in your menu bar — streak, weak-tag radar, next-problem recommender, and contest rating ETA. GraphQL only, zero dependencies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages