Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,23 @@ name: CI
on:
push:
branches: [develop]
paths:
- 'Sources/**'
- 'Tests/**'
- 'Entitlements/**'
- 'project.yml'
- 'build.sh'
- 'test.sh'
- '.github/workflows/ci.yml'
pull_request:
paths:
- 'Sources/**'
- 'Tests/**'
- 'Entitlements/**'
- 'project.yml'
- 'build.sh'
- 'test.sh'
- '.github/workflows/ci.yml'

concurrency:
group: ci-${{ github.ref }}
Expand Down
86 changes: 62 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,116 @@
# GitHub Project Menu Bar
<p align="center">
<img src="docs/images/icon.png" width="120" alt="GitHub Project Menu Bar icon">
</p>

English / [日本語](docs/README.ja.md)
<h1 align="center">GitHub Project Menu Bar</h1>

A native macOS **menu bar app + WidgetKit widget** to browse and edit your
private **GitHub Projects (v2)** boards. Built with **SwiftUI**.
<p align="center">
Browse and edit your private <b>GitHub Projects (v2)</b> boards from the macOS
menu bar — with a Notification Center / desktop widget.
</p>

- **Menu bar panel**: kanban board — drag cards between columns to change Status,
right-click to change Status, "+" to add a task, click a card to open it on
GitHub. Sorted to match the project view's configured sort.
- **Widget** (Notification Center / desktop): status buttons across the top; tap
one to show that status's tasks. Read-only, refreshes on a timeline.
<p align="center">English / <a href="docs/README.ja.md">日本語</a></p>

## Screenshots

**Menu bar panel** — a kanban board. Drag cards between columns to change Status,
right-click to change Status, "+" to add a task, click a card to open it on GitHub.

<p align="center">
<img src="docs/images/menubar.png" width="720" alt="Menu bar kanban panel">
</p>

**Widget** — status buttons across the top; tap one to show that status's tasks.

<p align="center">
<img src="docs/images/widget.png" width="380" alt="Notification Center widget">
</p>

## Features

- **Kanban board** in the menu bar: view, drag-and-drop Status changes, add tasks (draft issues), open items on GitHub.
- **Sorted like GitHub**: cards follow the project view's configured sort.
- **Status colors**: each card shows its Status color dot.
- **Interactive widget**: pick a status, see its tasks — read-only, auto-refreshing.
- **Menu-bar-only**: no Dock icon; the panel drops down from the menu bar.

## Stack

| Layer | Choice |
|---|---|
| App | SwiftUI `MenuBarExtra` (menu-bar-only, no Dock icon) |
| App | SwiftUI `MenuBarExtra` (no Dock icon) |
| Widget | WidgetKit + interactive AppIntents |
| GitHub API | Projects v2 **GraphQL** (URLSession) |
| Token / shared state | OS **Keychain** (shared between app & widget) |
| Project generation | [XcodeGen](https://github.com/yonaskolb/XcodeGen) (`project.yml`) |

No third-party runtime dependencies (Foundation / SwiftUI / WidgetKit / AppIntents / Security only).

## Layout

```
project.yml XcodeGen spec (app + widget targets, one team)
project.yml XcodeGen spec (app + widget + tests, one team)
build.sh CLI build → sign → install to /Applications → launch
Entitlements/ App.entitlements / Widget.entitlements (sandbox, keychain, network)
test.sh CLI unit tests
Entitlements/ App.entitlements / Widget.entitlements
Sources/
Shared/ compiled into BOTH targets
AppConfig / TokenStore(+SharedStore, BoardCache) / Models
GitHubAPI (GraphQL + view-sort replication) / StatusColor
SelectStatusIntent (widget button intent — must be in the app too)
App/ MenuBarExtra app: App / BoardViewModel / MenuContent / BoardView / SettingsView
Widget/ WidgetKit: BoardProvider / BoardWidgetView
GitHubAPI (GraphQL + view-sort replication) / StatusColor / SelectStatusIntent
App/ MenuBarExtra app + Assets.xcassets (AppIcon)
Widget/ WidgetKit: provider + views
Tests/ Shared-logic unit tests
```

The `.xcodeproj` and `Generated/` are produced by XcodeGen and are git-ignored;
The `.xcodeproj` and `Generated/` are produced by XcodeGen and git-ignored;
`project.yml` is the source of truth.

## Prerequisites

- Full **Xcode** installed
- **XcodeGen** (`brew install xcodegen`)
- Full **Xcode**
- **XcodeGen** `brew install xcodegen`

## Build & install (no need to open Xcode)

```bash
./build.sh
```

This regenerates the project, builds a signed Release, installs it to
Regenerates the project, builds a signed Release, installs it to
`/Applications/GitHubProjectMenuBar.app`, and launches it.

> Signed with a **free personal team** (`DEVELOPMENT_TEAM` in `project.yml`).
> Free provisioning profiles expire after ~7 days — just re-run `./build.sh`.

## Setup

1. Click the menu bar icon → **⚙ Settings** → paste a GitHub **classic PAT**
with the `project` scope (and `repo` if your board draws from private repos).
1. Click the menu bar icon → **⚙ Settings** → paste a GitHub **classic PAT** with
the `project` scope (and `repo` if your board draws from private repos).
2. Pick a project → the board loads.
3. Add the widget from **Edit Widgets** (Notification Center / desktop).

## Tests
The token is stored in the OS **Keychain** and shared with the widget via a
keychain access group (works on a free Apple ID; no App Group needed).

Unit tests cover the Shared logic (view-sort replication, models):
## Tests

```bash
./test.sh
```

Unit tests cover the Shared logic (view-sort replication, models).

## Open in Xcode (optional)

```bash
xcodegen generate && open GitHubProjectMenuBar.xcodeproj
```

## Notes & limitations

- **Manual drag order** (no configured sort) can't be reproduced — the GitHub API
doesn't expose it. Only **field-based sorts** match.
- The widget is **read-only** (WidgetKit); it refreshes on a timeline, and status
switching renders instantly from a cache.
- Built for **personal use on your own Mac** with a free team — not for
distribution. Others must build with their own Apple ID / Team ID.
82 changes: 51 additions & 31 deletions docs/README.ja.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,81 @@
# GitHub Project Menu Bar
<p align="center">
<img src="images/icon.png" width="120" alt="GitHub Project Menu Bar アイコン">
</p>

[English](../README.md) / 日本語
<h1 align="center">GitHub Project Menu Bar</h1>

自分の **GitHub Projects (v2)** のボードを、macOSの**メニューバー**と**ウィジェット**から閲覧・編集できるネイティブアプリです。**SwiftUI** 製。
<p align="center">
自分の <b>GitHub Projects (v2)</b> のボードを、macOSの<b>メニューバー</b>と<b>ウィジェット</b>から閲覧・編集できるネイティブアプリです。
</p>

- **メニューバーパネル**: カンバンボード表示。カードを**ドラッグ**して列(Status)を変更、**右クリック**でもStatus変更、**「+」**でタスク追加、カードをクリックでGitHubを開く。GitHub Project のビューで設定した**ソート順**に並びます。
- **ウィジェット**(通知センター/デスクトップ): 上部に各Statusのボタンが並び、**タップするとそのStatusのタスク**が表示されます。読み取り専用で、一定間隔で更新されます。
<p align="center"><a href="../README.md">English</a> / 日本語</p>

## スクリーンショット

**メニューバーパネル** — カンバンボード。カードを**ドラッグ**して列(Status)を変更、**右クリック**でもStatus変更、**「+」**でタスク追加、カードをクリックでGitHubを開きます。

<p align="center">
<img src="images/menubar.png" width="720" alt="メニューバーのカンバンパネル">
</p>

**ウィジェット** — 上部のStatusボタンをタップすると、そのStatusのタスクが表示されます。

<p align="center">
<img src="images/widget.png" width="380" alt="通知センターのウィジェット">
</p>

## 特徴

- メニューバーの**カンバンボード**: 閲覧・**ドラッグ&ドロップでStatus変更**・タスク追加(ドラフトIssue)・GitHubで開く
- **GitHubと同じ並び**: プロジェクトビューで設定したソート順を再現
- **Status色**: 各カードにStatusの色ドットを表示
- **インタラクティブなウィジェット**: Statusを選んでタスク一覧を表示(読み取り専用・自動更新)
- **メニューバー常駐**: Dockアイコンなし。アイコンからパネルが開きます

## 技術スタック

| レイヤー | 採用 |
|---|---|
| アプリ本体 | SwiftUI `MenuBarExtra`(メニューバー常駐・Dockアイコンなし) |
| アプリ本体 | SwiftUI `MenuBarExtra`(Dockアイコンなし) |
| ウィジェット | WidgetKit + インタラクティブ AppIntents |
| GitHub API | Projects v2 **GraphQL**(URLSession) |
| トークン/共有状態 | OS **Keychain**(本体とウィジェットで共有) |
| プロジェクト生成 | [XcodeGen](https://github.com/yonaskolb/XcodeGen)(`project.yml`) |

サードパーティ製の実行時依存はありません(Foundation / SwiftUI / WidgetKit / AppIntents / Security のみ)。

## ディレクトリ構成

```
project.yml XcodeGen定義(App+Widgetの2ターゲット、Team固定)
project.yml XcodeGen定義(App+Widget+Tests、Team固定)
build.sh CLIビルド → 署名 → /Applications へインストール → 起動
Entitlements/ App.entitlements / Widget.entitlements(サンドボックス・Keychain・ネットワーク)
test.sh CLIユニットテスト
Entitlements/ App.entitlements / Widget.entitlements
Sources/
Shared/ App と Widget の両方にコンパイルされる共通コード
AppConfig / TokenStore(+SharedStore, BoardCache)/ Models
GitHubAPI(GraphQL+ビューのソート再現)/ StatusColor
SelectStatusIntent(ウィジェットのボタン用Intent。本体にも必要なのでShared)
App/ MenuBarExtra アプリ: App / BoardViewModel / MenuContent / BoardView / SettingsView
Widget/ WidgetKit: BoardProvider / BoardWidgetView
App/Assets.xcassets アプリアイコン(AppIcon)
AppConfig / TokenStore(+SharedStore, BoardCache) / Models
GitHubAPI(GraphQL+ビューのソート再現)/ StatusColor / SelectStatusIntent
App/ MenuBarExtra アプリ + Assets.xcassets(AppIcon)
Widget/ WidgetKit: プロバイダ+ビュー
Tests/ 共通ロジックのユニットテスト
```

`.xcodeproj` と `Generated/` は XcodeGen が生成する成果物なので **gitignore** しています。**source of truth は `project.yml`** です。
`.xcodeproj` と `Generated/` は XcodeGen の生成物で **gitignore** しています。**source of truth は `project.yml`** です。

## 必要なもの

- **Xcode** 本体(フル版。CommandLine Toolsのみでは不可
- **XcodeGen**`brew install xcodegen`
- **Xcode** 本体(フル版)
- **XcodeGen**`brew install xcodegen`

## ビルド&インストール(Xcodeを開かずに実行)

```bash
./build.sh
```

`build.sh` は次を自動で行います。

1. `xcodegen generate`(プロジェクト再生成)
2. `xcodebuild`(Release・**署名付き**)でアプリ+ウィジェットをビルド
3. `/Applications/GitHubProjectMenuBar.app` に上書きインストール
4. アプリを起動
プロジェクト再生成 → 署名付きReleaseビルド → `/Applications/GitHubProjectMenuBar.app` にインストール → 起動、までを自動で行います。

> **無料のPersonal Team**(`project.yml` の `DEVELOPMENT_TEAM`)で署名しています
> **無料のPersonal Team**(`project.yml` の `DEVELOPMENT_TEAM`)で署名します
> 無料Teamのプロビジョニングは**約7日で失効**するので、切れたら `./build.sh` を再実行してください。

## 使い方
Expand All @@ -62,18 +84,16 @@ Sources/
2. プロジェクトを選択 → ボードが読み込まれます
3. **ウィジェットを編集**(通知センター/デスクトップ)から「GitHub Project」を追加

### トークンについて
- トークンは **OS Keychain** に保存され、ソースやファイルには平文で残りません。
- 本体とウィジェットは `keychain-access-groups` で同じ項目を共有します(無料Apple IDでも動作。App Groupは有料が必要なため未使用)。
トークンは OS **Keychain** に保存され、Keychainアクセスグループで本体↔ウィジェットが共有します(無料Apple IDで動作。App Groupは不要)。

## テスト

共通ロジック(ビューのソート再現・モデル)のユニットテストがあります。

```bash
./test.sh
```

共通ロジック(ビューのソート再現・モデル)のユニットテストがあります。

## Xcodeで開く場合(任意)

```bash
Expand All @@ -83,5 +103,5 @@ xcodegen generate && open GitHubProjectMenuBar.xcodeproj
## 補足・既知の制約

- **手動ドラッグの並び順**(ソート未設定)はGitHub APIが公開しておらず再現できません。ビューで**フィールドソート**を設定している場合のみ順序が一致します。
- ウィジェットは WidgetKit の仕様上**読み取り専用**で、更新はタイムライン(数十分間隔)に依存します。Statusボタンの切り替えはキャッシュから即時描画されます。
- **配布不可・自分のMac専用**(無料Team)。他人が使うには各自の Apple ID / Team ID に置き換えてビルドが必要です
- ウィジェットは WidgetKit の仕様上**読み取り専用**で、更新はタイムラインに依存します。Statusボタンの切り替えはキャッシュから即時描画されます。
- **自分のMac専用**(無料Team)。配布は不可で、他人が使うには各自の Apple ID / Team ID でビルドが必要です
Binary file added docs/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/menubar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading