Skip to content

Commit f9352a5

Browse files
committed
✨ feat(app): build Token Menu Bar
The prototype moved on tab switches and hid content behind scrolling. Provider work repeated during redraws. Anchor the popover to the status item. Keep tab views alive and cache presenter results. Isolate tests from credentials and HTTP, then verify each distribution on macOS 14 through 27.
1 parent 53b4ff8 commit f9352a5

382 files changed

Lines changed: 59678 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: ci-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
lint:
18+
name: Lint
19+
runs-on: macos-26
20+
steps:
21+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22+
with:
23+
persist-credentials: false
24+
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
25+
- name: Select Xcode
26+
run: Scripts/select-xcode.sh
27+
- name: Run every hook
28+
run: just lint
29+
30+
package-compatibility:
31+
name: Package (${{ matrix.name }})
32+
runs-on: ${{ matrix.runner }}
33+
strategy:
34+
fail-fast: false
35+
matrix:
36+
include:
37+
- name: macOS 14 / Xcode 16
38+
runner: ${{ vars.MACOS_14_RUNNER || 'macos-14' }}
39+
minimum-xcode: 16
40+
- name: macOS 15 / Xcode 26
41+
runner: macos-15
42+
minimum-xcode: 26
43+
- name: Xcode 27 SDK
44+
runner: ${{ vars.XCODE_27_RUNNER || 'xcode-27' }}
45+
minimum-xcode: 27
46+
steps:
47+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
48+
with:
49+
persist-credentials: false
50+
- name: Select Xcode
51+
id: xcode
52+
env:
53+
MIN_XCODE_MAJOR: ${{ matrix.minimum-xcode }}
54+
EXACT_XCODE_MAJOR: ${{ matrix.minimum-xcode }}
55+
run: |
56+
Scripts/select-xcode.sh
57+
version="$(xcodebuild -version | tr '\n' '-')"
58+
echo "cache=$(printf '%s' "$version" | shasum -a 256 | cut -c 1-12)" >> "$GITHUB_OUTPUT"
59+
- name: Cache SwiftPM
60+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
61+
with:
62+
path: .build
63+
key: spm-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift')
64+
}}
65+
- name: Build
66+
run: swift build
67+
- name: Test
68+
run: Scripts/check-test-isolation.sh && swift test --no-parallel
69+
70+
coverage:
71+
name: Tests and coverage (macOS 26)
72+
runs-on: macos-26
73+
steps:
74+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
75+
with:
76+
persist-credentials: false
77+
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
78+
- name: Select Xcode
79+
id: xcode
80+
run: |
81+
Scripts/select-xcode.sh
82+
version="$(xcodebuild -version | tr '\n' '-')"
83+
echo "cache=$(printf '%s' "$version" | shasum -a 256 | cut -c 1-12)" >> "$GITHUB_OUTPUT"
84+
- name: Cache SwiftPM
85+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
86+
with:
87+
path: .build
88+
key: spm-${{ runner.os }}-${{ runner.arch }}-macos-26-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift')
89+
}}
90+
- name: Build
91+
run: swift build
92+
- name: Tests with coverage gate
93+
run: just coverage
94+
95+
application-ui:
96+
name: Application UI (${{ matrix.name }})
97+
runs-on: ${{ matrix.runner }}
98+
strategy:
99+
fail-fast: false
100+
matrix:
101+
include:
102+
- name: macOS 14
103+
runner: ${{ vars.MACOS_14_RUNNER || 'macos-14' }}
104+
minimum-xcode: 16
105+
- name: macOS 15
106+
runner: macos-15
107+
minimum-xcode: 26
108+
- name: macOS 26
109+
runner: macos-26
110+
minimum-xcode: 26
111+
- name: Xcode 27 SDK
112+
runner: ${{ vars.XCODE_27_RUNNER || 'xcode-27' }}
113+
minimum-xcode: 27
114+
steps:
115+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
116+
with:
117+
persist-credentials: false
118+
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
119+
- name: Select Xcode
120+
id: xcode
121+
env:
122+
MIN_XCODE_MAJOR: ${{ matrix.minimum-xcode }}
123+
EXACT_XCODE_MAJOR: ${{ matrix.minimum-xcode }}
124+
run: |
125+
Scripts/select-xcode.sh
126+
version="$(xcodebuild -version | tr '\n' '-')"
127+
echo "cache=$(printf '%s' "$version" | shasum -a 256 | cut -c 1-12)" >> "$GITHUB_OUTPUT"
128+
- name: Cache Xcode packages
129+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
130+
with:
131+
path: .build/xcode-packages
132+
key: xcode-spm-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift',
133+
'App/project.yml') }}
134+
- name: Generate project
135+
run: just xcode
136+
- name: Test the running application
137+
run: |
138+
set -o pipefail
139+
xcodebuild -project App/TokenMenuBar.xcodeproj -scheme TokenMenuBar-Direct -configuration Debug \
140+
-destination 'platform=macOS' -derivedDataPath .build/ui-derived \
141+
-clonedSourcePackagesDirPath .build/xcode-packages \
142+
-only-testing:TokenMenuBarApplicationUITests test | tail -50
143+
144+
application:
145+
name: Build ${{ matrix.scheme }} (${{ matrix.name }})
146+
runs-on: ${{ matrix.runner }}
147+
strategy:
148+
fail-fast: false
149+
matrix:
150+
include:
151+
- name: macOS 26
152+
runner: macos-26
153+
minimum-xcode: 26
154+
scheme: TokenMenuBar-Direct
155+
target: TokenMenuBarDirect
156+
configuration: Release
157+
distribution: Direct
158+
condition: DIRECT
159+
sandbox: NO
160+
entitlements: Direct.entitlements
161+
updater: required
162+
- name: macOS 26
163+
runner: macos-26
164+
minimum-xcode: 26
165+
scheme: TokenMenuBar-AppStore
166+
target: TokenMenuBarAppStore
167+
configuration: AppStore
168+
distribution: App Store
169+
condition: APPSTORE
170+
sandbox: YES
171+
entitlements: AppStore.entitlements
172+
updater: forbidden
173+
- name: macOS 26
174+
runner: macos-26
175+
minimum-xcode: 26
176+
scheme: TokenMenuBar-Homebrew
177+
target: TokenMenuBarHomebrew
178+
configuration: Homebrew
179+
distribution: Homebrew
180+
condition: HOMEBREW
181+
sandbox: NO
182+
entitlements: Direct.entitlements
183+
updater: forbidden
184+
- name: Xcode 27 SDK
185+
runner: ${{ vars.XCODE_27_RUNNER || 'xcode-27' }}
186+
minimum-xcode: 27
187+
scheme: TokenMenuBar-Direct
188+
target: TokenMenuBarDirect
189+
configuration: Release
190+
distribution: Direct
191+
condition: DIRECT
192+
sandbox: NO
193+
entitlements: Direct.entitlements
194+
updater: required
195+
- name: Xcode 27 SDK
196+
runner: ${{ vars.XCODE_27_RUNNER || 'xcode-27' }}
197+
minimum-xcode: 27
198+
scheme: TokenMenuBar-AppStore
199+
target: TokenMenuBarAppStore
200+
configuration: AppStore
201+
distribution: App Store
202+
condition: APPSTORE
203+
sandbox: YES
204+
entitlements: AppStore.entitlements
205+
updater: forbidden
206+
- name: Xcode 27 SDK
207+
runner: ${{ vars.XCODE_27_RUNNER || 'xcode-27' }}
208+
minimum-xcode: 27
209+
scheme: TokenMenuBar-Homebrew
210+
target: TokenMenuBarHomebrew
211+
configuration: Homebrew
212+
distribution: Homebrew
213+
condition: HOMEBREW
214+
sandbox: NO
215+
entitlements: Direct.entitlements
216+
updater: forbidden
217+
steps:
218+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
219+
with:
220+
persist-credentials: false
221+
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
222+
- name: Select Xcode
223+
id: xcode
224+
env:
225+
MIN_XCODE_MAJOR: ${{ matrix.minimum-xcode }}
226+
EXACT_XCODE_MAJOR: ${{ matrix.minimum-xcode }}
227+
run: |
228+
Scripts/select-xcode.sh
229+
version="$(xcodebuild -version | tr '\n' '-')"
230+
echo "cache=$(printf '%s' "$version" | shasum -a 256 | cut -c 1-12)" >> "$GITHUB_OUTPUT"
231+
- name: Cache Xcode packages
232+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
233+
with:
234+
path: .build/xcode-packages
235+
key: xcode-spm-${{ runner.os }}-${{ runner.arch }}-${{ matrix.runner }}-${{ steps.xcode.outputs.cache }}-${{ hashFiles('Package.swift',
236+
'App/project.yml') }}
237+
- name: Generate project
238+
run: just xcode
239+
- name: Build
240+
env:
241+
SCHEME: ${{ matrix.scheme }}
242+
CONFIGURATION: ${{ matrix.configuration }}
243+
run: |
244+
set -o pipefail
245+
xcodebuild -project App/TokenMenuBar.xcodeproj -scheme "$SCHEME" -configuration "$CONFIGURATION" \
246+
-destination 'platform=macOS' -derivedDataPath .build/app-derived \
247+
-clonedSourcePackagesDirPath .build/xcode-packages CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY="" \
248+
SPARKLE_PUBLIC_ED_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= build \
249+
| tail -30
250+
- name: Verify distribution settings
251+
env:
252+
CONFIGURATION: ${{ matrix.configuration }}
253+
DISTRIBUTION: ${{ matrix.distribution }}
254+
CONDITION: ${{ matrix.condition }}
255+
SANDBOX: ${{ matrix.sandbox }}
256+
ENTITLEMENTS: ${{ matrix.entitlements }}
257+
TARGET: ${{ matrix.target }}
258+
run: Scripts/verify-build-settings.sh "$TARGET" "$CONFIGURATION" "$DISTRIBUTION" "$CONDITION" "$SANDBOX" "$ENTITLEMENTS"
259+
- name: Verify every shipped deployment target
260+
run: |
261+
app="$(find .build/app-derived/Build/Products -path '*/Token Menu Bar.app' -type d | head -1)"
262+
test -n "$app"
263+
Scripts/verify-deployment-targets.sh "$app" 14.0
264+
Scripts/verify-app-bundle.sh "$app" "${{ matrix.distribution }}" "${{ matrix.updater }}"
265+
- name: Package the macOS 14 smoke artifact
266+
if: matrix.runner == 'macos-26' && matrix.scheme == 'TokenMenuBar-Direct'
267+
run: |
268+
app="$(find .build/app-derived/Build/Products -path '*/Token Menu Bar.app' -type d | head -1)"
269+
codesign --force --deep --sign - "$app"
270+
ditto -c -k --keepParent "$app" .build/token-menu-bar-macos-14-smoke.zip
271+
- name: Upload the macOS 14 smoke artifact
272+
if: matrix.runner == 'macos-26' && matrix.scheme == 'TokenMenuBar-Direct'
273+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
274+
with:
275+
name: token-menu-bar-macos-14-smoke
276+
path: .build/token-menu-bar-macos-14-smoke.zip
277+
if-no-files-found: error
278+
retention-days: 1
279+
280+
macos-14-runtime:
281+
name: Release smoke (macOS 14 runtime)
282+
needs: application
283+
runs-on: ${{ vars.MACOS_14_RUNTIME_RUNNER || 'macos-14' }}
284+
steps:
285+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
286+
with:
287+
name: token-menu-bar-macos-14-smoke
288+
path: smoke
289+
- name: Launch with isolated data
290+
run: |
291+
ditto -x -k smoke/token-menu-bar-macos-14-smoke.zip smoke/app
292+
executable="smoke/app/Token Menu Bar.app/Contents/MacOS/Token Menu Bar"
293+
runtime="$PWD/smoke/runtime"
294+
session="macos-14-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
295+
support="$runtime/token-menu-bar-verify-$session"
296+
ready="$support/snapshots-demo.json"
297+
mkdir -p "$support"
298+
TOKEN_MENU_BAR_VERIFY_SESSION="$session" TOKEN_MENU_BAR_VERIFY_SUPPORT_DIRECTORY="$support" \
299+
"$executable" --verify-ui >smoke/launch.log 2>&1 &
300+
pid=$!
301+
for _ in {1..80}; do
302+
if grep -Fq '"claude"' "$ready" 2>/dev/null && grep -Fq '"codex"' "$ready" 2>/dev/null; then
303+
break
304+
fi
305+
if ! kill -0 "$pid" 2>/dev/null; then
306+
cat smoke/launch.log
307+
exit 1
308+
fi
309+
sleep 0.25
310+
done
311+
if ! grep -Fq '"claude"' "$ready" 2>/dev/null || ! grep -Fq '"codex"' "$ready" 2>/dev/null; then
312+
cat smoke/launch.log
313+
test ! -f "$support/log.txt" || cat "$support/log.txt"
314+
kill "$pid" 2>/dev/null || true
315+
wait "$pid" || true
316+
exit 1
317+
fi
318+
kill "$pid" 2>/dev/null || true
319+
wait "$pid" || true
320+
321+
macos-27-runtime:
322+
name: Application UI (macOS 27 runtime)
323+
if: vars.MACOS_27_RUNTIME_RUNNER != ''
324+
runs-on: ${{ vars.MACOS_27_RUNTIME_RUNNER }}
325+
steps:
326+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
327+
with:
328+
persist-credentials: false
329+
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
330+
- name: Select Xcode
331+
env:
332+
MIN_XCODE_MAJOR: 27
333+
EXACT_XCODE_MAJOR: 27
334+
run: Scripts/select-xcode.sh
335+
- name: Generate project
336+
run: just xcode
337+
- name: Test the running application
338+
run: |
339+
set -o pipefail
340+
xcodebuild -project App/TokenMenuBar.xcodeproj -scheme TokenMenuBar-Direct -configuration Debug \
341+
-destination 'platform=macOS' -derivedDataPath .build/ui-derived \
342+
-only-testing:TokenMenuBarApplicationUITests test | tail -50
343+
344+
macos-27-runtime-policy:
345+
name: Required macOS 27 runtime
346+
if: always()
347+
needs: macos-27-runtime
348+
runs-on: ubuntu-24.04
349+
steps:
350+
- name: Require real runtime coverage
351+
env:
352+
RUNNER: ${{ vars.MACOS_27_RUNTIME_RUNNER }}
353+
RESULT: ${{ needs.macos-27-runtime.result }}
354+
run: |
355+
if [ -z "$RUNNER" ]; then
356+
echo "::error::Set MACOS_27_RUNTIME_RUNNER to a self-hosted runner running macOS 27"
357+
echo "Xcode 27 jobs verify the SDK, not the macOS 27 runtime. This required status stays red until a real runtime runner is configured." \
358+
>> "$GITHUB_STEP_SUMMARY"
359+
exit 1
360+
elif [ "$RESULT" != "success" ]; then
361+
echo "::error::The configured macOS 27 runtime job did not pass"
362+
exit 1
363+
else
364+
echo "macOS 27 runtime UI tests passed on $RUNNER." >> "$GITHUB_STEP_SUMMARY"
365+
fi

0 commit comments

Comments
 (0)