8.0 KiB
Bugger for macOS — High-Level Design
Overview
Bugger is a lightweight macOS menu bar application that surfaces bugs assigned to you from a Feishu Bitable. It mirrors the experience of the Windows Bugger tool: a glanceable badge in the menu bar, a popover with your active bugs, native notifications when bugs are assigned or changed, and an optional floating widget.
Architecture
┌──────────────────────────────────────────────────────┐
│ User's Mac │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Menu Bar │ │ Floating │ │
│ │ Icon+Badge │ │ Widget │ │
│ │ [🐛 3] │ │ (optional) │ │
│ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────┴──────────────────┴───────┐ │
│ │ BugStore │ │
│ │ (ObservableObject) │ │
│ │ - bugs: [Bug] │ │
│ │ - unseenCount: Int │ │
│ │ - lastUpdated: Date │ │
│ └──────┬──────────────────────────┘ │
│ │ │
│ ┌──────┴──────────┐ ┌─────────────┐ │
│ │ PollerService │───▶│ FeishuService│ │
│ │ (Timer, 5min) │ │ │ │
│ └─────────────────┘ └──────┬──────┘ │
│ │ │
│ ┌──────────────┐ ┌────────┴───────┐ │
│ │ Notification │ │ TokenManager │ │
│ │ Service │ │ (Keychain) │ │
│ └──────────────┘ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘
│
│ HTTPS (OAuth Bearer)
▼
┌──────────────────────────────────────────────────────┐
│ Feishu Cloud │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ OAuth │ │ Bitable API │ │
│ │ /authen/v1/ │ │ /bitable/v1/ │ │
│ │ oidc/ │ │ apps/{token}/ │ │
│ │ access_token│ │ tables/{id}/ │ │
│ └──────────────┘ │ records │ │
│ └──────────────────┘ │
└──────────────────────────────────────────────────────┘
Key Design Decisions
1. Native SwiftUI (not cross-platform)
The app targets macOS exclusively. SwiftUI + AppKit bridging gives the best
integration with macOS idioms — MenuBarExtra, NSPanel floating windows,
Keychain, and UserNotifications — while keeping the binary under 10 MB.
2. Poll-based sync, not push
Feishu webhooks require a public-facing HTTP endpoint. For a personal tool, polling the Bitable API every N minutes is simpler, safer, and sufficient. Default interval: 5 minutes.
3. OAuth user_access_token with refresh
Uses Feishu's OAuth 2.0 authorization code flow to get a user_access_token
(valid 2h) and refresh_token (valid 30 days). Tokens are stored in the macOS
Keychain. The app acts on behalf of the user — no admin approval needed.
4. No backend server
Everything runs locally. No database, no server, no cloud hosting. The app stores only:
- OAuth tokens (Keychain)
- Configuration (UserDefaults: table ID, polling interval, etc.)
- Cached bug state (in-memory, rebuilt on launch)
5. Menu bar first, floating widget second
The menu bar popover is the primary interaction surface. The floating widget is an optional companion — a small always-on-top window showing the count, which clicks through to open the menu bar popover.
Component Map
| Component | Responsibility |
|---|---|
App |
@main entry, sets activation policy to .accessory (no dock icon) |
AppDelegate |
Menu bar icon registration, window management |
BugStore |
Central observable state: bug list, unseen count, loading/error |
FeishuService |
HTTP client for Feishu Open API (bitable records, OAuth) |
TokenManager |
OAuth flow orchestration, Keychain read/write, token refresh |
PollerService |
Configurable timer driving the fetch cycle |
NotificationService |
Diff engine + UNUserNotificationCenter push |
MenuBarView |
Menu bar icon with badge count |
BugListPopover |
Popover containing the scrollable bug list |
BugRow |
Single bug: title, priority pill, age, clickable |
FloatingWidget |
Optional NSPanel-based always-on-top overlay |
SettingsWindow |
Preferences: table config, poll interval, widget toggle |
KeychainHelper |
Thin wrapper around Security framework |
Data Flow
1. App Launch
├── Load tokens from Keychain
├── If no token → show OAuth setup window
├── If token expired → refresh via refresh_token
└── Start PollerService
2. Poll Cycle (every 5 min, configurable)
├── FeishuService.fetchRecords(table_id, filter=assignee=me)
├── Parse response → [Bug]
├── BugStore.diff(old, new)
│ ├── New bugs → NotificationService.fire()
│ ├── Status changes → NotificationService.fire()
│ └── Assignee changes → NotificationService.fire()
└── BugStore.bugs = new; BugStore.lastUpdated = now
3. User Interaction
├── Click menu bar icon → toggle popover
├── Click bug in popover → open in Feishu (browser / Feishu app URL)
├── "Mark all seen" → reset badge count
└── Floating widget click → open menu bar popover
Feishu API Surface
| API | Method | Purpose |
|---|---|---|
/authen/v1/oidc/access_token |
POST | Exchange authorization code for user_access_token |
/authen/v1/refresh_access_token |
POST | Refresh expired user_access_token |
/bitable/v1/apps/{app_token}/tables/{table_id}/records |
GET | Fetch bug records with filter |
All calls use Authorization: Bearer <user_access_token> and go to
https://open.feishu.cn.
Non-Functional Requirements
- Memory: < 80 MB at steady state
- CPU: idle < 0.1%, spike < 2% during poll
- Binary: < 15 MB
- Startup: < 2 seconds to menu bar icon visible
- Offline: graceful degradation (show stale data, retry on next interval)
- macOS: 14.0+ (Sonoma) —
MenuBarExtrais stable from 13.3, but 14.0 is a reasonable floor for 2024+